#include "e.h"

/* local subsystem functions */

/* local subsystem globals */

/* externally accessible functions */

E_API E_About *
e_about_new(void)
{
   E_Obj_Dialog *od;
   char buf[16384];
   FILE *f;
   Eina_Strbuf *tbuf;

   od = e_obj_dialog_new(_("About Enlightenment"), "E", "_about");
   if (!od) return NULL;
   e_obj_dialog_obj_theme_set(od, "base/theme/about", "e/widgets/about/main");
   e_obj_dialog_obj_part_text_set(od, "e.text.label", _("Close"));
   e_obj_dialog_obj_part_text_set(od, "e.text.title", _("Enlightenment"));
   e_obj_dialog_obj_part_text_set(od, "e.text.version", VERSION);
   snprintf
     (buf, sizeof(buf), "%s%s",
     _(
       "<title>Copyright &copy; 2000-2022, by the Enlightenment "
       "Development Team</><ps/>"
       "<ps/>"
       "We hope you enjoy using this software as much as we enjoyed "
       "writing it.<ps/>"
       "<ps/>"
       "To contact us please visit:<ps/>"
       "<hilight>http://www.enlightenment.org</><ps/>"
       "<ps/>"
       ),
     "All rights reserved.<ps/>"
     "<ps/>"
     "Redistribution and use in source and binary forms, with or without "
     "modification, are permitted provided that the following conditions "
     "are met:<ps/>"
     "<ps/>"
     "1. Redistributions of source code must retain the above copyright "
     "notice, this list of conditions and the following disclaimer.<ps/>"
     "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.<ps/>"
     "<ps/>"
     "<hilight>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.</><ps/>"
     );
   e_obj_dialog_obj_part_text_set(od, "e.textblock.about", buf);

   e_prefix_data_concat_static(buf, "AUTHORS");
   f = fopen(buf, "r");
   if (f)
     {
        tbuf = eina_strbuf_new();
        eina_strbuf_append(tbuf, _("<title>The Team</><ps/><ps/>"));
        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)
                    {
                       char *p;

                       do
                         {
                            p = strchr(buf, '<');
                            if (p) *p = 0;
                         }
                       while (p);
                       do
                         {
                            p = strchr(buf, '>');
                            if (p) *p = 0;
                         }
                       while (p);
                       eina_strbuf_append_printf(tbuf, "%s<ps/>", buf);
                    }
               }
          }
        fclose(f);
        if (tbuf)
          {
             e_obj_dialog_obj_part_text_set(od, "e.textblock.authors",
                                            eina_strbuf_string_get(tbuf));
             eina_strbuf_free(tbuf);
          }
     }
   return (E_About *)od;
}

E_API void
e_about_show(E_About *about)
{
   e_obj_dialog_show((E_Obj_Dialog *)about);
   e_obj_dialog_icon_set((E_Obj_Dialog *)about, "help-about");
}

#include "e.h"

/* TODO:
 *
 * Sanatize data received from acpi for message status into something
 * meaningful (ie: 00000002 == LID_CLOSED, etc, etc).
 *
 * Find someone with a WIFI that actually emits ACPI events and add/debug the
 * E_EVENT_ACPI for wifi.
 *
 */

/* local structures */
/* for simple acpi device mapping */
typedef struct _E_ACPI_Device_Simple       E_ACPI_Device_Simple;
typedef struct _E_ACPI_Device_Simple_State E_ACPI_Device_Simple_State;
typedef struct _E_ACPI_Device_Multiplexed  E_ACPI_Device_Multiplexed;

struct _E_ACPI_Device_Simple
{
   const char *name;
   // ->
   int         type;
};

struct _E_ACPI_Device_Simple_State
{
   const char *name;
   const char *bus;
   const char *state;
   // ->
   int         type;
};

struct _E_ACPI_Device_Multiplexed
{
   const char *name;
   const char *bus;
   int         status;
   // ->
   int         type;
};

/* local function prototypes */
static Eina_Bool _e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
static Eina_Bool _e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
static void      _e_acpi_cb_event_free(void *data EINA_UNUSED, void *event);
static int       _e_acpi_lid_status_get(const char *device, const char *bus);
static Eina_Bool _e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);

/* local variables */
static int _e_acpi_events_frozen = 0;
static Ecore_Con_Server *_e_acpid = NULL;
static Eina_List *_e_acpid_hdls = NULL;
static Eina_Strbuf *acpibuf = NULL;
static int lid_is_closed = -1;

static E_ACPI_Device_Simple _devices_simple[] =
{
   /* NB: DO NOT TRANSLATE THESE. */
   {"ac_adapter", E_ACPI_TYPE_AC_ADAPTER},
   {"battery", E_ACPI_TYPE_BATTERY},
   {"button/lid", E_ACPI_TYPE_LID},
   {"button/power", E_ACPI_TYPE_POWER},
   {"button/sleep", E_ACPI_TYPE_SLEEP},
   {"button/volumedown", E_ACPI_TYPE_VOLUME_DOWN},
   {"button/volumeup", E_ACPI_TYPE_VOLUME_UP},
   {"button/mute", E_ACPI_TYPE_MUTE},
   {"button/wlan", E_ACPI_TYPE_WIFI},
   {"fan", E_ACPI_TYPE_FAN},
   {"processor", E_ACPI_TYPE_PROCESSOR},
   {"thermal_zone", E_ACPI_TYPE_THERMAL},
   {"video", E_ACPI_TYPE_VIDEO},
   {"video/brightnessdown", E_ACPI_TYPE_BRIGHTNESS_DOWN},
   {"video/brightnessup", E_ACPI_TYPE_BRIGHTNESS_UP},
   {"video/switchmode", E_ACPI_TYPE_VIDEO},
   {"button/zoom", E_ACPI_TYPE_ZOOM},
   {"button/screenlock", E_ACPI_TYPE_SCREENLOCK},
   {"button/battery", E_ACPI_TYPE_BATTERY_BUTTON},
   {"video/tabletmode", E_ACPI_TYPE_TABLET},

   //bluetooth virtual input devices for A/V Remote Control
   {"cd/next", E_ACPI_TYPE_CD_NEXT},
   {"cd/prev", E_ACPI_TYPE_CD_PREV},
   {"cd/stop", E_ACPI_TYPE_CD_STOP},
   {"cd/play", E_ACPI_TYPE_CD_PLAY},

   {NULL, E_ACPI_TYPE_UNKNOWN}
};

static E_ACPI_Device_Simple_State _devices_simple_state[] =
{
   /* NB: DO NOT TRANSLATE THESE. */
   {"video/tabletmode", "TBLT", "on", E_ACPI_TYPE_TABLET_ON},
   {"video/tabletmode", "TBLT", "off", E_ACPI_TYPE_TABLET_OFF},

   {NULL, NULL, NULL, E_ACPI_TYPE_UNKNOWN}
};

static E_ACPI_Device_Multiplexed _devices_multiplexed[] =
{
   /* NB: DO NOT TRANSLATE THESE. */
/* Sony VAIO - VPCF115FM / PCG-81114L - nvidia gfx */
   {"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS_DOWN},
   {"sony/hotkey", NULL, 0x11, E_ACPI_TYPE_BRIGHTNESS_UP},
   {"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},
   {"sony/hotkey", NULL, 0x14, E_ACPI_TYPE_ZOOM_OUT},
   {"sony/hotkey", NULL, 0x15, E_ACPI_TYPE_ZOOM_IN},
   {"sony/hotkey", NULL, 0x17, E_ACPI_TYPE_HIBERNATE},
   {"sony/hotkey", NULL, 0xa6, E_ACPI_TYPE_ASSIST},
   {"sony/hotkey", NULL, 0x20, E_ACPI_TYPE_S1},
   {"sony/hotkey", NULL, 0xa5, E_ACPI_TYPE_VAIO},

/* Sony VAIO - X505 - intel gfx */
   {"sony/hotkey", NULL, 0x0e, E_ACPI_TYPE_MUTE},
   {"sony/hotkey", NULL, 0x0f, E_ACPI_TYPE_VOLUME},
   {"sony/hotkey", NULL, 0x10, E_ACPI_TYPE_BRIGHTNESS},
   {"sony/hotkey", NULL, 0x12, E_ACPI_TYPE_VIDEO},

/* HP Compaq Presario - CQ61 - intel gfx */
/** interesting these get auto-mapped to keys in x11. here for documentation
** but not enabled as we can use regular keybinds for these
   {"video",       "DD03", 0x87, E_ACPI_TYPE_BRIGHTNESS_DOWN},
   {"video",       "DD03", 0x86, E_ACPI_TYPE_BRIGHTNESS_UP},
   {"video",       "OVGA", 0x80, E_ACPI_TYPE_VIDEO},
*/
/* END */
   {NULL, NULL, 0x00, E_ACPI_TYPE_UNKNOWN}
};

/* public variables */
E_API int E_EVENT_ACPI = 0;

static Eina_Bool
_acpi_error_cb(void *data EINA_UNUSED)
{
   e_util_dialog_show
     (_("ACPI Error"),
      _("You seem to have an ACPI based system, but<br>"
        "<hilight>acpid</hilight> does not seem to be running or<br>"
        "contactable. Perhaps enable the <hilight>acpid</hilight><br>"
        "service on your system?"));
   return EINA_FALSE;
}

/* public functions */
EINTERN int
e_acpi_init(void)
{
   E_EVENT_ACPI = ecore_event_type_new();

   /* check for running acpid */
   if (!ecore_file_exists("/var/run/acpid.socket"))
     {
        if (ecore_file_exists("/proc/acpi"))
          ecore_timer_add(5.0, _acpi_error_cb, NULL);
        return 1;
     }

   /* try to connect to acpid socket */
   _e_acpid = ecore_con_server_connect(ECORE_CON_LOCAL_SYSTEM,
                                       "/var/run/acpid.socket", -1, NULL);
   if (!_e_acpid) return 1;

   /* setup handlers */
   _e_acpid_hdls =
     eina_list_append(_e_acpid_hdls,
                      ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DEL,
                                              _e_acpi_cb_server_del, NULL));
   _e_acpid_hdls =
     eina_list_append(_e_acpid_hdls,
                      ecore_event_handler_add(ECORE_CON_EVENT_SERVER_DATA,
                                              _e_acpi_cb_server_data, NULL));

   /* Add handlers for standard acpi events */
   _e_acpid_hdls =
     eina_list_append(_e_acpid_hdls,
                      ecore_event_handler_add(E_EVENT_ACPI,
                                              _e_acpi_cb_event, NULL));
   return 1;
}

EINTERN int
e_acpi_shutdown(void)
{
   Ecore_Event_Handler *hdl;

   /* cleanup event handlers */
   EINA_LIST_FREE(_e_acpid_hdls, hdl)
     ecore_event_handler_del(hdl);

   /* kill the server if existing */
   if (_e_acpid)
     {
        ecore_con_server_del(_e_acpid);
        _e_acpid = NULL;
     }
   return 1;
}

EINTERN E_Acpi_Lid_Status
e_acpi_lid_status_get(void)
{
   int i;

   for (i = 0; _devices_simple[i].name; i++)
     {
        if (_devices_simple[i].type == E_ACPI_TYPE_LID)
          {
             /* TODO: Can bus be anything other than LID? */
             return _e_acpi_lid_status_get(_devices_simple[i].name, "LID");
          }
     }

   return E_ACPI_LID_UNKNOWN;
}

E_API Eina_Bool
e_acpi_lid_is_closed(void)
{
   if (lid_is_closed == -1)
     lid_is_closed = (e_acpi_lid_status_get() == E_ACPI_LID_CLOSED);
   return lid_is_closed;
}

E_API void
e_acpi_events_freeze(void)
{
   _e_acpi_events_frozen++;
}

E_API void
e_acpi_events_thaw(void)
{
   _e_acpi_events_frozen--;
   if (_e_acpi_events_frozen < 0) _e_acpi_events_frozen = 0;
}

/* local functions */
static Eina_Bool
_e_acpi_cb_server_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Con_Event_Server_Del *ev;
   Ecore_Event_Handler *hdl;

   ev = event;
   if (ev->server != _e_acpid) return ECORE_CALLBACK_PASS_ON;

   /* cleanup event handlers */
   EINA_LIST_FREE(_e_acpid_hdls, hdl)
     ecore_event_handler_del(hdl);

   /* kill the server if existing */
   if (_e_acpid)
     {
        ecore_con_server_del(_e_acpid);
        _e_acpid = NULL;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_acpi_cb_server_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Con_Event_Server_Data *ev;
   E_Event_Acpi *acpi_event;
   int sig, status, i, done = 0;
   char device[1024], bus[1024], state[1024], *sdata;
   const char *str, *p;

   ev = event;

   if ((!ev->data) || (ev->size < 1)) return ECORE_CALLBACK_PASS_ON;

   /* write out actual acpi received data to stdout for debugging
      res = fwrite(ev->data, ev->size, 1, stdout);
    */
   /* data from a server isn't a string - its not 0 byte terminated. it's just
    * a blob of data. copy to string and 0 byte terminate it so it can be
    * string-swizzled/parsed etc. */
   if (!acpibuf) acpibuf = eina_strbuf_new();
   eina_strbuf_append_length(acpibuf, ev->data, ev->size);
   str = eina_strbuf_string_get(acpibuf);
   p = strchr(str, '\n');
   if (!p) return ECORE_CALLBACK_PASS_ON;
   while (p)
     {
        device[0] = bus[0] = state[0] = 0;
        sdata = alloca(p - str + 1);
        strncpy(sdata, str, (int)(p - str));
        sdata[p - str] = 0;
        /* parse out this acpi string into separate pieces */
        if (sscanf(sdata, "%1023s %1023s %x %x",
                   device, bus, &sig, &status) != 4)
          {
             sig = -1;
             status = -1;
             if (sscanf(sdata, "%1023s %1023s", device, bus) != 2)
               {
                  if (sscanf(sdata, "%1023s %1023s %1023s", device, bus, state) != 3)
                    goto done_event;
               }
          }

        /* create new event structure to raise */
        acpi_event = E_NEW(E_Event_Acpi, 1);
        acpi_event->bus_id = eina_stringshare_add(bus);
        acpi_event->signal = sig;
        acpi_event->status = status;

        /* FIXME: add in a key faking layer */
        if ((!done) && (sig >= 0) && (status >= 0))
          {
             for (i = 0; _devices_multiplexed[i].name; i++)
               {
                  // if device name matches
                  if ((!strcmp(device, _devices_multiplexed[i].name)) &&
                      // AND busname not set OR device name matches
                      (!_devices_multiplexed[i].bus ||
                       (_devices_multiplexed[i].bus &&
                        (!strcmp(bus, _devices_multiplexed[i].bus)))) &&
                      // AND status matches
                      (_devices_multiplexed[i].status == status))
                    {
                       acpi_event->type = _devices_multiplexed[i].type;
                       done = 1;
                       break;
                    }
               }
          }
        if ((!done) && (state[0]))
          {
             for (i = 0; _devices_simple_state[i].name; i++)
               {
                  if ((!strcmp(device, _devices_simple_state[i].name)) &&
                      ((!_devices_simple_state[i].bus) || (!strcmp(bus, _devices_simple_state[i].bus))) &&
                      (!strcmp(state, _devices_simple_state[i].state)))
                    {
                       acpi_event->type =  _devices_simple_state[i].type;
                       done = 1;
                       break;
                    }
               }
          }
        if (!done)
          {
             // if device name matches
             for (i = 0; _devices_simple[i].name; i++)
               {
                  if (!strcmp(device, _devices_simple[i].name))
                    {
                       acpi_event->type = _devices_simple[i].type;
                       done = 1;
                       break;
                    }
               }
          }
        if (!done)
          {
             free(acpi_event);
             acpi_event = NULL;
          }
        else
          {
             switch (acpi_event->type)
               {
                case E_ACPI_TYPE_LID:
                  acpi_event->status =
                    _e_acpi_lid_status_get(device, bus);
                  printf("RRR: acpi event @%1.8f\n", ecore_time_get());
                  /* no change in lid state */
                  if (lid_is_closed == (acpi_event->status == E_ACPI_LID_CLOSED)) break;
                  lid_is_closed = (acpi_event->status == E_ACPI_LID_CLOSED);
                  printf("RRR: lid event for lid %i\n", lid_is_closed);
                  if (!e_randr2_cfg->ignore_acpi_events)
                    e_randr2_screen_refresh_queue(EINA_TRUE);
                  if (!lid_is_closed) e_powersave_defer_cancel();
                  break;

                default:
                  break;
               }
             /* actually raise the event */
             ecore_event_add(E_EVENT_ACPI, acpi_event,
                             _e_acpi_cb_event_free, NULL);
          }
done_event:
        str = p + 1;
        p = strchr(str, '\n');
     }
   if (str[0] == 0)
     {
        eina_strbuf_free(acpibuf);
        acpibuf = NULL;
     }
   else
     {
        Eina_Strbuf *newbuf;

        newbuf = eina_strbuf_new();
        eina_strbuf_append(newbuf, str);
        eina_strbuf_free(acpibuf);
        acpibuf = newbuf;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_acpi_cb_event_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Acpi *ev;

   if (!(ev = event)) return;
   if (ev->device) eina_stringshare_del(ev->device);
   if (ev->bus_id) eina_stringshare_del(ev->bus_id);
   E_FREE(ev);
}

static int
_e_acpi_lid_status_get(const char *device, const char *bus)
{
   FILE *f;
   int i = 0;
   char buff[PATH_MAX], *ret;

   /* the acpi driver code in the kernel has a nice acpi function to return
    * the lid status easily, but that function is not exposed for user_space
    * so we need to check the proc fs to get the actual status */

   /* make sure we have a device and bus */
   if ((!device) || (!bus)) return E_ACPI_LID_UNKNOWN;

   /* open the state file from /proc */
   snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s/state", device, bus);
   if (!(f = fopen(buff, "r")))
     {
        /* hack around ppurka's Thinkpad (G460 + Linux) that reports lid
         * state as "/proc/acpi/button/lid/LID0/state" but where the lid
         * event says "button/lid LID close".
         *
         * so let's take the base device name "LID" and add a numeric like
         * 0, 1, 2, 3 so we have LID0, LID1, LID2 etc. - try up to LID9
         * and then give up.
         */
        for (i = 0; i < 10; i++)
          {
             snprintf(buff, sizeof(buff), "/proc/acpi/%s/%s%i/state",
                      device, bus, i);
             if ((f = fopen(buff, "r"))) break;
             f = NULL;
          }
        if (!f) return E_ACPI_LID_UNKNOWN;
     }

   /* read the line from state file */
   ret = fgets(buff, sizeof(buff), f);
   fclose(f);
   if (!ret)
     return E_ACPI_LID_UNKNOWN;

   /* parse out state file */
   i = 0;
   while (buff[i] != ':')
     i++;
   while (!isalnum(buff[i]))
     i++;
   ret = &(buff[i]);
   while (isalnum(buff[i]))
     i++;
   buff[i] = 0;

   /* compare value from state file and return something sane */
   if (!strcmp(ret, "open")) return E_ACPI_LID_OPEN;
   else if (!strcmp(ret, "closed"))
     return E_ACPI_LID_CLOSED;
   else return E_ACPI_LID_UNKNOWN;
}

static Eina_Bool
_e_acpi_cb_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Acpi *ev;

   ev = event;
   if (_e_acpi_events_frozen > 0) return ECORE_CALLBACK_PASS_ON;
   e_bindings_acpi_event_handle(E_BINDING_CONTEXT_NONE, NULL, ev);
   return ECORE_CALLBACK_PASS_ON;
}

#include "e.h"

#define INITS
#define ACT_GO(name)                                      \
  {                                                       \
     act = e_action_add(#name);                           \
     if (act) act->func.go = _e_actions_act_##name##_go;  \
  }
#define ACT_FN_GO(act, use) \
  static void _e_actions_act_##act##_go(E_Object * obj EINA_UNUSED, const char *params use)

#define ACT_GO_MOUSE(name)                                            \
  {                                                                   \
     act = e_action_add(#name);                                       \
     if (act) act->func.go_mouse = _e_actions_act_##name##_go_mouse;  \
  }
#define ACT_FN_GO_MOUSE(act, use) \
  static Eina_Bool _e_actions_act_##act##_go_mouse(E_Object * obj EINA_UNUSED, const char *params use, E_Binding_Event_Mouse_Button * ev EINA_UNUSED)

#define ACT_GO_WHEEL(name)                                            \
  {                                                                   \
     act = e_action_add(#name);                                       \
     if (act) act->func.go_wheel = _e_actions_act_##name##_go_wheel;  \
  }
#define ACT_FN_GO_WHEEL(act, use) \
  static Eina_Bool _e_actions_act_##act##_go_wheel(E_Object * obj EINA_UNUSED, const char *params use, E_Binding_Event_Wheel * ev EINA_UNUSED)

#define ACT_GO_EDGE(name)                                           \
  {                                                                 \
     act = e_action_add(#name);                                     \
     if (act) act->func.go_edge = _e_actions_act_##name##_go_edge;  \
  }
#define ACT_FN_GO_EDGE(act, use) \
  static void _e_actions_act_##act##_go_edge(E_Object * obj EINA_UNUSED, const char *params use, E_Event_Zone_Edge * ev EINA_UNUSED)

#define ACT_GO_SIGNAL(name)                                             \
  {                                                                     \
     act = e_action_add(#name);                                         \
     if (act) act->func.go_signal = _e_actions_act_##name##_go_signal;  \
  }
#define ACT_FN_GO_SIGNAL(act, use) \
  static void _e_actions_act_##act##_go_signal(E_Object * obj EINA_UNUSED, const char *params use, const char *sig, const char *src)

#define ACT_GO_KEY(name)                                          \
  {                                                               \
     act = e_action_add(#name);                                   \
     if (act) act->func.go_key = _e_actions_act_##name##_go_key;  \
  }
#define ACT_FN_GO_KEY(act, use1, use2) \
  static void _e_actions_act_##act##_go_key(E_Object * obj EINA_UNUSED, const char *params use1, Ecore_Event_Key * ev use2)

#define ACT_END(name)                                       \
  {                                                         \
     act = e_action_add(#name);                             \
     if (act) act->func.end = _e_actions_act_##name##_end;  \
  }
#define ACT_FN_END(act, use) \
  static void _e_actions_act_##act##_end(E_Object * obj EINA_UNUSED, const char *params use)

#define ACT_END_MOUSE(name)                                             \
  {                                                                     \
     act = e_action_add(#name);                                         \
     if (act) act->func.end_mouse = _e_actions_act_##name##_end_mouse;  \
  }
#define ACT_FN_END_MOUSE(act, use) \
  static Eina_Bool _e_actions_act_##act##_end_mouse(E_Object * obj EINA_UNUSED, const char *params use, E_Binding_Event_Mouse_Button * ev EINA_UNUSED)

#define ACT_END_KEY(name)                                           \
  {                                                                 \
     act = e_action_add(#name);                                     \
     if (act) act->func.end_key = _e_actions_act_##name##_end_key;  \
  }
#define ACT_FN_END_KEY(act, use) \
  static void _e_actions_act_##act##_end_key(E_Object * obj EINA_UNUSED, const char *params use, Ecore_Event_Key * ev EINA_UNUSED)

#define ACT_GO_ACPI(name)                                           \
  {                                                                 \
     act = e_action_add(#name);                                     \
     if (act) act->func.go_acpi = _e_actions_act_##name##_go_acpi;  \
  }
#define ACT_FN_GO_ACPI(act, use) \
  static void _e_actions_act_##act##_go_acpi(E_Object * obj EINA_UNUSED, const char *params use, E_Event_Acpi * ev EINA_UNUSED)

/* local functions forward declarations (window_jump_to needs the definition of exec) */
ACT_FN_GO(exec, );

/* local subsystem functions */
static void       _e_action_free(E_Action *act);
static E_Maximize _e_actions_maximize_parse(const char *maximize);
static int        _action_groups_sort_cb(const void *d1, const void *d2);

/* to save writing this in N places - the sections are defined here */
/***************************************************************************/
ACT_FN_GO(window_move, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE) return;
   if (!((E_Client *)(void *)obj)->lock_user_location)
     e_client_act_move_begin((E_Client *)(void *)obj, NULL);
}

ACT_FN_GO_MOUSE(window_move, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return EINA_FALSE;
   if (obj->type != E_CLIENT_TYPE) return EINA_FALSE;
   e_client_act_move_begin((E_Client *)(void *)obj, ev);
   return EINA_TRUE;
}

ACT_FN_GO_SIGNAL(window_move, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE) return;
   if (!((E_Client *)(void *)obj)->lock_user_location)
     {
        if ((params) && (!strcmp(params, "end")))
          e_client_signal_move_end((E_Client *)(void *)obj, sig, src);
        else
          {
             if (!((E_Client *)(void *)obj)->moving)
               e_client_signal_move_begin((E_Client *)(void *)obj, sig, src);
          }
     }
}

ACT_FN_END(window_move, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE) return;
   e_client_act_move_end((E_Client *)(void *)obj, NULL);
}

ACT_FN_END_MOUSE(window_move, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return EINA_FALSE;
   if (obj->type != E_CLIENT_TYPE) return EINA_FALSE;
   e_client_act_move_end((E_Client *)(void *)obj, ev);
   return EINA_TRUE;
}

ACT_FN_GO_KEY(window_move, EINA_UNUSED, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_location)
     e_client_act_move_keyboard((E_Client *)(void *)obj);
}

/***************************************************************************/
ACT_FN_GO(window_resize, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_size)
     e_client_act_resize_begin((E_Client *)(void *)obj, NULL);
}

ACT_FN_GO_MOUSE(window_resize, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return EINA_FALSE;
   if (obj->type != E_CLIENT_TYPE) return EINA_FALSE;
   if (!((E_Client *)(void *)obj)->lock_user_size)
     e_client_act_resize_begin((E_Client *)(void *)obj, ev);
   return EINA_TRUE;
}

ACT_FN_GO_SIGNAL(window_resize, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE) return;
   if (!((E_Client *)(void *)obj)->lock_user_size)
     {
        if ((params) && (!strcmp(params, "end")))
          e_client_signal_resize_end((E_Client *)(void *)obj, params, sig, src);
        else
          {
             if (!params) params = "";
             if (e_client_util_resizing_get((E_Client *)(void *)obj))
               e_client_signal_resize_end((E_Client *)(void *)obj, params, sig, src);
             else
               e_client_signal_resize_begin((E_Client *)(void *)obj, params, sig, src);
          }
     }
}

ACT_FN_END(window_resize, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE) return;
   e_client_act_resize_end((E_Client *)(void *)obj, NULL);
}

ACT_FN_END_MOUSE(window_resize, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return EINA_FALSE;
   if (obj->type != E_CLIENT_TYPE) return EINA_FALSE;
   e_client_act_resize_end((E_Client *)(void *)obj, ev);
   return EINA_TRUE;
}

ACT_FN_GO_KEY(window_resize, EINA_UNUSED, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE)
     {
        obj = E_OBJECT(e_client_focused_get());
        if (!obj) return;
     }
   if (!((E_Client *)(void *)obj)->lock_user_size)
     e_client_act_resize_keyboard((E_Client *)(void *)obj);
}

/***************************************************************************/
ACT_FN_GO(window_menu, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   e_client_act_menu_begin((E_Client *)(void *)obj, NULL, 0);
}

ACT_FN_GO_MOUSE(window_menu, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return EINA_FALSE;
   if (obj->type != E_CLIENT_TYPE)
     {
        obj = E_OBJECT(e_client_focused_get());
        if (!obj) return EINA_FALSE;
     }
   e_client_act_menu_begin((E_Client *)(void *)obj, ev, 0);
   return EINA_TRUE;
}

ACT_FN_GO_KEY(window_menu, EINA_UNUSED, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE)
     {
        obj = E_OBJECT(e_client_focused_get());
        if (!obj) return;
     }
   e_client_act_menu_begin((E_Client *)(void *)obj, NULL, 1);
}

/***************************************************************************/
ACT_FN_GO(window_raise, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_stacking)
     evas_object_raise(((E_Client *)(void *)obj)->frame);
}

/***************************************************************************/
ACT_FN_GO(window_lower, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_stacking)
     evas_object_lower(((E_Client *)(void *)obj)->frame);
}

/***************************************************************************/
ACT_FN_GO(window_close, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_close)
     e_client_act_close_begin((E_Client *)(void *)obj);
}

/***************************************************************************/
static E_Dialog *kill_dialog = NULL;

static void
_e_actions_cb_kill_dialog_ok(void *data, E_Dialog *dia)
{
   E_Object *obj;

   obj = data;
   if (dia)
     {
        e_object_del(E_OBJECT(kill_dialog));
        kill_dialog = NULL;
     }
   if ((!((E_Client *)(void *)obj)->lock_close) && (!((E_Client *)(void *)obj)->internal))
     e_client_act_kill_begin((E_Client *)(void *)obj);
}

static void
_e_actions_cb_kill_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(kill_dialog));
   kill_dialog = NULL;
}

static void
_e_actions_cb_kill_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_kill_dialog_cancel(NULL, data);
}

ACT_FN_GO(window_kill, EINA_UNUSED)
{
   E_Client *ec;
   char dialog_text[1024];

   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   ec = (E_Client *)(void *)obj;
   if ((ec->lock_close) || (ec->internal)) return;

   if (kill_dialog) e_object_del(E_OBJECT(kill_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_kill_dialog_ok(obj, NULL);
        return;
     }

   snprintf(dialog_text, sizeof(dialog_text),
            _("You are about to kill %s.<ps/><ps/>"
              "Please keep in mind that all data from this window<ps/>"
              "which has not yet been saved will be lost!<ps/><ps/>"
              "Are you sure you want to kill this window?"),
            ec->icccm.name);

   kill_dialog = e_dialog_new(NULL,
                              "E", "_kill_dialog");
   if (!kill_dialog) return;
   evas_object_event_callback_add(kill_dialog->win, EVAS_CALLBACK_DEL,
                             _e_actions_cb_kill_dialog_delete, kill_dialog);
   e_dialog_title_set(kill_dialog,
                      _("Are you sure you want to kill this window?"));
   e_dialog_text_set(kill_dialog, _(dialog_text));
   e_dialog_icon_set(kill_dialog, "application-exit", 64);
   e_dialog_button_add(kill_dialog, _("Kill"), NULL,
                       _e_actions_cb_kill_dialog_ok, obj);
   e_dialog_button_add(kill_dialog, _("No"), NULL,
                       _e_actions_cb_kill_dialog_cancel, NULL);
   e_dialog_button_focus_num(kill_dialog, 1);
   elm_win_center(kill_dialog->win, 1, 1);
   e_dialog_show(kill_dialog);
}

/***************************************************************************/
ACT_FN_GO(window_sticky_toggle, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_sticky)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (ec->sticky) e_client_unstick(ec);
        else e_client_stick(ec);
     }
}

/***************************************************************************/
ACT_FN_GO(window_sticky, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_sticky)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (params)
          {
             if (atoi(params) == 1)
               e_client_stick(ec);
             else if (atoi(params) == 0)
               e_client_unstick(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_iconic_toggle, EINA_UNUSED)
{
   E_Client *ec;

   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   ec = (E_Client *)(void *)obj;

   if ((!ec->lock_user_iconify) && (!ec->fullscreen) &&
       ((ec->netwm.type == E_WINDOW_TYPE_NORMAL) ||
        (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN)))
     {
        if (ec->iconic) e_client_uniconify(ec);
        else e_client_iconify(ec);
     }
}

/***************************************************************************/
ACT_FN_GO(window_iconic, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_iconify)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (params)
          {
             if (atoi(params) == 1)
               e_client_iconify(ec);
             else if (atoi(params) == 0)
               e_client_uniconify(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_fullscreen_toggle, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_fullscreen)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (ec->fullscreen)
          e_client_unfullscreen(ec);
        else if (!params || *params == '\0')
          e_client_fullscreen(ec, e_config->fullscreen_policy);
        else if (!strcmp(params, "resize"))
          e_client_fullscreen(ec, E_FULLSCREEN_RESIZE);
        else if (!strcmp(params, "zoom"))
          e_client_fullscreen(ec, E_FULLSCREEN_ZOOM);
     }
}

/***************************************************************************/
ACT_FN_GO(window_fullscreen, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_fullscreen)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (params)
          {
             int v;
             char buf[32];

             buf[0] = 0;
             if (sscanf(params, "%i %20s", &v, buf) == 2)
               {
                  if (v == 1)
                    {
                       if (*buf == '\0')
                         e_client_fullscreen(ec, e_config->fullscreen_policy);
                       else if (!strcmp(buf, "resize"))
                         e_client_fullscreen(ec, E_FULLSCREEN_RESIZE);
                       else if (!strcmp(buf, "zoom"))
                         e_client_fullscreen(ec, E_FULLSCREEN_ZOOM);
                    }
                  else if (v == 0)
                    e_client_unfullscreen(ec);
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_maximized_toggle, )
{
   E_Client *ec;
   Eina_Bool resize = EINA_FALSE;

   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   ec = (E_Client *)(void *)obj;

   /* internal dialog which is resizable */
   if (ec->internal && (ec->netwm.type == E_WINDOW_TYPE_DIALOG))
     resize = (ec->icccm.max_w != ec->icccm.min_w);
   if ((!ec->lock_user_maximize) && (!ec->fullscreen) &&
       (resize || (ec->netwm.type == E_WINDOW_TYPE_NORMAL) ||
        (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN)))
     {
        if ((ec->maximized & E_MAXIMIZE_TYPE) != E_MAXIMIZE_NONE)
          {
             if (!params)
               e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
             else
               {
                  E_Maximize max;

                  max = _e_actions_maximize_parse(params);
                  max &= E_MAXIMIZE_DIRECTION;
                  if (max == E_MAXIMIZE_VERTICAL)
                    {
                       if (ec->maximized & E_MAXIMIZE_VERTICAL)
                         e_client_unmaximize(ec, E_MAXIMIZE_VERTICAL);
                       else
                         goto maximize;
                    }
                  else if (max == E_MAXIMIZE_HORIZONTAL)
                    {
                       if (ec->maximized & E_MAXIMIZE_HORIZONTAL)
                         e_client_unmaximize(ec, E_MAXIMIZE_HORIZONTAL);
                       else
                         goto maximize;
                    }
                  else if (max == E_MAXIMIZE_LEFT)
                    {
                       if (ec->maximized & E_MAXIMIZE_LEFT)
                         e_client_unmaximize(ec, E_MAXIMIZE_LEFT);
                       else
                         goto maximize;
                    }
                  else if (max == E_MAXIMIZE_RIGHT)
                    {
                       if (ec->maximized & E_MAXIMIZE_RIGHT)
                         e_client_unmaximize(ec, E_MAXIMIZE_RIGHT);
                       else
                         goto maximize;
                    }
                  else
                    e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
               }
          }
        else
          {
maximize:
             e_client_maximize(ec, _e_actions_maximize_parse(params));
          }
     }
}
/***************************************************************************/
ACT_FN_GO(window_maximized, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_maximize)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (params)
          {
             E_Maximize max;
             int v, ret;
             char s1[32], s2[32];

             max = (e_config->maximize_policy & E_MAXIMIZE_DIRECTION);
             ret = sscanf(params, "%i %20s %20s", &v, s1, s2);
             if (ret == 3)
               {
                  if (!strcmp(s2, "horizontal"))
                    max = E_MAXIMIZE_HORIZONTAL;
                  else if (!strcmp(s2, "vertical"))
                    max = E_MAXIMIZE_VERTICAL;
                  else if (!strcmp(s2, "left"))
                    max = E_MAXIMIZE_LEFT;
                  else if (!strcmp(s2, "right"))
                    max = E_MAXIMIZE_RIGHT;
                  else
                    max = E_MAXIMIZE_BOTH;
               }
             if (ret > 1)
               {
                  if (v == 1)
                    {
                       if (!strcmp(s1, "fullscreen"))
                         e_client_maximize(ec, E_MAXIMIZE_FULLSCREEN | max);
                       else if (!strcmp(s1, "smart"))
                         e_client_maximize(ec, E_MAXIMIZE_SMART | max);
                       else if (!strcmp(s1, "expand"))
                         e_client_maximize(ec, E_MAXIMIZE_EXPAND | max);
                       else if (!strcmp(s1, "fill"))
                         e_client_maximize(ec, E_MAXIMIZE_FILL | max);
                       else
                         e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | max);
                    }
                  else if (v == 0)
                    e_client_unmaximize(ec, max);
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_shaded_toggle, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_shade)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (ec->shaded)
          {
             if (!params)
               e_client_unshade(ec, E_DIRECTION_UP);
             else
               {
                  if (!strcmp(params, "up"))
                    e_client_unshade(ec, E_DIRECTION_UP);
                  else if (!strcmp(params, "down"))
                    e_client_unshade(ec, E_DIRECTION_DOWN);
                  else if (!strcmp(params, "left"))
                    e_client_unshade(ec, E_DIRECTION_LEFT);
                  else if (!strcmp(params, "right"))
                    e_client_unshade(ec, E_DIRECTION_RIGHT);
                  else
                    e_client_unshade(ec, E_DIRECTION_UP);
               }
          }
        else
          {
             if (!params)
               e_client_shade(ec, E_DIRECTION_UP);
             else
               {
                  if (!strcmp(params, "up"))
                    e_client_shade(ec, E_DIRECTION_UP);
                  else if (!strcmp(params, "down"))
                    e_client_shade(ec, E_DIRECTION_DOWN);
                  else if (!strcmp(params, "left"))
                    e_client_shade(ec, E_DIRECTION_LEFT);
                  else if (!strcmp(params, "right"))
                    e_client_shade(ec, E_DIRECTION_RIGHT);
                  else
                    e_client_shade(ec, E_DIRECTION_UP);
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_shaded, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_user_shade)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (!e_comp_object_frame_allowed(ec->frame)) return;
        if (params)
          {
             int v;
             char buf[32];

             if (sscanf(params, "%i %20s", &v, buf) == 2)
               {
                  if (v == 1)
                    {
                       if (!strcmp(buf, "up"))
                         e_client_shade(ec, E_DIRECTION_UP);
                       else if (!strcmp(buf, "down"))
                         e_client_shade(ec, E_DIRECTION_DOWN);
                       else if (!strcmp(buf, "left"))
                         e_client_shade(ec, E_DIRECTION_LEFT);
                       else if (!strcmp(buf, "right"))
                         e_client_shade(ec, E_DIRECTION_RIGHT);
                    }
                  else if (v == 0)
                    {
                       if (!strcmp(buf, "up"))
                         e_client_unshade(ec, E_DIRECTION_UP);
                       else if (!strcmp(buf, "down"))
                         e_client_unshade(ec, E_DIRECTION_DOWN);
                       else if (!strcmp(buf, "left"))
                         e_client_unshade(ec, E_DIRECTION_LEFT);
                       else if (!strcmp(buf, "right"))
                         e_client_unshade(ec, E_DIRECTION_RIGHT);
                    }
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_borderless_toggle, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_border)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (!e_comp_object_frame_allowed(ec->frame)) return;
        ec->borderless = !ec->borderless;

        ec->border.changed = 1;
        EC_CHANGED(ec);
     }
}

/***************************************************************************/
ACT_FN_GO(window_border_set, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_border)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (!e_comp_object_frame_allowed(ec->frame)) return;
        if (ec && params)
          {
             eina_stringshare_replace(&ec->bordername, params);
             ec->border.changed = 1;
             EC_CHANGED(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_border_cycle, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_border)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if (!e_comp_object_frame_allowed(ec->frame)) return;
        if (ec && params)
          {
             const char *space;

             while (*params == ' ')
               params++;

             if (ec->bordername)
               {
                  const char *bdname = params;

                  while ((space = strchr(bdname, ' ')))
                    {
                       if (strncmp(ec->bordername, bdname, space - bdname) == 0)
                         {
                            bdname = space + 1;
                            while (*bdname == ' ')
                              bdname++;
                            space = strchr(bdname, ' ');
                            if (space)
                              eina_stringshare_replace_length(
                                &ec->bordername,
                                bdname, space - bdname);
                            else
                              eina_stringshare_replace(&ec->bordername, bdname);
                            ec->border.changed = 1;
                            EC_CHANGED(ec);
                            return;
                         }
                       bdname = space + 1;
                       while (*bdname == ' ')
                         bdname++;
                    }
               }

             space = strchr(params, ' ');
             if (space)
               eina_stringshare_replace_length(&ec->bordername,
                                               params, space - params);
             else
               eina_stringshare_replace(&ec->bordername, params);
             ec->border.changed = 1;
             EC_CHANGED(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_pinned_toggle, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (!((E_Client *)(void *)obj)->lock_border)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if ((ec->netwm.state.stacking == E_STACKING_BELOW) &&
            (ec->user_skip_winlist) && (ec->borderless))
          e_client_pinned_set(ec, 0);
        else
          e_client_pinned_set(ec, 1);
     }
}

/***************************************************************************/
ACT_FN_GO(window_move_by, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (obj->type != E_CLIENT_TYPE)
     {
        obj = E_OBJECT(e_client_focused_get());
        if (!obj) return;
     }
   if (params)
     {
        int dx, dy;

        if (sscanf(params, "%i %i", &dx, &dy) == 2)
          {
             E_Client *ec;

             ec = (E_Client *)(void *)obj;
             evas_object_move(ec->frame, ec->x + dx, ec->y + dy);

             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_move_to, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   if (params)
     {
        E_Client *ec;
        int x, y, zx, zy, zw, zh;
        char cx, cy;

        ec = (E_Client *)(void *)obj;
        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);

        if (sscanf(params, "%c%i %c%i", &cx, &x, &cy, &y) == 4)
          {
             x += zx;
             y += zy;
          }
        else if (sscanf(params, "* %c%i", &cy, &y) == 2)
          {
             /* Updated y, keep x. */
             y += zy;
             x = ec->x;
             cx = 0;
          }
        else if (sscanf(params, "%c%i *", &cx, &x) == 2)
          {
             /* Updated x, keep y. */
             x += zx;
             y = ec->y;
             cy = 0;
          }
        else return;

        if (cx == '-') x = zw - ec->w - x + 2 * zx;  /* compensate x with zx */
        if (cy == '-') y = zh - ec->h - y + 2 * zy;  /* compensate y with zy */

        if ((x != ec->x) || (y != ec->y))
          {
             evas_object_move(ec->frame, x, y);

             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
     }
}


/***************************************************************************/
ACT_FN_GO(window_quick_tile_to_quadrant, )
{
   E_Client *ec;
   int x, y, zx, zy, zw, zh;

   if ((!obj) || (obj->type != E_CLIENT_TYPE))
      obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;

   ec = (E_Client *)(void *)obj;
   if((ec->maximized & E_MAXIMIZE_TYPE) != E_MAXIMIZE_NONE)
      e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
   e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);

   if (params)
     {
        if (strcmp(params, "upper_left") == 0)
          {
             x = zx;
             y = zy;
          }
        else if (strcmp(params, "upper_right") == 0)
          {
             x = zx + (zw / 2);
             y = zy;
          }
        else if (strcmp(params, "lower_left") == 0)
          {
             x = zx;
             y = zy + (zh / 2);
          }
        else if (strcmp(params, "lower_right") == 0)
          {
             x = zx + (zw / 2);
             y = zy + (zh / 2);
          }
        else
          return;

        evas_object_geometry_set(ec->frame, x, y, zw / 2, zh / 2);
     }
}


/***************************************************************************/
ACT_FN_GO(window_move_to_center, EINA_UNUSED)
{
   E_Client *ec;

   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;

   ec = (E_Client *)(void *)obj;
   e_comp_object_util_center(ec->frame);

   if (!e_client_focus_policy_click(ec))
     e_client_pointer_warp_to_center_now(ec);
}

/***************************************************************************/
ACT_FN_GO(window_resize_by, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;

   if (params)
     {
        int dw, dh;

        if (sscanf(params, "%i %i", &dw, &dh) == 2)
          {
             E_Client *ec;

             ec = (E_Client *)(void *)obj;

             dw += ec->w;
             dh += ec->h;
             e_client_resize_limit(ec, &dw, &dh);
             evas_object_resize(ec->frame, dw, dh);

             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_push, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;

   if (params)
     {
        E_Client *ec, *cur;
        E_Desk *desk_current;
        int hdir, vdir;
        int x, y, zx, zy, zw, zh;

        if (strcmp(params, "left") == 0)
          {
             hdir = -1;
             vdir = 0;
          }
        else if (strcmp(params, "right") == 0)
          {
             hdir = +1;
             vdir = 0;
          }
        else if (strcmp(params, "up") == 0)
          {
             hdir = 0;
             vdir = -1;
          }
        else if (strcmp(params, "down") == 0)
          {
             hdir = 0;
             vdir = +1;
          }
        else if (strcmp(params, "up-left") == 0)
          {
             hdir = -1;
             vdir = -1;
          }
        else if (strcmp(params, "up-right") == 0)
          {
             hdir = +1;
             vdir = -1;
          }
        else if (strcmp(params, "down-left") == 0)
          {
             hdir = -1;
             vdir = +1;
          }
        else if (strcmp(params, "down-right") == 0)
          {
             hdir = +1;
             vdir = +1;
          }
        else
          return;

        ec = (E_Client *)(void *)obj;
        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);

        if (hdir < 0) x = zx;
        else if (hdir > 0)
          x = zx + zw - ec->w;
        else x = ec->x;

        if (vdir < 0) y = zy;
        else if (vdir > 0)
          y = zy + zh - ec->h;
        else y = ec->y;

        desk_current = e_desk_current_get(ec->zone);

        E_CLIENT_FOREACH(cur)
          {
             if (((cur->desk == desk_current) || (cur->sticky)) && (ec != cur) && (!cur->iconic))
               {
                  if ((hdir < 0)
                      && (cur->x + cur->w < ec->x)
                      && (E_SPANS_COMMON(ec->y, ec->h, cur->y, cur->h)))
                    x = MAX(x, cur->x + cur->w);
                  else if ((hdir > 0)
                           && (cur->x > ec->x + ec->w)
                           && (E_SPANS_COMMON(ec->y, ec->h, cur->y, cur->h)))
                    x = MIN(x, zx + cur->x - ec->w);

                  if ((vdir < 0)
                      && (cur->y + cur->h < ec->y)
                      && (E_SPANS_COMMON(ec->x, ec->w, cur->x, cur->w)))
                    y = MAX(y, cur->y + cur->h);
                  else if ((vdir > 0)
                           && (cur->y > ec->y + ec->h)
                           && (E_SPANS_COMMON(ec->x, ec->w, cur->x, cur->w)))
                    y = MIN(y, zy + cur->y - ec->h);
               }
          }

        if ((x != ec->x) || (y != ec->y))
          {
             evas_object_move(ec->frame, x, y);
             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_grow, )
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;

   if (params)
     {
        E_Client *ec = (E_Client *)((void *)obj), *cur;
        E_Desk *desk_current;
        int hdir = 0, vdir = 0;
        int x1, y1, x2, y2, w, h;
        int zx, zy, zw, zh;

        if (!strcmp(params, "left"))
          hdir = -1;
        else if (!strcmp(params, "right"))
          hdir = 1;
        else if (!strcmp(params, "up"))
          vdir = -1;
        else if (!strcmp(params, "down"))
          vdir = 1;
        else if (!strcmp(params, "up-left"))
          {
             hdir = -1;
             vdir = -1;
          }
        else if (!strcmp(params, "up-right"))
          {
             hdir =  1;
             vdir = -1;
          }
        else if (!strcmp(params, "down-left"))
          {
             hdir = -1;
             vdir =  1;
          }
        else if (!strcmp(params, "down-right"))
          {
             hdir =  1;
             vdir =  1;
          }
        else
          return;

        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);

        x1 = ec->x;
        y1 = ec->y;
        x2 = ec->x + ec->w;
        y2 = ec->y + ec->h;

        if (hdir < 0)
          x1 = zx;
        else if (hdir > 0)
          x2 = zx + zw;

        if (vdir < 0)
          y1 = zy;
        else if (vdir > 0)
          y2 = zy + zh;

        w = x2 - x1;
        h = y2 - y1;

        e_client_resize_limit(ec, &w, &h);

        desk_current = e_desk_current_get(ec->zone);

        E_CLIENT_FOREACH(cur)
          {
             if (((cur->desk == desk_current) || (cur->sticky)) && (ec != cur) && (!cur->iconic))
               {
                  if ((hdir < 0)
                      && ((cur->x + cur->w) < ec->x)
                      && (E_SPANS_COMMON(ec->y, ec->h, cur->y, cur->h)))
                    {
                       int nw = ec->w + (ec->x - cur->x - cur->w);
                       int nh = h;
                       e_client_resize_limit(ec, &nw, &nh);
                       if ((nw > ec->w) && (nw < w))
                         {
                            x1 = cur->x + cur->w;
                            w = nw;
                            h = nh;
                         }
                    }
                  else if ((hdir > 0)
                           && (cur->x > (ec->x + ec->w))
                           && (E_SPANS_COMMON(ec->y, ec->h, cur->y, cur->h)))
                    {
                       int nw = cur->x - x1;
                       int nh = h;
                       e_client_resize_limit(ec, &nw, &nh);
                       if ((nw > ec->w) && (nw < w))
                         {
                            w = nw;
                            h = nh;
                         }
                    }

                  if ((vdir < 0)
                      && ((cur->y + cur->h) < ec->y)
                      && (E_SPANS_COMMON(ec->x, ec->w, cur->x, cur->w)))
                    {
                       int nw = w;
                       int nh = y2 - (cur->y + cur->h);
                       e_client_resize_limit(ec, &nw, &nh);
                       if ((nh > ec->h) && (nh < h))
                         {
                            y1 = cur->y + cur->h;
                            w = nw;
                            h = nh;
                         }
                    }
                  else if ((vdir > 0)
                           && (cur->y > (ec->y + ec->h))
                           && (E_SPANS_COMMON(ec->x, ec->w, cur->x, cur->w)))
                    {
                       int nw = w;
                       int nh = cur->y - y1;
                       e_client_resize_limit(ec, &nw, &nh);
                       if ((nh > ec->h) && (nh < h))
                         {
                            w = nw;
                            h = nh;
                         }
                    }
               }
          }

        if ((x1 != ec->x) || (y1 != ec->y) || (w != ec->w) || (h != ec->h))
          {
             evas_object_move(ec->frame, x1, y1);
             evas_object_resize(ec->frame, w, h);
             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
     }
}

/*
 * These actions jump to a window with the given name. It uses the last focused
 * window it finds (going through e_client_focus_stack_get), so the name should
 * be unique, if you want to jump to a specific window.
 *
 */
static int
window_jump_to(const char *params)
{
   Eina_List *l, *ll;
   E_Zone *current_zone;
   E_Client *ec;

   if (!params) return 0;

   /* Go through the list of all windows (that's what the focus stack is used for) */
   l = e_client_focus_stack_get();
   /* If we can start at the second window, we start there in order to cycle.
    * If not, there is only one window, so let's use it */
   if (eina_list_next(l))
     l = l->next;
   EINA_LIST_FOREACH(l, ll, ec)
     {
        if (!ec->icccm.name) continue;
        if (strcasecmp(ec->icccm.name, params)) continue;
        /* Jump to the screen the window is on if it isn't on the current screen but
         * only if we don't have to warp the pointer anyway */
        current_zone = e_zone_current_get();

        /* Change the virtual desktop if the window isn't on the current virtual desktop */
        e_desk_show(ec->desk);

        /* A minimized window won't be focusable for key input, un-minimize it */
        if (!ec->lock_user_iconify)
          e_client_uniconify(ec);


        evas_object_raise(ec->frame);
        if (ec->zone != current_zone)
          {
             if (!e_client_focus_policy_click(ec))
               e_client_pointer_warp_to_center_now(ec);
          }
        evas_object_focus_set(ec->frame, 1);
        return 1;
     }
   return 0;
}

ACT_FN_GO(window_jump_to, )
{
   window_jump_to(params);
}

ACT_FN_GO(window_jump_to_or_start, )
{
   char *window_name, *start_name;
   if (!params) return;

   window_name = strdup(params);
   start_name = window_name;
   start_name = strchr(window_name, ' ');
   if (start_name)
     {
        start_name[0] = 0;
        start_name++;
     }
   else
     {
        /* The user just specified one parameter, just call window_jump_to */
        window_jump_to(window_name);
        free(window_name);
        return;
     }

   /* If we cannot jump to the specified window... */
   if (!window_jump_to(window_name))
     {
        /* ...start application by calling "exec" action */
        _e_actions_act_exec_go(obj, start_name);
     }
   free(window_name);
}

/***************************************************************************/
ACT_FN_GO(window_drag_icon, EINA_UNUSED)
{
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj) return;
   {
      E_Client *ec;

      ec = (E_Client *)(void *)obj;
      ec->drag.start = 1;
      ec->drag.x = -1;
      ec->drag.y = -1;
   }
}

/***************************************************************************/
ACT_FN_GO(window_desk_move_by, )
{
   E_Client *ec;
   int x, y;

   if (!params) return;
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj)
     obj = E_OBJECT(e_client_action_get());
   if (!obj) return;

   ec = (E_Client *)(void *)obj;
   if ((!ec->zone) || (!ec->desk)) return;
   if (sscanf(params, "%d %d", &x, &y) == 2)
     {
        E_Desk *desk;
        int dx, dy;
        int to_x = 0, to_y = 0;

        e_desk_xy_get(ec->desk, &dx, &dy);

        to_x = dx + x;
        to_y = dy + y;
        while (!(desk = e_desk_at_xy_get(ec->zone, to_x, to_y)))
          {
             /* here we are out of our desktop range */
             while (to_x >= ec->zone->desk_x_count)
               {
                  to_x -= ec->zone->desk_x_count;
                  to_y++;
               }
             while (to_x < 0)
               {
                  to_x += ec->zone->desk_x_count;
                  to_y--;
               }

             while (to_y >= ec->zone->desk_y_count)
               to_y -= ec->zone->desk_y_count;
             while (to_y < 0)
               to_y += ec->zone->desk_y_count;
          }

        if (desk)
          {
             /* send the border to the required desktop. */
             e_client_desk_set(ec, desk);
             /* switch desktop. Quite useful from the interface point of view. */
             e_zone_desk_flip_by(ec->zone, to_x - dx, to_y - dy);
             if (!ec->lock_user_stacking)
               evas_object_raise(ec->frame);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(window_zone_move_by, )
{
   E_Client *ec;
   E_Zone *zone;
   E_Maximize max;
   int move;
   char *end;
   E_Fullscreen fs;
   Eina_Bool fullscreen;

   if (!params) return;
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj)
     obj = E_OBJECT(e_client_action_get());
   if (!obj) return;

   ec = (E_Client *)(void *)obj;
   /* bad */
   if (!ec->zone) return;
   /* only one screen */
   if (eina_list_count(e_comp->zones) < 2) return;
   errno = 0;
   move = strtol(params, &end, 10);
   if ((!end) || end[0] || errno) return;
   move += ec->zone->num;
   if (move < 0)
     move = eina_list_count(e_comp->zones) - 1;
   else if ((unsigned int)move >= eina_list_count(e_comp->zones))
     move = 0;
   zone = e_comp_zone_number_get(move);
   if (!zone) return;
   if (ec->zone == zone) return;
   max = ec->maximized;
   fs = ec->fullscreen_policy;
   fullscreen = ec->fullscreen;
   if (ec->maximized) e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
   if (fullscreen) e_client_unfullscreen(ec);
   e_client_zone_set(ec, zone);
   if (max) e_client_maximize(ec, max);
   if (fullscreen) e_client_fullscreen(ec, fs);
   e_client_focus_set_with_pointer(ec);
}

/***************************************************************************/
ACT_FN_GO(window_desk_move_to, )
{
   E_Client *ec;
   int x, y;

   if (!params) return;
   if ((!obj) || (obj->type != E_CLIENT_TYPE))
     obj = E_OBJECT(e_client_focused_get());
   if (!obj)
     obj = E_OBJECT(e_client_action_get());
   if (!obj) return;

   ec = (E_Client *)(void *)obj;
   if ((!ec->zone) || (!ec->desk)) return;
   if (sscanf(params, "%d %d", &x, &y) == 2)
     {
        E_Desk *desk;
        E_Desk *old_desk = ec->desk;
        Eina_Bool was_focused = e_client_stack_focused_get(ec);

        desk = e_desk_at_xy_get(ec->zone, x, y);
        if ((desk) && (desk != old_desk))
          {
             e_client_desk_set(ec, desk);
             if (was_focused)
               e_desk_last_focused_focus(old_desk);
          }
     }
}

/***************************************************************************/
static E_Zone *
_e_actions_zone_get(E_Object *obj)
{
   if (obj)
     {
        if (obj->type == (int)E_COMP_TYPE)
          return e_zone_current_get();
        else if (obj->type == (int)E_ZONE_TYPE)
          return (void *)obj;
        else if (obj->type == (int)E_CLIENT_TYPE)
          return ((E_Client *)(void *)obj)->zone;
        else if (obj->type == (int)E_SHELF_TYPE)
          return ((E_Shelf *)(void *)obj)->zone;
        else if (e_obj_is_win(obj))
          return e_win_client_get((void*)obj)->zone;
     }
   return e_zone_current_get();
}

ACT_FN_GO(desk_flip_by, )
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             int dx = 0, dy = 0;

             if (sscanf(params, "%i %i", &dx, &dy) == 2)
               e_zone_desk_flip_by(zone, dx, dy);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_flip_to, )
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             int dx = 0, dy = 0;

             if (sscanf(params, "%i %i", &dx, &dy) == 2)
               e_zone_desk_flip_to(zone, dx, dy);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_flip_prev, EINA_UNUSED)
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (!zone) return;
   e_zone_desk_flip_to(zone, zone->desk_x_prev, zone->desk_y_prev);
}

/***************************************************************************/
#define ACT_FLIP_LEFT(zone)       ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1)) || ((zone)->desk_x_current > 0))
#define ACT_FLIP_RIGHT(zone)      ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1)) || (((zone)->desk_x_current + 1) < (zone)->desk_x_count))
#define ACT_FLIP_UP(zone)         ((e_config->desk_flip_wrap && ((zone)->desk_y_count > 1)) || ((zone)->desk_y_current > 0))
#define ACT_FLIP_DOWN(zone)       ((e_config->desk_flip_wrap && ((zone)->desk_y_count > 1)) || (((zone)->desk_y_current + 1) < (zone)->desk_y_count))
#define ACT_FLIP_UP_LEFT(zone)    ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1) && ((zone)->desk_y_count > 1)) || (((zone)->desk_x_current > 0) && ((zone)->desk_y_current > 0)))
#define ACT_FLIP_UP_RIGHT(zone)   ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1) && ((zone)->desk_y_count > 1)) || ((((zone)->desk_x_current + 1) < (zone)->desk_x_count) && ((zone)->desk_y_current > 0)))
#define ACT_FLIP_DOWN_RIGHT(zone) ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1) && ((zone)->desk_y_count > 1)) || ((((zone)->desk_x_current + 1) < (zone)->desk_x_count) && (((zone)->desk_y_current + 1) < (zone)->desk_y_count)))
#define ACT_FLIP_DOWN_LEFT(zone)  ((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1) && ((zone)->desk_y_count > 1)) || (((zone)->desk_x_current > 0) && (((zone)->desk_y_current + 1) < (zone)->desk_y_count)))

ACT_FN_GO_EDGE(desk_flip_in_direction, )
{
   E_Zone *zone;
   E_Desk *current = NULL;
   E_Event_Pointer_Warp *wev;
   int x, y, offset = 25;

   if (!ev) return;  // with flip on _e_zone_cb_edge_timer we don't have ev!!!
   if (e_client_action_get() && e_client_util_resizing_get(e_client_action_get())) return;
   zone = _e_actions_zone_get(obj);
   if (!zone) return;
   wev = E_NEW(E_Event_Pointer_Warp, 1);
   if (!wev) return;
   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   wev->prev.x = x;
   wev->prev.y = y;
   if (params)
     {
        if (sscanf(params, "%i", &offset) != 1)
          offset = 25;
     }
   switch (ev->edge)
     {
      case E_ZONE_EDGE_LEFT:
        if (ACT_FLIP_LEFT(zone))
          {
             e_zone_desk_flip_by(zone, -1, 0);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + zone->w - offset, y);
             wev->curr.y = y;
             wev->curr.x = zone->w - offset;
          }
        break;

      case E_ZONE_EDGE_RIGHT:
        if (ACT_FLIP_RIGHT(zone))
          {
             e_zone_desk_flip_by(zone, 1, 0);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + offset, y);
             wev->curr.y = y;
             wev->curr.x = offset;
          }
        break;

      case E_ZONE_EDGE_TOP:
        if (ACT_FLIP_UP(zone))
          {
             e_zone_desk_flip_by(zone, 0, -1);
             ecore_evas_pointer_warp(e_comp->ee, x, zone->y + zone->h - offset);
             wev->curr.x = x;
             wev->curr.y = zone->h - offset;
          }
        break;

      case E_ZONE_EDGE_BOTTOM:
        if (ACT_FLIP_DOWN(zone))
          {
             e_zone_desk_flip_by(zone, 0, 1);
             ecore_evas_pointer_warp(e_comp->ee, x, zone->y + offset);
             wev->curr.x = x;
             wev->curr.y = offset;
          }
        break;

      case E_ZONE_EDGE_TOP_LEFT:
        if (ACT_FLIP_UP_LEFT(zone))
          {
             e_zone_desk_flip_by(zone, -1, -1);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + zone->w - offset, zone->y + zone->h - offset);
             wev->curr.x = zone->w - offset;
             wev->curr.y = zone->h - offset;
          }
        break;

      case E_ZONE_EDGE_TOP_RIGHT:
        if (ACT_FLIP_UP_RIGHT(zone))
          {
             e_zone_desk_flip_by(zone, 1, -1);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + offset, zone->y + zone->h - offset);
             wev->curr.x = offset;
             wev->curr.y = zone->h - offset;
          }
        break;

      case E_ZONE_EDGE_BOTTOM_LEFT:
        if (ACT_FLIP_DOWN_LEFT(zone))
          {
             e_zone_desk_flip_by(zone, -1, 1);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + zone->w - offset, zone->y + offset);
             wev->curr.y = offset;
             wev->curr.x = zone->w - offset;
          }
        break;

      case E_ZONE_EDGE_BOTTOM_RIGHT:
        if (ACT_FLIP_DOWN_RIGHT(zone))
          {
             e_zone_desk_flip_by(zone, 1, 1);
             ecore_evas_pointer_warp(e_comp->ee, zone->x + offset, zone->y + offset);
             wev->curr.y = offset;
             wev->curr.x = offset;
          }
        break;

      default:
        break;
     }

   current = e_desk_current_get(zone);
   if (current)
     ecore_event_add(E_EVENT_POINTER_WARP, wev, NULL, NULL);
   else
     free(wev);
}

/***************************************************************************/
ACT_FN_GO(desk_linear_flip_by, )
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             int dx = 0;

             if (sscanf(params, "%i", &dx) == 1)
               e_zone_desk_linear_flip_by(zone, dx);
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_linear_flip_to, )
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             int dx = 0;

             if (sscanf(params, "%i", &dx) == 1)
               e_zone_desk_linear_flip_to(zone, dx);
          }
     }
}

#define DESK_ACTION_ALL(zone, act) \
   do { \
     E_Zone *zone; \
     const Eina_List *lz; \
     \
     EINA_LIST_FOREACH(e_comp->zones, lz, zone) { \
        { \
           zone->desk_flip_sync = 1; \
           act; \
        } \
     } \
   } while (0)

/***************************************************************************/
ACT_FN_GO(desk_linear_flip_to_screen, )
{
   if (params)
     {
        int dx = 0, ds = 0;

        if (sscanf(params, "%i %i", &dx, &ds) == 2)
          {
             int current_screen = 0;

             DESK_ACTION_ALL(zone,
                             if (current_screen == ds)
                               {
                                  e_zone_desk_linear_flip_to(zone, dx);
                                  break;
                               }
                             else current_screen++;
                            );
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_flip_by_all, )
{
   if (params)
     {
        int dx = 0, dy = 0;

        if (sscanf(params, "%i %i", &dx, &dy) == 2)
          {
             DESK_ACTION_ALL(zone, e_zone_desk_flip_by(zone, dx, dy));
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_flip_to_all, )
{
   if (params)
     {
        int dx = 0, dy = 0;

        if (sscanf(params, "%i %i", &dx, &dy) == 2)
          {
             DESK_ACTION_ALL(zone, e_zone_desk_flip_to(zone, dx, dy));
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_linear_flip_by_all, )
{
   if (params)
     {
        int dx = 0;

        if (sscanf(params, "%i", &dx) == 1)
          {
             DESK_ACTION_ALL(zone, e_zone_desk_linear_flip_by(zone, dx));
          }
     }
}

/***************************************************************************/
ACT_FN_GO(desk_linear_flip_to_all, )
{
   if (params)
     {
        int dx = 0;

        if (sscanf(params, "%i", &dx) == 1)
          {
             DESK_ACTION_ALL(zone, e_zone_desk_linear_flip_to(zone, dx));
          }
     }
}

/***************************************************************************/
ACT_FN_GO(screen_send_to, )
{
   E_Zone *zone, *zone2;
   int scr;

   zone = _e_actions_zone_get(obj);
   if (!zone) zone = e_zone_current_get();
   if (!zone) return;
   if (!params) return;

   errno = 0;
   scr = strtol(params, NULL, 10);
   if (errno) return;

   if (scr != -1)
     scr = scr % eina_list_count(e_comp->zones);
   if (scr < 0) scr += eina_list_count(e_comp->zones);
   zone2 = e_comp_zone_number_get(scr);
   if ((zone2) && (zone != zone2))
     {
        ecore_evas_pointer_warp(e_comp->ee,
                                zone2->x + (zone2->w / 2),
                                zone2->y + (zone2->h / 2));
        e_desk_last_focused_focus(e_desk_current_get(zone2));
     }
}

ACT_FN_GO(screen_send_by, )
{
   E_Zone *zone, *zone2;
   int scr = 0;

   zone = _e_actions_zone_get(obj);
   if (!zone) zone = e_zone_current_get();
   if (!zone) return;
   if (!params) return;

   errno = 0;
   scr = strtol(params, NULL, 10);
   if (errno) return;
   scr += zone->num;
   if (scr != -1)
     scr = scr % eina_list_count(e_comp->zones);
   if (scr < 0) scr += eina_list_count(e_comp->zones);
   zone2 = e_comp_zone_number_get(scr);
   if ((zone2) && (zone != zone2))
     {
        ecore_evas_pointer_warp(e_comp->ee,
                             zone2->x + (zone2->w / 2),
                             zone2->y + (zone2->h / 2));
        e_desk_last_focused_focus(e_desk_current_get(zone2));
     }
}

#define ZONE_DESK_ACTION(con_num, zone_num, zone, act)                    \
  E_Zone * zone;                                                          \
  if ((con_num < 0) || (zone_num < 0)) {                                  \
       Eina_List *l;                                           \
       if ((con_num >= 0) && (zone_num < 0)) /* con=1 zone=all */ {       \
            EINA_LIST_FOREACH(e_comp->zones, l, zone) {                     \
                 act;                                                     \
              } }                                                         \
       else if ((con_num < 0) && (zone_num >= 0)) /* c=all zone=1 */ {  \
              zone = e_comp_zone_number_get(zone_num);  \
              if (zone)                                           \
                act;                                              \
                    }                                                  \
       else if ((con_num < 0) && (zone_num < 0)) /* c=all zone=all */ { \
              EINA_LIST_FOREACH(e_comp->zones, lll, zone) {         \
                   act;                                           \
                } } }                                         \
  else {                                                                  \
       zone = e_comp_zone_number_get(zone_num);        \
       if (zone) act;                                                     \
    }

/***************************************************************************/
#if 0
ACT_FN_GO(zone_desk_flip_by, )
{
   if (params)
     {
        int con_num = 0, zone_num = 0;
        int dx = 0, dy = 0;

        if (sscanf(params, "%i %i %i %i", &con_num, &zone_num, &dx, &dy) == 4)
          ZONE_DESK_ACTION(con_num, zone_num, zone,
                           e_zone_desk_flip_by(zone, dx, dy));
     }
}
#endif

/***************************************************************************/
#if 0
ACT_FN_GO(zone_desk_flip_to, )
{
   if (params)
     {
        int con_num = 0, zone_num = 0;
        int dx = 0, dy = 0;

        if (sscanf(params, "%i %i %i %i", &con_num, &zone_num, &dx, &dy) == 4)
          ZONE_DESK_ACTION(con_num, zone_num, zone,
                           e_zone_desk_flip_to(zone, dx, dy));
     }
}
#endif

/***************************************************************************/
#if 0
ACT_FN_GO(zone_desk_linear_flip_by, )
{
   if (params)
     {
        int con_num = 0, zone_num = 0;
        int dx = 0;

        if (sscanf(params, "%i %i %i", &con_num, &zone_num, &dx) == 3)
          ZONE_DESK_ACTION(con_num, zone_num, zone,
                           e_zone_desk_linear_flip_by(zone, dx));
     }
}
#endif

/***************************************************************************/
#if 0
ACT_FN_GO(zone_desk_linear_flip_to, )
{
   if (params)
     {
        int con_num = 0, zone_num = 0;
        int dx = 0;

        if (sscanf(params, "%i %i %i", &con_num, &zone_num, &dx) == 3)
          ZONE_DESK_ACTION(con_num, zone_num, zone,
                           e_zone_desk_linear_flip_to(zone, dx));
     }
}
#endif

/***************************************************************************/
static void
_e_actions_cb_menu_end(void *data EINA_UNUSED, E_Menu *m)
{
   e_object_del(E_OBJECT(m));
}

static E_Menu *
_e_actions_menu_find(const char *name)
{
   if (!strcmp(name, "main"))
     return e_int_menus_main_new();
   else if (!strcmp(name, "favorites"))
     return e_int_menus_favorite_apps_new();
   else if (!strcmp(name, "all"))
     return e_int_menus_all_apps_new();
   else if (!strcmp(name, "clients"))
     return e_int_menus_clients_new();
   else if (!strcmp(name, "lost_clients"))
     return e_int_menus_lost_clients_new();
   else if (!strcmp(name, "configuration"))
     return e_int_menus_config_new();
   return NULL;
}

ACT_FN_GO(menu_show, )
{
   E_Zone *zone;

   /* menu is active - abort */
   if (e_comp_util_kbd_grabbed() || e_comp_util_mouse_grabbed()) return;
   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             E_Menu *m = NULL;

             m = _e_actions_menu_find(params);
             if (m)
               {
                  int x, y;

                  /* FIXME: this is a bit of a hack... setting m->c - bad hack */
                  m->zone = zone;
                  ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
                  e_menu_post_deactivate_callback_set(m, _e_actions_cb_menu_end, NULL);
                  e_menu_activate_mouse(m, zone, x, y, 1, 1,
                                        E_MENU_POP_DIRECTION_AUTO, 0);
               }
          }
     }
}

ACT_FN_GO_MOUSE(menu_show, )
{
   E_Zone *zone;
   E_Menu *m = NULL;
   int x, y;

   /* menu is active - abort */
   if (e_comp_util_kbd_grabbed() || e_comp_util_mouse_grabbed()) return EINA_TRUE;
   zone = _e_actions_zone_get(obj);
   if (!zone) return EINA_TRUE;
   if (!params) return EINA_TRUE;
   m = _e_actions_menu_find(params);
   if (!m) return EINA_TRUE;
   /* FIXME: this is a bit of a hack... setting m->zone - bad hack */
   m->zone = zone;
   x = ev->canvas.x;
   y = ev->canvas.y;
   e_menu_post_deactivate_callback_set(m, _e_actions_cb_menu_end, NULL);
   e_menu_activate_mouse(m, zone, x, y, 1, 1,
                         E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
   return EINA_TRUE;
}

ACT_FN_GO_KEY(menu_show, , EINA_UNUSED)
{
   E_Zone *zone;

   /* menu is active - abort */
   if (e_comp_util_kbd_grabbed() || e_comp_util_mouse_grabbed()) return;
   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             E_Menu *m = NULL;

             m = _e_actions_menu_find(params);
             if (m)
               {
                  int x, y;

                  /* FIXME: this is a bit of a hack... setting m->con - bad hack */
                  m->zone = zone;
                  ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
                  e_menu_post_deactivate_callback_set(m, _e_actions_cb_menu_end, NULL);
                  e_menu_activate_key(m, zone, x, y, 1, 1,
                                      E_MENU_POP_DIRECTION_DOWN);
               }
          }
     }
}

ACT_FN_GO(menu_show_object,)
{
   E_Zone *zone;

   /* menu is active - abort */
   if (e_comp_util_kbd_grabbed() || e_comp_util_mouse_grabbed()) return;
   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             E_Menu *m = NULL;

             m = _e_actions_menu_find(params);
             if (m)
               {
                  int x, y, w, h;
                  Eina_List *l, *ll;
                  Evas_Object *o;

                  ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
                  l = evas_objects_at_xy_get(e_comp->evas, x, y, 0, 0);
                  EINA_LIST_REVERSE_FOREACH(l, ll, o)
                    {
                       if (evas_object_repeat_events_get(o) && ll->prev) continue;
                       evas_object_geometry_get(o, &x, &y, &w, &h);
                       m->zone = e_comp_object_util_zone_get(o);
                       e_menu_post_deactivate_callback_set(m, _e_actions_cb_menu_end, NULL);
                       e_menu_activate_mouse(m, zone, x, y, w, h,
                                             E_MENU_POP_DIRECTION_AUTO, 0);
                       break;
                    }
                  eina_list_free(l);
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(exec, )
{
   E_Zone *zone;
   static double lock;
   double test;

   /* prevent exec actions from occurring too frequently */
   test = ecore_loop_time_get();
   if (test - lock < 0.05)
     {
        lock = test;
        return;
     }
   lock = ecore_loop_time_get();
   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          e_exec(zone, NULL, params, NULL, "action/exec");
     }
}

/***************************************************************************/
ACT_FN_GO(app, )
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)
     {
        if (params)
          {
             Efreet_Desktop *desktop = NULL;
             char *p, *p2;
             size_t plen;

             plen = strlen(params);
             p2 = memcpy(alloca(plen + 1), params, plen + 1);
             p = strchr(p2, ':');
             if (p)
               {
                  *p++ = 0;
                  if (*p == ' ')
                    {
                       E_Dialog *dia;
                       char dialog_text[1024];

                       dia = e_dialog_new(NULL, "E", "_e_action_act_app_go_syntax_error");
                       if (!dia) return;

                       snprintf(dialog_text, sizeof(dialog_text),
                                "%s<ps/><ps/>"
                                "Check syntax. You should not put a whitespace right after colon in action params.<ps/>"
                                "syntax: [file:file.desktop|name:App Name|generic:Generic Name|exe:exename]<ps/><ps/>"
                                "exe:terminology (O)<ps/>"
                                "exe: terminology (X)", params);

                       e_dialog_title_set(dia, _("Action Params Syntax Error"));
                       e_dialog_text_set(dia, _(dialog_text));
                       e_dialog_icon_set(dia, "dialog-error", 64);
                       e_dialog_button_add(dia, _("Close"), NULL, NULL, NULL);
                       e_dialog_button_focus_num(dia, 0);
                       elm_win_center(dia->win, 1, 1);
                       e_dialog_show(dia);

                       return;
                    }
                  if (!strcmp(p2, "file"))
                    desktop = efreet_util_desktop_file_id_find(p);
                  else if (!strcmp(p2, "name"))
                    desktop = efreet_util_desktop_name_find(p);
                  else if (!strcmp(p2, "generic"))
                    desktop = efreet_util_desktop_generic_name_find(p);
                  else if (!strcmp(p2, "exe"))
                    desktop = efreet_util_desktop_exec_find(p);
                  if (desktop)
                    {
                       e_exec(zone, desktop, NULL, NULL, "action/app");
                       efreet_desktop_free(desktop);
                    }
               }
          }
     }
}

/***************************************************************************/
ACT_FN_GO(app_new_instance, EINA_UNUSED)
{
   E_Client *ec;
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (!zone)
     zone = e_zone_current_get();

   if (!obj) obj = E_OBJECT(e_client_focused_get());
   if ((!obj) || (!zone)) return;
   if (obj->type != E_CLIENT_TYPE)
     {
        obj = E_OBJECT(e_client_focused_get());
        if (!obj) return;
     }
   ec = (E_Client *)(void *)obj;

   if (ec->desktop)
     e_exec(zone, ec->desktop, NULL, NULL, "action/app");
}

/***************************************************************************/
ACT_FN_GO(desk_deskshow_toggle, EINA_UNUSED)
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (!zone) zone = e_zone_current_get();
   if (zone) e_desk_deskshow(zone);
}

ACT_FN_GO(cleanup_windows, EINA_UNUSED)
{
   E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (!zone) zone = e_zone_current_get();
   if (zone) e_place_zone_region_smart_cleanup(zone);
}

/***************************************************************************/
static E_Dialog *exit_dialog = NULL;

static void
_e_actions_cb_exit_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(exit_dialog));
        exit_dialog = NULL;
     }
   e_sys_action_do(E_SYS_EXIT, NULL);
}

static void
_e_actions_cb_exit_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(exit_dialog));
   exit_dialog = NULL;
}

static void
_e_actions_cb_exit_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_exit_dialog_cancel(NULL, data);
}

ACT_FN_GO(exit, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_EXIT, NULL);
        return;
     }
   if (exit_dialog) e_object_del(E_OBJECT(exit_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_exit_dialog_ok(NULL, NULL);
        return;
     }

   exit_dialog = e_dialog_new(NULL, "E", "_exit_dialog");
   if (!exit_dialog) return;
   evas_object_event_callback_add(exit_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_exit_dialog_delete, exit_dialog);
   e_dialog_title_set(exit_dialog, _("Exit"));
   e_dialog_text_set(exit_dialog, _("Are you sure you want to exit Enlightenment?"));
   e_dialog_icon_set(exit_dialog, "application-exit", 64);
   e_dialog_button_add(exit_dialog, _("Yes"), NULL,
                       _e_actions_cb_exit_dialog_ok, NULL);
   e_dialog_button_add(exit_dialog, _("No"), NULL,
                       _e_actions_cb_exit_dialog_cancel, NULL);
   e_dialog_button_focus_num(exit_dialog, 1);
   elm_win_center(exit_dialog->win, 1, 1);
   e_dialog_show(exit_dialog);
}

/***************************************************************************/
ACT_FN_GO(restart, EINA_UNUSED)
{
   e_sys_action_do(E_SYS_RESTART, NULL);
}

/***************************************************************************/
ACT_FN_GO(exit_now, EINA_UNUSED)
{
   e_sys_action_do(E_SYS_EXIT_NOW, NULL);
}

/***************************************************************************/
ACT_FN_GO(halt_now, EINA_UNUSED)
{
   e_sys_action_do(E_SYS_HALT_NOW, NULL);
}

/***************************************************************************/
static E_Dialog *logout_dialog = NULL;

static void
_e_actions_cb_logout_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(logout_dialog));
        logout_dialog = NULL;
     }
   e_sys_action_do(E_SYS_LOGOUT, NULL);
}

static void
_e_actions_cb_logout_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(logout_dialog));
   logout_dialog = NULL;
}

static void
_e_actions_cb_logout_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_logout_dialog_cancel(NULL, data);
}

ACT_FN_GO(logout, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_LOGOUT, NULL);
        return;
     }
   if (logout_dialog) e_object_del(E_OBJECT(logout_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_logout_dialog_ok(NULL, NULL);
        return;
     }

   logout_dialog = e_dialog_new(NULL, "E", "_logout_dialog");
   if (!logout_dialog) return;
   evas_object_event_callback_add(logout_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_logout_dialog_delete, logout_dialog);
   e_dialog_title_set(logout_dialog, _("Logout"));
   e_dialog_text_set(logout_dialog, _("Are you sure you want to logout?"));
   e_dialog_icon_set(logout_dialog, "system-log-out", 64);
   e_dialog_button_add(logout_dialog, _("Yes"), NULL,
                       _e_actions_cb_logout_dialog_ok, NULL);
   e_dialog_button_add(logout_dialog, _("No"), NULL,
                       _e_actions_cb_logout_dialog_cancel, NULL);
   e_dialog_button_focus_num(logout_dialog, 1);
   elm_win_center(logout_dialog->win, 1, 1);
   e_dialog_show(logout_dialog);
}

/***************************************************************************/
static E_Dialog *halt_dialog = NULL;

static void
_e_actions_cb_halt_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(halt_dialog));
        halt_dialog = NULL;
     }
   e_sys_action_do(E_SYS_HALT, NULL);
}

static void
_e_actions_cb_halt_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(halt_dialog));
   halt_dialog = NULL;
}

static void
_e_actions_cb_halt_dialog_delete(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (!halt_dialog) return;
   e_object_del(E_OBJECT(halt_dialog));
   halt_dialog = NULL;
}

ACT_FN_GO(halt, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_HALT, NULL);
        return;
     }
   if (halt_dialog) e_object_del(E_OBJECT(halt_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_halt_dialog_ok(NULL, NULL);
        return;
     }

   halt_dialog = e_dialog_new(NULL, "E", "_halt_dialog");
   if (!halt_dialog) return;
   evas_object_event_callback_add(halt_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_halt_dialog_delete, halt_dialog);
   e_dialog_title_set(halt_dialog, _("Power off"));
   e_dialog_text_set(halt_dialog,
                     _("Are you sure you want to power off your computer?"));
   e_dialog_icon_set(halt_dialog, "system-shutdown", 64);
   e_dialog_button_add(halt_dialog, _("Yes"), NULL,
                       _e_actions_cb_halt_dialog_ok, NULL);
   e_dialog_button_add(halt_dialog, _("No"), NULL,
                       _e_actions_cb_halt_dialog_cancel, NULL);
   e_dialog_button_focus_num(halt_dialog, 1);
   elm_win_center(halt_dialog->win, 1, 1);
   e_dialog_show(halt_dialog);
}

/***************************************************************************/
static E_Dialog *reboot_dialog = NULL;

static void
_e_actions_cb_reboot_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(reboot_dialog));
        reboot_dialog = NULL;
     }
   e_sys_action_do(E_SYS_REBOOT, NULL);
}

static void
_e_actions_cb_reboot_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(reboot_dialog));
   reboot_dialog = NULL;
}

static void
_e_actions_cb_reboot_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_reboot_dialog_cancel(NULL, data);
}

ACT_FN_GO(reboot, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_REBOOT, NULL);
        return;
     }
   if (reboot_dialog) e_object_del(E_OBJECT(reboot_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_reboot_dialog_ok(NULL, NULL);
        return;
     }

   reboot_dialog = e_dialog_new(NULL, "E", "_reboot_dialog");
   if (!reboot_dialog) return;
   evas_object_event_callback_add(reboot_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_reboot_dialog_delete, reboot_dialog);
   e_dialog_title_set(reboot_dialog, _("Reboot"));
   e_dialog_text_set(reboot_dialog, _("Are you sure you want to reboot your computer?"));
   e_dialog_icon_set(reboot_dialog, "system-restart", 64);
   e_dialog_button_add(reboot_dialog, _("Yes"), NULL,
                       _e_actions_cb_reboot_dialog_ok, NULL);
   e_dialog_button_add(reboot_dialog, _("No"), NULL,
                       _e_actions_cb_reboot_dialog_cancel, NULL);
   e_dialog_button_focus_num(reboot_dialog, 1);
   elm_win_center(reboot_dialog->win, 1, 1);
   e_dialog_show(reboot_dialog);
}

/***************************************************************************/
static E_Dialog *suspend_dialog = NULL;

static void
_e_actions_cb_suspend_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(suspend_dialog));
        suspend_dialog = NULL;
     }
   e_sys_action_do(E_SYS_SUSPEND, NULL);
}

static void
_e_actions_cb_suspend_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(suspend_dialog));
   suspend_dialog = NULL;
}

static void
_e_actions_cb_suspend_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_suspend_dialog_cancel(NULL, data);
}

ACT_FN_GO(suspend_now, EINA_UNUSED)
{
   e_sys_action_do(E_SYS_SUSPEND, NULL);
}

ACT_FN_GO(suspend, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_SUSPEND, NULL);
        return;
     }
   if (suspend_dialog) e_object_del(E_OBJECT(suspend_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_suspend_dialog_ok(NULL, NULL);
        return;
     }

   suspend_dialog = e_dialog_new(NULL, "E", "_suspend_dialog");
   if (!suspend_dialog) return;
   evas_object_event_callback_add(suspend_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_suspend_dialog_delete, suspend_dialog);
   e_dialog_title_set(suspend_dialog, _("Suspend"));
   e_dialog_text_set(suspend_dialog, _("Are you sure you want to suspend your computer?"));
   e_dialog_icon_set(suspend_dialog, "system-suspend", 64);
   e_dialog_button_add(suspend_dialog, _("Yes"), NULL,
                       _e_actions_cb_suspend_dialog_ok, NULL);
   e_dialog_button_add(suspend_dialog, _("No"), NULL,
                       _e_actions_cb_suspend_dialog_cancel, NULL);
   e_dialog_button_focus_num(suspend_dialog, 1);
   elm_win_center(suspend_dialog->win, 1, 1);
   e_dialog_show(suspend_dialog);
}

static Eina_Bool
_have_lid_and_external_screens_on(void)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   int lids = 0;
   int ext_screens = 0;

   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (s->info.is_lid) lids++;
        else if ((s->config.enabled) &&
                 (s->config.geom.w > 0) &&
                 (s->config.geom.h > 0))
          ext_screens++;
     }
   if ((lids > 0) && (ext_screens > 0)) return EINA_TRUE;
   return EINA_FALSE;
}

static Eina_Bool
_should_suspend_if_plugged_in(void)
{
   if ((e_config->screensaver_suspend_on_ac) ||
       (e_powersave_mode_get() > E_POWERSAVE_MODE_LOW))
     return EINA_TRUE;
   return EINA_FALSE;
}

ACT_FN_GO(suspend_smart, EINA_UNUSED)
{
   if (!_have_lid_and_external_screens_on())
     {
        if (_should_suspend_if_plugged_in())
          e_sys_action_do(E_SYS_SUSPEND, NULL);
        else
          e_powersave_defer_suspend();
     }
}

/***************************************************************************/
static E_Dialog *hibernate_dialog = NULL;

static void
_e_actions_cb_hibernate_dialog_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia)
     {
        e_object_del(E_OBJECT(hibernate_dialog));
        hibernate_dialog = NULL;
     }
   e_sys_action_do(E_SYS_HIBERNATE, NULL);
}

static void
_e_actions_cb_hibernate_dialog_cancel(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(hibernate_dialog));
   hibernate_dialog = NULL;
}

static void
_e_actions_cb_hibernate_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_actions_cb_hibernate_dialog_cancel(NULL, data);
}

ACT_FN_GO(hibernate_now, EINA_UNUSED)
{
   e_sys_action_do(E_SYS_HIBERNATE, NULL);
}

ACT_FN_GO(hibernate, )
{
   if ((params) && (!strcmp(params, "now")))
     {
        e_sys_action_do(E_SYS_HIBERNATE, NULL);
        return;
     }
   if (hibernate_dialog) e_object_del(E_OBJECT(hibernate_dialog));

   if (e_config->cnfmdlg_disabled)
     {
        _e_actions_cb_hibernate_dialog_ok(NULL, NULL);
        return;
     }

   hibernate_dialog = e_dialog_new(NULL, "E", "_hibernate_dialog");
   if (!hibernate_dialog) return;
   evas_object_event_callback_add(hibernate_dialog->win, EVAS_CALLBACK_DEL, _e_actions_cb_hibernate_dialog_delete, hibernate_dialog);
   e_dialog_title_set(hibernate_dialog, _("Hibernate"));
   e_dialog_text_set(hibernate_dialog, _("Are you sure you want to hibernate your computer?"));
   e_dialog_icon_set(hibernate_dialog, "system-suspend-hibernate", 64);
   e_dialog_button_add(hibernate_dialog, _("Yes"), NULL,
                       _e_actions_cb_hibernate_dialog_ok, NULL);
   e_dialog_button_add(hibernate_dialog, _("No"), NULL,
                       _e_actions_cb_hibernate_dialog_cancel, NULL);
   e_dialog_button_focus_num(hibernate_dialog, 1);
   elm_win_center(hibernate_dialog->win, 1, 1);
   e_dialog_show(hibernate_dialog);
}

ACT_FN_GO(hibernate_smart, EINA_UNUSED)
{
   if (!_have_lid_and_external_screens_on())
     {
        if (_should_suspend_if_plugged_in())
          e_sys_action_do(E_SYS_HIBERNATE, NULL);
        else
          e_powersave_defer_hibernate();
     }
}

/***************************************************************************/
ACT_FN_GO(pointer_resize_push, )
{
   if (!obj) return;
   if (obj->type == E_CLIENT_TYPE)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if ((ec->lock_user_size) || (ec->shaded) || (ec->shading) ||
            (ec->fullscreen) || ((ec->maximized) && (!e_config->allow_manip)))
          return;
        e_pointer_type_push(e_comp->pointer, ec, params);
     }
}

/***************************************************************************/
ACT_FN_GO(pointer_resize_pop, )
{
   if (!obj) return;
   if (obj->type == E_CLIENT_TYPE)
     {
        E_Client *ec;

        ec = (E_Client *)(void *)obj;
        if ((ec->lock_user_size) || (ec->shaded) || (ec->shading) ||
            (ec->fullscreen) || ((ec->maximized) && (!e_config->allow_manip)))
          return;
        e_pointer_type_pop(e_comp->pointer, ec, params);
     }
}

/***************************************************************************/
ACT_FN_GO(desk_lock, EINA_UNUSED)
{
/*  E_Zone *zone;

   zone = _e_actions_zone_get(obj);
   if (zone)*/
   e_desklock_show_manual(EINA_FALSE);
}

/***************************************************************************/
ACT_FN_GO(shelf_show, )
{
   Eina_List *l;
   E_Shelf *es;

   if (params)
     {
        for (; *params != '\0'; params++)
          if (!isspace(*params))
            break;
        if (*params == '\0')
          params = NULL;
     }

   EINA_LIST_FOREACH(e_shelf_list(), l, es)
     {
        if ((!params) || (params && (eina_fnmatch(params, es->name, 0))))
          {
             e_shelf_toggle(es, 1);
             e_shelf_toggle(es, 0);
          }
     }
}
/***************************************************************************/
#define ACT_SHELF_SHOW(params, es)                                  \
  if ((!params) || (params && (eina_fnmatch(params, es->name, 0)))) \
    {                                                               \
       e_shelf_toggle(es, 1);                                       \
       e_shelf_toggle(es, 0);                                       \
    }

ACT_FN_GO_EDGE(shelf_show, )
{
   Eina_List *l;
   E_Shelf *es;

   if (params)
     {
        for (; *params != '\0'; params++)
          {
             if (!isspace(*params))
               break;
          }
        if (*params == '\0')
          params = NULL;
     }

   EINA_LIST_FOREACH(e_shelf_list(), l, es)
     {
        switch (ev->edge)
          {
           case E_ZONE_EDGE_LEFT:
             if ((es->gadcon->orient == E_GADCON_ORIENT_LEFT ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_LT ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_LB) &&
                 (ev->y >= es->y) && (ev->y <= (es->y + es->h)))
               ACT_SHELF_SHOW(params, es);
             break;

           case E_ZONE_EDGE_RIGHT:
             if ((es->gadcon->orient == E_GADCON_ORIENT_RIGHT ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_RT ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_RB) &&
                 (ev->y >= es->y) && (ev->y <= (es->y + es->h)))
               ACT_SHELF_SHOW(params, es);
             break;

           case E_ZONE_EDGE_TOP:
             if ((es->gadcon->orient == E_GADCON_ORIENT_TOP ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_TL ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_TR) &&
                 (ev->x >= es->x) && (ev->x <= (es->x + es->w)))
               ACT_SHELF_SHOW(params, es);
             break;

           case E_ZONE_EDGE_BOTTOM:
             if ((es->gadcon->orient == E_GADCON_ORIENT_BOTTOM ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_BL ||
                  es->gadcon->orient == E_GADCON_ORIENT_CORNER_BR) &&
                 (ev->x >= es->x) && (ev->x <= (es->x + es->w)))
               ACT_SHELF_SHOW(params, es);
             break;

           default:
             break;
          }
     }
}
#undef ACT_SHELF_SHOW

/***************************************************************************/
typedef struct _Delayed_Action Delayed_Action;

struct _Delayed_Action
{
   int          mouse, button;
   const char  *key;
   E_Object    *obj;
   Ecore_Timer *timer;
   struct
   {
      const char *action, *params;
   } def, delayed;
};

static Eina_List *_delayed_actions = NULL;

static void
_delayed_action_free(Delayed_Action *da)
{
   if (da->obj) e_object_unref(da->obj);
   if (da->key) eina_stringshare_del(da->key);
   if (da->timer) ecore_timer_del(da->timer);
   if (da->def.action) eina_stringshare_del(da->def.action);
   if (da->def.params) eina_stringshare_del(da->def.params);
   if (da->delayed.action) eina_stringshare_del(da->delayed.action);
   if (da->delayed.params) eina_stringshare_del(da->delayed.params);
   free(da);
}

static Eina_Bool
_delayed_action_cb_timer(void *data)
{
   Delayed_Action *da;
   E_Action *act;

   da = data;
   da->timer = NULL;
   if (da->delayed.action)
     {
        act = e_action_find(da->delayed.action);
        if (act)
          {
             if (act->func.go) act->func.go(da->obj, da->delayed.params);
          }
     }
   _delayed_actions = eina_list_remove(_delayed_actions, da);
   _delayed_action_free(da);
   return ECORE_CALLBACK_CANCEL;
}

static void
_delayed_action_do(Delayed_Action *da)
{
   E_Action *act;

   if (da->def.action)
     {
        act = e_action_find(da->def.action);
        if (act)
          {
             if (act->func.go) act->func.go(da->obj, da->def.params);
          }
     }
}

static void
_delayed_action_list_parse_action(const char *str, double *delay, const char **action, const char **params)
{
   char fbuf[16];
   char buf[1024];
   const char *p;

   buf[0] = 0;
   if (sscanf(str, "%10s %1000s", fbuf, buf) != 2) return;
   *action = eina_stringshare_add(buf);
   *delay = atof(fbuf);
   p = strchr(str, ' ');
   if (p)
     {
        p++;
        p = strchr(p, ' ');
        if (p)
          {
             p++;
             *params = eina_stringshare_add(p);
          }
     }
}

static void
_delayed_action_list_parse(Delayed_Action *da, const char *params)
{
   double delay = 2.0;
   const char *p, *a1start = NULL, *a1stop = NULL;
   const char *a2start = NULL, *a2stop = NULL;

   // FORMAT: "[0.0 default_action param1 param2] [x.x action2 param1 param2]"
   p = params;
   while (*p)
     {
        if ((*p == '[') && ((p == params) || ((p > params) && (p[-1] != '\\')))) {a1start = p + 1; break; }
        p++;
     }
   while (*p)
     {
        if ((*p == ']') && ((p == params) || ((p > params) && (p[-1] != '\\')))) {a1stop = p; break; }
        p++;
     }
   while (*p)
     {
        if ((*p == '[') && ((p == params) || ((p > params) && (p[-1] != '\\')))) {a2start = p + 1; break; }
        p++;
     }
   while (*p)
     {
        if ((*p == ']') && ((p == params) || ((p > params) && (p[-1] != '\\')))) {a2stop = p; break; }
        p++;
     }
   if ((a1start) && (a2start) && (a1stop) && (a2stop))
     {
        char *a1, *a2;

        a1 = alloca(a1stop - a1start + 1);
        eina_strlcpy(a1, a1start, a1stop - a1start + 1);
        _delayed_action_list_parse_action(a1, &delay, &da->def.action, &da->def.params);

        a2 = alloca(a1stop - a1start + 1);
        eina_strlcpy(a2, a2start, a2stop - a2start + 1);
        _delayed_action_list_parse_action(a2, &delay, &da->delayed.action, &da->delayed.params);
     }
   da->timer = ecore_timer_loop_add(delay, _delayed_action_cb_timer, da);
}

static void
_delayed_action_key_add(E_Object *obj, const char *params, Ecore_Event_Key *ev)
{
   Delayed_Action *da;

   da = E_NEW(Delayed_Action, 1);
   if (!da) return;
   if (obj)
     {
        da->obj = obj;
        e_object_ref(da->obj);
     }
   da->mouse = 0;
   da->key = eina_stringshare_add(ev->key);
   if (params) _delayed_action_list_parse(da, params);
   _delayed_actions = eina_list_append(_delayed_actions, da);
}

static void
_delayed_action_key_del(E_Object *obj, const char *params EINA_UNUSED, Ecore_Event_Key *ev)
{
   Eina_List *l;
   Delayed_Action *da;

   EINA_LIST_FOREACH(_delayed_actions, l, da)
     {
        if ((da->obj == obj) && (!da->mouse) &&
            (!strcmp(da->key, ev->key)))
          {
             _delayed_action_do(da);
             _delayed_action_free(da);
             _delayed_actions = eina_list_remove_list(_delayed_actions, l);
             return;
          }
     }
}

static void
_delayed_action_mouse_add(E_Object *obj, const char *params, E_Binding_Event_Mouse_Button *ev)
{
   Delayed_Action *da;

   da = E_NEW(Delayed_Action, 1);
   if (!da) return;
   if (obj)
     {
        da->obj = obj;
        e_object_ref(da->obj);
     }
   da->mouse = 1;
   da->button = ev->button;
   if (params) _delayed_action_list_parse(da, params);
   _delayed_actions = eina_list_append(_delayed_actions, da);
}

static void
_delayed_action_mouse_del(E_Object *obj, const char *params EINA_UNUSED, E_Binding_Event_Mouse_Button *ev)
{
   Eina_List *l;
   Delayed_Action *da;

   EINA_LIST_FOREACH(_delayed_actions, l, da)
     {
        if ((da->obj == obj) && (da->mouse) &&
            ((int)ev->button == da->button))
          {
             _delayed_action_do(da);
             _delayed_action_free(da);
             _delayed_actions = eina_list_remove_list(_delayed_actions, l);
             return;
          }
     }
}

// obj , params  , ev
ACT_FN_GO_KEY(delayed_action, , )
{
   _delayed_action_key_add(obj, params, ev);
}

ACT_FN_GO_MOUSE(delayed_action, )
{
   _delayed_action_mouse_add(obj, params, ev);
   return EINA_TRUE;
}

ACT_FN_END_KEY(delayed_action, )
{
   _delayed_action_key_del(obj, params, ev);
}

ACT_FN_END_MOUSE(delayed_action, )
{
   _delayed_action_mouse_del(obj, params, ev);
   return EINA_TRUE;
}

ACT_FN_GO(dim_screen, EINA_UNUSED)
{
   E_Zone *zone = _e_actions_zone_get(obj);
   e_backlight_mode_set(zone, E_BACKLIGHT_MODE_DIM);
}

ACT_FN_GO(undim_screen, EINA_UNUSED)
{
   E_Zone *zone = _e_actions_zone_get(obj);
   e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
}

ACT_FN_GO(backlight_set, )
{
   E_Zone *zone = _e_actions_zone_get(obj);
   int v;
   if (params)
     v = atoi(params);
   else
     {
        v = e_backlight_level_get(zone) * 100.0;
        if (v == 0) v = 100;
        else v = 0;
     }
   e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
   e_backlight_level_set(zone, ((double)v / 100.0), -1.0);
   e_config->backlight.normal = e_backlight_level_get(zone);
   e_config_save_queue();
}

ACT_FN_GO(backlight_adjust, )
{
   E_Zone *zone = _e_actions_zone_get(obj);
   int v;
   if (!params) return;
   v = atoi(params);
   e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
   e_backlight_level_set(zone, e_backlight_level_get(zone) + ((double)v / 100.0), -1.0);
   e_config->backlight.normal = e_backlight_level_get(zone);
   e_config_save_queue();
}

ACT_FN_GO(blanking, EINA_UNUSED)
{
   if (params && (!strcmp(params, "on")))
     e_config->screensaver_enable = 1;
   else if (params && (!strcmp(params, "off")))
     e_config->screensaver_enable = 0;
   else if (params && (!strcmp(params, "toggle")))
     {
        if (e_config->screensaver_enable) e_config->screensaver_enable = 0;
        else e_config->screensaver_enable = 1;
     }
   e_screensaver_update();
   e_dpms_update();
   e_config_save_queue();
}

ACT_FN_GO(kbd_layout, )
{
   unsigned int x;

   if (!params) return;
   errno = 0;
   x = strtoul(params, NULL, 10);
   if (errno || (x > eina_list_count(e_config->xkb.used_layouts) - 1)) return;
   e_xkb_layout_set(eina_list_nth(e_config->xkb.used_layouts, x));
}

ACT_FN_GO(kbd_layout_next, EINA_UNUSED)
{
   e_xkb_layout_next();
}

ACT_FN_GO(kbd_layout_prev, EINA_UNUSED)
{
   e_xkb_layout_prev();
}

#ifdef HAVE_WAYLAND
ACT_FN_GO_MOUSE(mouse_to_key, )
{
   const char *p, *nextp, *key = NULL;
   const char *mods[] =
   {
      "shift",
      "ctrl",
      "alt",
      "win",
      "altgr",
      NULL
   };
   int modifiers = 0, mod = 0;

   if ((!params) || (!params[0]) || (params[0] == '+')) return EINA_TRUE;
   for (p = params; p; p = nextp)
     {
        const char **m;

        nextp = strchr(p + 1, '+');
        if (!nextp) break;
        for (m = mods; *m; m++)
          {
             if (strncmp(p, *m, nextp - p)) continue;
             modifiers |= 1 << (m - mods);
             break;
          }
        key = nextp;
     }
   if (key)
     key++;
   else
     key = params;
   if (!key[0]) return EINA_TRUE;

   mod |= (ECORE_EVENT_MODIFIER_SHIFT * !!(modifiers & E_BINDING_MODIFIER_SHIFT));
   mod |= (ECORE_EVENT_MODIFIER_CTRL * !!(modifiers & E_BINDING_MODIFIER_CTRL));
   mod |= (ECORE_EVENT_MODIFIER_ALT * !!(modifiers & E_BINDING_MODIFIER_ALT));
   mod |= (ECORE_EVENT_MODIFIER_WIN * !!(modifiers & E_BINDING_MODIFIER_WIN));
   mod |= (ECORE_EVENT_MODIFIER_ALTGR * !!(modifiers & E_BINDING_MODIFIER_ALTGR));
   e_comp_wl_input_keyboard_event_generate(key, mod, 0);
   e_comp_wl_input_keyboard_event_generate(key, mod, 1);
   return EINA_TRUE;
}
#endif

ACT_FN_GO(module_enable, )
{
   E_Module *m;

   if (!params) return;
   m = e_module_find(params);
   if (!m)
     {
        m = e_module_new(params);
        if (!m) return;
     }
   e_module_enable(m);
}

ACT_FN_GO(module_disable, )
{
   E_Module *m;

   if (!params) return;
   m = e_module_find(params);
   if (!m) return;
   e_module_disable(m);
}

ACT_FN_GO(module_toggle, )
{
   E_Module *m;

   fprintf(stderr, "toggle\n");
   if (!params) return;
   fprintf(stderr, "'%s'\n", params);
   m = e_module_find(params);
   fprintf(stderr, "m = %p\n", m);
   if (!m)
     {
        m = e_module_new(params);
        if (!m) return;
     }
   fprintf(stderr, "currently %i\n", e_module_enabled_get(m));
   if (e_module_enabled_get(m)) e_module_disable(m);
   else e_module_enable(m);
}

ACT_FN_GO(screen_redo, EINA_UNUSED)
{
   printf("REDOOOOOOOOOOOOOOOOOOOOOOOOOOO\n");
#ifndef HAVE_WAYLAND_ONLY
   e_randr2_screeninfo_update();
   e_randr2_config_apply();
#endif
}

/***************************************************************************/
static Eina_Bool
_skip_win(E_Client *ec, E_Zone *zone, E_Desk *desk)
{
   if ((!ec->icccm.accepts_focus) && (!ec->icccm.take_focus)) return EINA_TRUE;
   if (ec->netwm.state.skip_taskbar) return EINA_TRUE;
   if (ec->user_skip_winlist) return EINA_TRUE;
   if (ec->iconic) return EINA_TRUE;
   if (ec->zone != zone) return EINA_TRUE;
   if (!((ec->sticky) || (ec->desk == desk))) return EINA_TRUE;
   return EINA_FALSE;
}

static int
_point_line_dist(int x, int y, int lx1, int ly1, int lx2, int ly2)
{
   int xx, yy, dx, dy;
   int a = x - lx1;
   int b = y - ly1;
   int c = lx2 - lx1;
   int d = ly2 - ly1;
   int dot = (a * c) + (b * d);
   int len_sq = (c * c) + (d * d);
   double dist, param = -1.0;

   // if line is 0 length
   if (len_sq) param = (double)dot / len_sq;

   if (param < 0)
     {
        xx = lx1;
        yy = ly1;
     }
   else if (param > 1)
     {
        xx = lx2;
        yy = ly2;
     }
   else
     {
        xx = lx1 + lround(param * c);
        yy = ly1 + lround(param * d);
     }

   dx = x - xx;
   dy = y - yy;
   dist = sqrt((dx * dx) + (dy * dy));
   return lround(dist);
}

ACT_FN_GO(window_focus, EINA_UNUSED)
{
   E_Zone *zone = e_zone_current_get();
   E_Desk *desk = e_desk_current_get(zone);
   E_Client *ec, *ec_orig,
     *ec_prev = NULL, *ec_last = NULL, *ec_first = NULL, *ec_next = NULL;
   Eina_List *l;
   int distance = INT_MAX, cx, cy, dir = -1, found = 0;

   if (!params) return;
   ec_orig = e_client_focused_get();
   if (!ec_orig)
     {
        // XXX: just pick any window to focus
        EINA_LIST_FOREACH(e_client_focus_stack_get(), l, ec)
          {
             if (_skip_win(ec, zone, desk)) continue;
             e_client_focus_set_with_pointer(ec);
             return;
          }
        return;
     }

   if      (!strcmp(params, "next"))  dir = -1;
   else if (!strcmp(params, "prev"))  dir = -2;
   else if (!strcmp(params, "up"))    dir =  0;
   else if (!strcmp(params, "down"))  dir =  1;
   else if (!strcmp(params, "left"))  dir =  2;
   else if (!strcmp(params, "right")) dir =  3;
   else
     {
        e_util_dialog_show(_("Error: window_focus action"),
                           _("Invalid parameter: %s"), params);
        return;
     }
   if (dir < 0)
     {
        EINA_LIST_FOREACH(e_client_focus_stack_get(), l, ec)
          {
             if (_skip_win(ec, zone, desk)) continue;

             if (ec == ec_orig)              found = 1;
             else if (!found)                ec_prev = ec;
             else if ((found) && (!ec_next)) ec_next = ec;

             if (!ec_first)                  ec_first = ec;
             ec_last = ec;
          }
        if (dir == -1) /* next */
          {
             if (ec_next) goto do_focus;
             else if (ec_first) ec_next = ec_first;
             else return;
          }
        else if (dir == -2)
          {
             if (ec_prev) ec_next = ec_prev;
             else if (ec_last) ec_next = ec_last;
             else return;
          }
        else return;
        goto do_focus;
     }

   cx = ec_orig->x + (ec_orig->w / 2);
   cy = ec_orig->y + (ec_orig->h / 2);

   EINA_LIST_FOREACH(e_client_focus_stack_get(), l, ec)
     {
        int a = 0, d = 0;

        if (ec == ec_orig) continue;
        if (_skip_win(ec, zone, desk)) continue;

        switch (dir)
          {
           case 0: /* up */
             d = _point_line_dist(cx, cy,
                                  ec->x,         ec->y + ec->h,
                                  ec->x + ec->w, ec->y + ec->h);
             if (d >= distance) continue;
             d = _point_line_dist(cx, cy,
                                  ec->x,         ec->y + (ec->h / 2),
                                  ec->x + ec->w, ec->y + (ec->h / 2));
             if (d >= distance) continue;
             if (cy <= (ec->y + (ec->h / 2))) continue;
             a = abs(cx - (ec->x + (ec->w / 2)));
             d += (a * a) / d;
             if (d >= distance) continue;
             break;
           case 1: /* down */
             d = _point_line_dist(cx, cy,
                                  ec->x,         ec->y,
                                  ec->x + ec->w, ec->y);
             if (d >= distance) continue;
             d = _point_line_dist(cx, cy,
                                  ec->x,         ec->y + (ec->h / 2),
                                  ec->x + ec->w, ec->y + (ec->h / 2));
             if (d >= distance) continue;
             if (cy >= (ec->y + (ec->h / 2))) continue;
             a = abs(cx - (ec->x + (ec->w / 2)));
             d += (a * a) / d;
             if (d >= distance) continue;
             break;
           case 2: /* left */
             d = _point_line_dist(cx, cy,
                                  ec->x + ec->w, ec->y,
                                  ec->x + ec->w, ec->y + ec->h);
             if (d >= distance) continue;
             d = _point_line_dist(cx, cy,
                                  ec->x + (ec->w / 2), ec->y,
                                  ec->x + (ec->w / 2), ec->y + ec->h);
             if (d >= distance) continue;
             if (cx <= (ec->x + (ec->w / 2))) continue;
             a = abs(cy - (ec->y + (ec->h / 2)));
             d += (a * a) / d;
             if (d >= distance) continue;
             break;
           case 3: /* right */
             d = _point_line_dist(cx, cy,
                                  ec->x, ec->y,
                                  ec->x, ec->y + ec->h);
             if (d >= distance) continue;
             d = _point_line_dist(cx, cy,
                                  ec->x + (ec->w / 2), ec->y,
                                  ec->x + (ec->w / 2), ec->y + ec->h);
             if (d >= distance) continue;
             if (cx >= (ec->x + (ec->w / 2))) continue;
             a = abs(cy - (ec->y + (ec->h / 2)));
             d += (a * a) / d;
             if (d >= distance) continue;
             break;
          }
        ec_next = ec;
        distance = d;
     }
do_focus:
   if (!ec_next) return;

   ec = ec_next;
   if (ec->iconic) e_client_uniconify(ec);
   if (ec->shaded) e_client_unshade(ec, ec->shade_dir);
   if (!ec->lock_user_stacking)
     {
        evas_object_raise(ec->frame);
        e_client_raise_latest_set(ec);
     }
   e_client_focus_set_with_pointer(ec);
}

/* local subsystem globals */
static Eina_Hash *actions = NULL;
static Eina_List *action_list = NULL;
static Eina_List *action_names = NULL;
static Eina_List *action_groups = NULL;

static void
_e_actions_post_init(void *d EINA_UNUSED)
{
#ifdef HAVE_WAYLAND
   E_Action *act;

   /* wayland-specific actions */
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        /* mouse -> key */
        /* "key" here is the platform-specific key name;
         * /usr/share/X11/xkb/keycodes/evdev is probably what your system is using
         */
        ACT_GO_MOUSE(mouse_to_key);
        e_action_predef_name_set(N_("Mouse Remapping"),
                                 N_("Mouse to key"), "mouse_to_key",
                                 NULL, "[AD02] [ctrl+shift+alt+win+altgr+AD02]", 1);
     }
#endif
}

ACT_FN_GO_KEY(profile_switch, , EINA_UNUSED)
{
   E_Action *a;
   const char *cur_profile;

   if (!params) return;

   cur_profile = e_config_profile_get();

   if (strcmp(cur_profile, params) == 0) return;

   e_config_save_flush();
   e_config_profile_set(params);
   elm_config_profile_set(params);
   e_config_profile_save();
   e_config_save_block_set(1);

   a = e_action_find("restart");
   if ((a) && (a->func.go)) a->func.go(NULL, NULL);
}

static void
_e_actions_profiles_create(void)
{
   E_Action *act;
   Eina_List *profiles, *l;
   int i = 0;

   ACT_GO_KEY(profile_switch);

   profiles = e_config_profile_list();
   for (i = 0, l = profiles; l; l = l->next, i++)
     {
        Efreet_Desktop *desk = NULL;
        char *prof, *pdir, buff[PATH_MAX];
        const char *label;

        prof = l->data;

        pdir = e_config_profile_dir_get(prof);
        snprintf(buff, sizeof(buff), "%s/profile.desktop", pdir);
        desk = efreet_desktop_new(buff);
        if (!desk)
          {
             e_prefix_data_snprintf(buff, sizeof(buff),
                                    "data/config/%s/", prof);
             free(pdir);
             pdir = strdup(buff);
             if (pdir)
               {
                  snprintf(buff, sizeof(buff), "%s/profile.desktop", pdir);
                  desk = efreet_desktop_new(buff);
               }
          }

        label = prof;
        if ((desk) && (desk->name)) label = desk->name;
        e_action_predef_name_set(N_("Profile: Switch"), label,
                                 "profile_switch", prof, NULL, 0);

        free(prof);
        free(pdir);
        if (desk) efreet_desktop_free(desk);
     }
   if (profiles) eina_list_free(profiles);
}

/* externally accessible functions */

EINTERN int
e_actions_init(void)
{
   E_Action *act;

   actions = eina_hash_string_superfast_new(NULL);
   ACT_GO(window_move);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move"),
                            "window_move", NULL, NULL, 0);

   ACT_GO_MOUSE(window_move);
   ACT_GO_SIGNAL(window_move);
   ACT_END(window_move);
   ACT_END_MOUSE(window_move);
   ACT_GO_KEY(window_move);

   /* window_resize */
   ACT_GO(window_resize);
   e_action_predef_name_set(N_("Window : Actions"), N_("Resize"),
                            "window_resize", NULL, NULL, 0);

   ACT_GO_MOUSE(window_resize);
   ACT_GO_SIGNAL(window_resize);
   ACT_END(window_resize);
   ACT_END_MOUSE(window_resize);
   ACT_GO_KEY(window_resize);

   /* window_menu */
   ACT_GO(window_menu);
   e_action_predef_name_set(N_("Menu"), N_("Window Menu"),
                            "window_menu", NULL, NULL, 0);

   ACT_GO_MOUSE(window_menu);
   ACT_GO_KEY(window_menu);

   /* window_raise */
   ACT_GO(window_raise);
   e_action_predef_name_set(N_("Window : Actions"), N_("Raise"),
                            "window_raise", NULL, NULL, 0);

   /* window_lower */
   ACT_GO(window_lower);
   e_action_predef_name_set(N_("Window : Actions"), N_("Lower"),
                            "window_lower", NULL, NULL, 0);

   /* window_close */
   ACT_GO(window_close);
   e_action_predef_name_set(N_("Window : Actions"), N_("Close"),
                            "window_close", NULL, NULL, 0);

   /* window_kill */
   ACT_GO(window_kill);
   e_action_predef_name_set(N_("Window : Actions"), N_("Kill"),
                            "window_kill", NULL, NULL, 0);

   /* window_sticky_toggle */
   ACT_GO(window_sticky_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Sticky Mode Toggle"),
                            "window_sticky_toggle", NULL, NULL, 0);

   ACT_GO(window_sticky);
   e_action_predef_name_set(N_("Window : State"), N_("Sticky Mode Enable"),
                            "window_sticky", NULL, NULL, 0);

   /* window_iconic_toggle */
   ACT_GO(window_iconic_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Iconic Mode Toggle"),
                            "window_iconic_toggle", NULL, NULL, 0);

   ACT_GO(window_iconic);
   e_action_predef_name_set(N_("Window : State"), N_("Iconic Mode Enable"),
                            "window_iconic", NULL, NULL, 0);

   /* window_fullscreen_toggle */
   ACT_GO(window_fullscreen_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Fullscreen Mode Toggle"),
                            "window_fullscreen_toggle", NULL, NULL, 0);

   ACT_GO(window_fullscreen);
   e_action_predef_name_set(N_("Window : State"), N_("Fullscreen Mode Enable"),
                            "window_fullscreen", NULL, NULL, 0);

   /* window_maximized_toggle */
   ACT_GO(window_maximized_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize"),
                            "window_maximized_toggle", NULL, NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Vertically"),
                            "window_maximized_toggle", "default vertical",
                            NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Horizontally"),
                            "window_maximized_toggle", "default horizontal",
                            NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Left"),
                            "window_maximized_toggle", "default left",
                            NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Right"),
                            "window_maximized_toggle", "default right",
                            NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Fullscreen"),
                            "window_maximized_toggle", "fullscreen", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Mode \"Smart\""),
                            "window_maximized_toggle", "smart", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Mode \"Expand\""),
                            "window_maximized_toggle", "expand", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Maximize Mode \"Fill\""),
                            "window_maximized_toggle", "fill", NULL, 0);

   ACT_GO(window_maximized);

   /* window_shaded_toggle */
   ACT_GO(window_shaded_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Shade Up Mode Toggle"),
                            "window_shaded_toggle", "up", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Shade Down Mode Toggle"),
                            "window_shaded_toggle", "down", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Shade Left Mode Toggle"),
                            "window_shaded_toggle", "left", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Shade Right Mode Toggle"),
                            "window_shaded_toggle", "right", NULL, 0);
   e_action_predef_name_set(N_("Window : State"), N_("Shade Mode Toggle"),
                            "window_shaded_toggle", NULL, NULL, 0);

   ACT_GO(window_shaded);
   e_action_predef_name_set(N_("Window : State"), N_("Set Shaded State"),
                            "window_shaded", NULL, _("syntax: \"(0|1) (up|down|left|right)\""), 1);

   /* window_borderless_toggle */
   ACT_GO(window_borderless_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Toggle Borderless State"),
                            "window_borderless_toggle", NULL, NULL, 0);

   /* window_border_set */
   ACT_GO(window_border_set);
   e_action_predef_name_set(N_("Window : State"), N_("Set Border"),
                            "window_border_set", NULL,
                            "syntax: BorderName, example: pixel", 1);

   /* window_border_cycle */
   ACT_GO(window_border_cycle);
   e_action_predef_name_set(N_("Window : State"), N_("Cycle between Borders"),
                            "window_border_cycle", NULL,
                            "syntax: BorderNames, example: default pixel", 1);

   /* window_pinned_toggle */
   ACT_GO(window_pinned_toggle);
   e_action_predef_name_set(N_("Window : State"), N_("Toggle Pinned State"),
                            "window_pinned_toggle", NULL, NULL, 0);

   /* desk_flip_by */
   ACT_GO(desk_flip_by);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Left"),
                            "desk_flip_by", "-1 0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Right"),
                            "desk_flip_by", "1 0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Up"),
                            "desk_flip_by", "0 -1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Down"),
                            "desk_flip_by", "0 1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop By..."),
                            "desk_flip_by", NULL,
                            "syntax: X-offset Y-offset, example: -1 0", 1);

   /* desk_flip_prev */
   ACT_GO(desk_flip_prev);
   e_action_predef_name_set(N_("Desktop"), N_("Flip To Previous Desktop"),
                            "desk_flip_prev", NULL, NULL, 0);

   /* desk_deskshow_toggle */
   ACT_GO(desk_deskshow_toggle);
   e_action_predef_name_set(N_("Desktop"), N_("Show The Desktop"),
                            "desk_deskshow_toggle", NULL, NULL, 0);

   /* shelf_show */
   ACT_GO(shelf_show);
   ACT_GO_EDGE(shelf_show);
   e_action_predef_name_set(N_("Desktop"), N_("Show The Shelf"), "shelf_show",
                            NULL, "shelf name glob: Shelf-* ", 1);

   /* desk_linear_flip_to */
   ACT_GO(desk_flip_to);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop To..."),
                            "desk_flip_to", NULL,
                            "syntax: X Y, example: 1 2", 1);
   /* desk_linear_flip_to_screen */
   ACT_GO(desk_linear_flip_to_screen);
   e_action_predef_name_set(N_("Desktop"), N_("Switch Desktop To... On Screen..."),
                            "desk_linear_flip_to_screen", NULL,
                            "syntax: D S, example: 0 1", 1);

   /* desk_linear_flip_by */
   ACT_GO(desk_linear_flip_by);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Linearly..."),
                            "desk_linear_flip_by",
                            NULL, "syntax: N-offset, example: -2", 1);

   /* desk_linear_flip_to */
   ACT_GO(desk_linear_flip_to);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 0"),
                            "desk_linear_flip_to", "0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 1"),
                            "desk_linear_flip_to", "1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 2"),
                            "desk_linear_flip_to", "2", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 3"),
                            "desk_linear_flip_to", "3", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 4"),
                            "desk_linear_flip_to", "4", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 5"),
                            "desk_linear_flip_to", "5", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 6"),
                            "desk_linear_flip_to", "6", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 7"),
                            "desk_linear_flip_to", "7", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 8"),
                            "desk_linear_flip_to", "8", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 9"),
                            "desk_linear_flip_to", "9", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 10"),
                            "desk_linear_flip_to", "10", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 11"),
                            "desk_linear_flip_to", "11", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop..."),
                            "desk_linear_flip_to", NULL,
                            "syntax: N, example: 1", 1);

   /* desk_flip_by_all */
   ACT_GO(desk_flip_by_all);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Left (All Screens)"),
                            "desk_flip_by_all", "-1 0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Right (All Screens)"),
                            "desk_flip_by_all", "1 0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Up (All Screens)"),
                            "desk_flip_by_all", "0 -1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Down (All Screens)"),
                            "desk_flip_by_all", "0 1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop By... (All Screens)"),
                            "desk_flip_by_all", NULL,
                            "syntax: X-offset Y-offset, example: -1 0", 1);

   /* desk_flip_to_all */
   ACT_GO(desk_flip_to_all);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop To... (All Screens)"),
                            "desk_flip_to_all", NULL,
                            "syntax: X Y, example: 1 2", 1);

   /* desk_linear_flip_by_all */
   ACT_GO(desk_linear_flip_by_all);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop Linearly... (All Screens)"),
                            "desk_linear_flip_by_all",
                            NULL, "syntax: N-offset, example: -2", 1);

   /* desk_flip_in_direction */
   ACT_GO_EDGE(desk_flip_in_direction);
   e_action_predef_name_set(N_("Desktop"), N_("Flip Desktop In Direction..."),
                            "desk_flip_in_direction", NULL, "syntax: N-pixel-offset, example: 25", 1);

   /* desk_linear_flip_to_all */
   ACT_GO(desk_linear_flip_to_all);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 0 (All Screens)"),
                            "desk_linear_flip_to_all", "0", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 1 (All Screens)"),
                            "desk_linear_flip_to_all", "1", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 2 (All Screens)"),
                            "desk_linear_flip_to_all", "2", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 3 (All Screens)"),
                            "desk_linear_flip_to_all", "3", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 4 (All Screens)"),
                            "desk_linear_flip_to_all", "4", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 5 (All Screens)"),
                            "desk_linear_flip_to_all", "5", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 6 (All Screens)"),
                            "desk_linear_flip_to_all", "6", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 7 (All Screens)"),
                            "desk_linear_flip_to_all", "7", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 8 (All Screens)"),
                            "desk_linear_flip_to_all", "8", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 9 (All Screens)"),
                            "desk_linear_flip_to_all", "9", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 10 (All Screens)"),
                            "desk_linear_flip_to_all", "10", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop 11 (All Screens)"),
                            "desk_linear_flip_to_all", "11", NULL, 0);
   e_action_predef_name_set(N_("Desktop"), N_("Switch To Desktop... (All Screens)"),
                            "desk_linear_flip_to_all", NULL,
                            "syntax: N, example: 1", 1);

   /* window_jump_to */
   ACT_GO(window_jump_to);
   e_action_predef_name_set(N_("Window : List"), N_("Jump to window..."),
                            "window_jump_to", NULL, "syntax: icccm window name, example: urxvt-mutt", 1);

   ACT_GO(window_jump_to_or_start);
   e_action_predef_name_set(N_("Window : List"), N_("Jump to window... or start..."),
                            "window_jump_to_or_start", NULL, "syntax: icccm_window_name application", 1);

   /* screen_send_to */
   ACT_GO(screen_send_to);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse To Screen 0"),
                            "screen_send_to", "0", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse To Screen 1"),
                            "screen_send_to", "1", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse To Screen..."),
                            "screen_send_to", NULL,
                            "syntax: N, example: 0", 1);

   /* screen_send_by */
   ACT_GO(screen_send_by);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse Forward 1 Screen"),
                            "screen_send_by", "1", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse Back 1 Screen"),
                            "screen_send_by", "-1", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Send Mouse Forward/Back Screens..."),
                            "screen_send_by", NULL,
                            "syntax: N-offset, example: -2", 1);

   ACT_GO(dim_screen);
   e_action_predef_name_set(N_("Screen"), N_("Dim"), "dim_screen",
                            NULL, NULL, 0);
   ACT_GO(undim_screen);
   e_action_predef_name_set(N_("Screen"), N_("Undim"), "undim_screen",
                            NULL, NULL, 0);
   ACT_GO(backlight_set);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Set"), "backlight_set",
                            NULL, "syntax: brightness(0 - 100), example: 50", 1);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Min"), "backlight_set",
                            "0", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Mid"), "backlight_set",
                            "50", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Max"), "backlight_set",
                            "100", NULL, 0);
   ACT_GO(backlight_adjust);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Adjust"), "backlight_adjust",
                            NULL, "syntax: brightness(-100 - 100), example: -20", 1);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Up"), "backlight_adjust",
                            "10", NULL, 0);
   e_action_predef_name_set(N_("Screen"), N_("Backlight Down"), "backlight_adjust",
                            "-10", NULL, 0);

   /* blanking */
   ACT_GO(blanking);
   e_action_predef_name_set(N_("Screen"), N_("Blanking Off"), "blanking",
                            "off", "syntax: toggle/off/on, example: off", 0);
   e_action_predef_name_set(N_("Screen"), N_("Blanking On"), "blanking",
                            "on", "syntax: toggle/off/on, example: on", 0);
   e_action_predef_name_set(N_("Screen"), N_("Blanking Toggle"), "blanking",
                            "toggle", "syntax: toggle/off/on, example: toggle", 0);
   /* screen setup */
   ACT_GO(screen_redo);
   e_action_predef_name_set(N_("Screen"),
                            N_("Update and re-apply screen setup"),
                            "screen_redo", NULL, NULL, 0);

   /* window_move_to_center */
   ACT_GO(window_move_to_center);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move To Center"),
                            "window_move_to_center", NULL, NULL, 0);

   /* window_quick_tile_upper_left */
   ACT_GO(window_quick_tile_to_quadrant);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move/resize to upper-left quadrant"),
                            "window_quick_tile_to_quadrant", "upper_left", NULL, 0);

   /* window_quick_tile_upper_right */
   ACT_GO(window_quick_tile_to_quadrant);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move/resize to upper-right quadrant"),
                            "window_quick_tile_to_quadrant", "upper_right", NULL, 0);

   /* window_quick_tile_lower_left */
   ACT_GO(window_quick_tile_to_quadrant);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move/resize to lower-left quadrant"),
                            "window_quick_tile_to_quadrant", "lower_left", NULL, 0);

   /* window_quick_tile_lower_right */
   ACT_GO(window_quick_tile_to_quadrant);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move/resize to lower-right quadrant"),
                            "window_quick_tile_to_quadrant", "lower_right", NULL, 0);

   /* window_move_to */
   ACT_GO(window_move_to);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move To Coordinates..."),
                            "window_move_to", NULL,
                            "syntax: [+,-]X [+,-]Y or * [+,-]Y or [+,-]X *, example: -1 +1", 1);
   /* window_move_by */
   ACT_GO(window_move_by);
   e_action_predef_name_set(N_("Window : Actions"), N_("Move By Coordinate Offset..."),
                            "window_move_by", NULL,
                            "syntax: X-offset Y-offset, example: -1 0", 1);

   /* window_resize_by */
   ACT_GO(window_resize_by);
   e_action_predef_name_set(N_("Window : Actions"), N_("Resize By..."),
                            "window_resize_by", NULL,
                            "syntax: W H, example: 100 150", 1);

   /* window_push */
   ACT_GO(window_push);
   e_action_predef_name_set(N_("Window : Actions"), N_("Push in Direction..."),
                            "window_push", NULL,
                            "syntax: direction, example: up, down, left, right, up-left, up-right, down-left, down-right", 1);

   /* window_grow */
   ACT_GO(window_grow);
   e_action_predef_name_set(N_("Window : Actions"), N_("Grow in Direction..."),
                            "window_grow", NULL,
                            "syntax: direction, example: up, down, left, right, up-left, up-right, down-left, down-right", 1);

   /* window_drag_icon */
   ACT_GO(window_drag_icon);
   e_action_predef_name_set(N_("Window : Actions"), N_("Drag Icon..."),
                            "window_drag_icon", NULL, NULL, 0);

   /* window_desk_move_by */
   ACT_GO(window_desk_move_by);
   e_action_predef_name_set(N_("Window : Moving"), N_("To Next Desktop"),
                            "window_desk_move_by", "1 0", NULL, 0);
   e_action_predef_name_set(N_("Window : Moving"), N_("To Previous Desktop"),
                            "window_desk_move_by", "-1 0", NULL, 0);
   e_action_predef_name_set(N_("Window : Moving"), N_("By Desktop #..."),
                            "window_desk_move_by", NULL,
                            "syntax: X-offset Y-offset, example: -2 2", 1);

   /* window_desk_move_to */
   ACT_GO(window_desk_move_to);
   e_action_predef_name_set(N_("Window : Moving"), N_("To Desktop..."),
                            "window_desk_move_to", NULL,
                            "syntax: X Y, example: 0 1", 1);

   /* window_zone_move_by */
   ACT_GO(window_zone_move_by);
   e_action_predef_name_set(N_("Window : Moving"), N_("To Next Screen"),
                            "window_zone_move_by", "1", NULL, 0);
   e_action_predef_name_set(N_("Window : Moving"), N_("To Previous Screen"),
                            "window_zone_move_by", "-1", NULL, 0);

   /* Move window focus somewhere */
   ACT_GO(window_focus);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus next window"),
                            "window_focus", "next", NULL, 0);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus previous window"),
                            "window_focus", "prev", NULL, 0);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus window above"),
                            "window_focus", "up", NULL, 0);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus window below"),
                            "window_focus", "down", NULL, 0);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus window left"),
                            "window_focus", "left", NULL, 0);
   e_action_predef_name_set(N_("Window : Focus"),
                            N_("Focus window right"),
                            "window_focus", "right", NULL, 0);

   /* menu_show */
   ACT_GO(menu_show);
   e_action_predef_name_set(N_("Menu"), N_("Show Main Menu"),
                            "menu_show", "main", NULL, 0);
   e_action_predef_name_set(N_("Menu"), N_("Show Favorites Menu"), "menu_show",
                            "favorites", NULL, 0);
   e_action_predef_name_set(N_("Menu"), N_("Show All Applications Menu"),
                            "menu_show", "all", NULL, 0);
   e_action_predef_name_set(N_("Menu"), N_("Show Clients Menu"), "menu_show",
                            "clients", NULL, 0);
   e_action_predef_name_set(N_("Menu"), N_("Show Menu..."), "menu_show", NULL,
                            "syntax: MenuName, example: MyMenu", 1);
   ACT_GO_MOUSE(menu_show);
   ACT_GO_KEY(menu_show);

   /* internal: for showing a menu from an object */
   ACT_GO(menu_show_object);

   /* exec */
   ACT_GO(exec);
   e_action_predef_name_set(N_("Launch"), N_("Command"), "exec", NULL,
                            "syntax: CommandName, example: /usr/bin/xmms", 1);

   /* app */
   ACT_GO(app);
   e_action_predef_name_set(N_("Launch"), N_("Application"), "app", NULL,
                            "syntax: [file:file.desktop|name:App Name|generic:Generic Name|exe:exename], example: file:terminology.desktop | file:/path/to/terminology.desktop | name:Terminology | generic:Terminal Emulator | exe:xterm", 1);

   /* new instance of focused app */
   ACT_GO(app_new_instance);
   e_action_predef_name_set(N_("Launch"), N_("New Instance of Focused App"), "app_new_instance", NULL,
                            NULL, 0);

   ACT_GO(restart);
   e_action_predef_name_set(N_("Enlightenment"), N_("Restart"), "restart",
                            NULL, NULL, 0);

   ACT_GO(exit);
   e_action_predef_name_set(N_("Enlightenment"), N_("Exit"), "exit",
                            NULL, NULL, 0);

   ACT_GO(exit_now);
   e_action_predef_name_set(N_("Enlightenment"), N_("Exit Now"),
                            "exit_now", NULL, NULL, 0);

   /* modules */
   ACT_GO(module_enable);
   e_action_predef_name_set(N_("Enlightenment : Module"),
                            N_("Enable the named module"),
                            "module_enable", NULL, NULL, 1);
   ACT_GO(module_disable);
   e_action_predef_name_set(N_("Enlightenment : Module"),
                            N_("Disable the named module"),
                            "module_disable", NULL, NULL, 1);
   ACT_GO(module_toggle);
   e_action_predef_name_set(N_("Enlightenment : Module"),
                            N_("Toggle the named module"),
                            "module_toggle", NULL, NULL, 1);

   ACT_GO(logout);
   e_action_predef_name_set(N_("System"), N_("Log Out"), "logout",
                            NULL, NULL, 0);

   ACT_GO(halt_now);
   e_action_predef_name_set(N_("System"), N_("Power Off Now"),
                            "halt_now", NULL, NULL, 0);

   ACT_GO(halt);
   e_action_predef_name_set(N_("System"), N_("Power Off"), "halt",
                            NULL, NULL, 0);

   ACT_GO(reboot);
   e_action_predef_name_set(N_("System"), N_("Reboot"), "reboot",
                            NULL, NULL, 0);

   ACT_GO(suspend_now);
   e_action_predef_name_set(N_("System"), N_("Suspend Now"), "suspend_now",
                            NULL, NULL, 0);

   ACT_GO(suspend);
   e_action_predef_name_set(N_("System"), N_("Suspend"), "suspend",
                            NULL, NULL, 0);

   ACT_GO(suspend_smart);
   e_action_predef_name_set(N_("System"), N_("Suspend Intelligently"), "suspend_smart",
                            NULL, NULL, 0);

   ACT_GO(hibernate);
   e_action_predef_name_set(N_("System"), N_("Hibernate"), "hibernate",
                            NULL, NULL, 0);

   ACT_GO(hibernate_now);
   e_action_predef_name_set(N_("System"), N_("Hibernate Now"), "hibernate_now",
                            NULL, NULL, 0);

   ACT_GO(hibernate_smart);
   e_action_predef_name_set(N_("System"), N_("Hibernate Intelligently"), "hibernate_smart",
                            NULL, NULL, 0);

   ACT_GO(pointer_resize_push);
   ACT_GO(pointer_resize_pop);

   /* desk_lock */
   ACT_GO(desk_lock);
   e_action_predef_name_set(N_("Desktop"), N_("Lock"), "desk_lock",
                            NULL, NULL, 0);

   /* cleanup_windows */
   ACT_GO(cleanup_windows);
   e_action_predef_name_set(N_("Desktop"), N_("Cleanup Windows"),
                            "cleanup_windows", NULL, NULL, 0);

   /* delayed_action */
   ACT_GO_KEY(delayed_action);
   e_action_predef_name_set(N_("Generic : Actions"), N_("Delayed Action"),
                            "delayed_action", NULL, "[0.0 exec xterm] [0.3 exec xev]", 1);
   ACT_GO_MOUSE(delayed_action);
   ACT_END_KEY(delayed_action);
   ACT_END_MOUSE(delayed_action);

   /* xkb */
   ACT_GO(kbd_layout);
   e_action_predef_name_set(N_("Keyboard Layouts"),
                            N_("Use keyboard layout"), "kbd_layout",
                            NULL, "syntax: Keyboard-layout-name, example: us", 1);
   ACT_GO(kbd_layout_next);
   e_action_predef_name_set(N_("Keyboard Layouts"),
                            N_("Next keyboard layout"), "kbd_layout_next",
                            NULL, NULL, 0);
   ACT_GO(kbd_layout_prev);
   e_action_predef_name_set(N_("Keyboard Layouts"),
                            N_("Previous keyboard layout"), "kbd_layout_prev",
                            NULL, NULL, 0);

   /* profiles */
   _e_actions_profiles_create();

   ecore_job_add(_e_actions_post_init, NULL);

   return 1;
}

EINTERN int
e_actions_shutdown(void)
{
   Eina_List *tlist = NULL, *l;
   E_Action *act;

   e_action_predef_name_all_del();

   EINA_LIST_FOREACH(action_list, l, act)
     tlist = eina_list_append(tlist, act);
   EINA_LIST_FREE(tlist, act)
     e_object_del(E_OBJECT(act));

   action_names = eina_list_free(action_names);
   eina_hash_free(actions);
   actions = NULL;

   return 1;
}

E_API Eina_List *
e_action_name_list(void)
{
   return action_names;
}

E_API E_Action *
e_action_add(const char *name)
{
   E_Action *act;

   act = e_action_find(name);
   if (!act)
     {
        act = E_OBJECT_ALLOC(E_Action, E_ACTION_TYPE, _e_action_free);
        if (!act) return NULL;
        act->name = name;
        eina_hash_direct_add(actions, act->name, act);
        action_names = eina_list_append(action_names, name);
        action_list = eina_list_append(action_list, act);
     }
   return act;
}

E_API void
e_action_del(const char *name)
{
   E_Action *act;

   act = eina_hash_find(actions, name);
   if (act) _e_action_free(act);
}

E_API E_Action *
e_action_find(const char *name)
{
   E_Action *act;

   act = eina_hash_find(actions, name);
   return act;
}

E_API const char *
e_action_predef_label_get(const char *action, const char *params)
{
   E_Action_Group *actg = NULL;
   E_Action_Description *actd = NULL;
   Eina_List *l, *l2;

   EINA_LIST_FOREACH(action_groups, l, actg)
     {
        EINA_LIST_FOREACH(actg->acts, l2, actd)
          {
             if (!strcmp(actd->act_cmd, action))
               {
                  if ((params) && (actd->act_params))
                    {
                       if (!strcmp(params, actd->act_params))
                         return actd->act_name;
                    }
                  else return actd->act_name;
               }
          }
     }
   if (params) return e_action_predef_label_get(action, NULL);
   return NULL;
}

E_API void
e_action_predef_name_set(const char *act_grp, const char *act_name, const char *act_cmd, const char *act_params, const char *param_example, int editable)
{
   E_Action_Group *actg = NULL;
   E_Action_Description *actd = NULL;
   Eina_List *l;

   if ((!act_grp) || (!act_name)) return;

   EINA_LIST_FOREACH(action_groups, l, actg)
     {
        if (!strcmp(actg->act_grp, act_grp)) break;
        actg = NULL;
     }

   if (!actg)
     {
        actg = E_NEW(E_Action_Group, 1);
        if (!actg) return;

        actg->act_grp = eina_stringshare_add(act_grp);
        action_groups = eina_list_append(action_groups, actg);
        action_groups =
          eina_list_sort(action_groups, -1, _action_groups_sort_cb);
     }

   EINA_LIST_FOREACH(actg->acts, l, actd)
     {
        if (!strcmp(actd->act_name, act_name)) break;
        actd = NULL;
     }

   if (actd) return;

   actd = E_NEW(E_Action_Description, 1);
   if (!actd) return;

   actd->act_name = eina_stringshare_add(act_name);
   actd->act_cmd = !act_cmd ? NULL : eina_stringshare_add(act_cmd);
   actd->act_params = !act_params ? NULL : eina_stringshare_add(act_params);
   actd->param_example = !param_example ? NULL : eina_stringshare_add(param_example);
   actd->editable = editable;

   actg->acts = eina_list_append(actg->acts, actd);
}

E_API void
e_action_predef_name_del(const char *act_grp, const char *act_name)
{
   E_Action_Group *actg = NULL;
   E_Action_Description *actd = NULL;
   Eina_List *l;

   EINA_LIST_FOREACH(action_groups, l, actg)
     {
        if (!strcmp(actg->act_grp, act_grp)) break;
        actg = NULL;
     }

   if (!actg) return;

   EINA_LIST_FOREACH(actg->acts, l, actd)
     {
        if (!strcmp(actd->act_name, act_name))
          {
             actg->acts = eina_list_remove(actg->acts, actd);

             if (actd->act_name) eina_stringshare_del(actd->act_name);
             if (actd->act_cmd) eina_stringshare_del(actd->act_cmd);
             if (actd->act_params) eina_stringshare_del(actd->act_params);
             if (actd->param_example) eina_stringshare_del(actd->param_example);

             E_FREE(actd);

             if (!eina_list_count(actg->acts))
               {
                  action_groups = eina_list_remove(action_groups, actg);
                  if (actg->act_grp) eina_stringshare_del(actg->act_grp);
                  E_FREE(actg);
               }
             break;
          }
     }
}

E_API void
e_action_predef_name_all_del(void)
{
   E_Action_Group *actg = NULL;
   E_Action_Description *actd = NULL;

   EINA_LIST_FREE(action_groups, actg)
     {
        EINA_LIST_FREE(actg->acts, actd)
          {
             if (actd->act_name) eina_stringshare_del(actd->act_name);
             if (actd->act_cmd) eina_stringshare_del(actd->act_cmd);
             if (actd->act_params) eina_stringshare_del(actd->act_params);
             if (actd->param_example) eina_stringshare_del(actd->param_example);

             E_FREE(actd);
          }
        if (actg->act_grp) eina_stringshare_del(actg->act_grp);
        E_FREE(actg);
     }
   action_groups = NULL;
}

E_API Eina_List *
e_action_groups_get(void)
{
   return action_groups;
}

/* local subsystem functions */

static void
_e_action_free(E_Action *act)
{
   eina_hash_del(actions, act->name, act);
   action_names = eina_list_remove(action_names, act->name);
   action_list = eina_list_remove(action_list, act);
   free(act);
}

static E_Maximize
_e_actions_maximize_parse(const char *params)
{
   E_Maximize max = 0;
   int ret;
   char s1[32], s2[32];

   if (!params) return e_config->maximize_policy;
   ret = sscanf(params, "%20s %20s", s1, s2);
   if (ret == 2)
     {
        if (!strcmp(s2, "horizontal"))
          max = E_MAXIMIZE_HORIZONTAL;
        else if (!strcmp(s2, "vertical"))
          max = E_MAXIMIZE_VERTICAL;
        else if (!strcmp(s2, "left"))
          max = E_MAXIMIZE_LEFT;
        else if (!strcmp(s2, "right"))
          max = E_MAXIMIZE_RIGHT;
        else
          max = E_MAXIMIZE_BOTH;
     }
   if (ret >= 1)
     {
        if (!strcmp(s1, "fullscreen"))
          max |= E_MAXIMIZE_FULLSCREEN;
        else if (!strcmp(s1, "smart"))
          max |= E_MAXIMIZE_SMART;
        else if (!strcmp(s1, "expand"))
          max |= E_MAXIMIZE_EXPAND;
        else if (!strcmp(s1, "fill"))
          max |= E_MAXIMIZE_FILL;
        else
          max |= (e_config->maximize_policy & E_MAXIMIZE_TYPE);
     }
   else
     max = e_config->maximize_policy;
   return max;
}

static int
_action_groups_sort_cb(const void *d1, const void *d2)
{
   const E_Action_Group *g1, *g2;

   if (!(g1 = d1)) return 1;
   if (!(g2 = d2)) return -1;
   return strcmp(g1->act_grp, g2->act_grp);
}

#include "e.h"
#include <sys/wait.h>

/* public variables */
E_API unsigned long _e_alert_composite_win = 0;

EINTERN int
e_alert_init(void)
{
   return 1;
}

EINTERN int
e_alert_shutdown(void)
{
   return 1;
}

E_API void
e_alert_composite_win(Ecore_X_Window root, Ecore_X_Window comp)
{
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Atom composite_win;

   composite_win = ecore_x_atom_get("_E_COMP_WINDOW");

   if (comp == 0)
     ecore_x_window_prop_property_del(root, composite_win);
   else
     ecore_x_window_prop_card32_set(root, composite_win, &comp, 1);
#else
   (void)root;
   (void)comp;
#endif
}

E_API void
e_alert_show(void)
{
   if (!e_nopause)
     {
        fprintf(stderr, "PAUSE !\n");
        pause();
     }
}

#include "config.h"

#include <Elementary.h>
#ifndef HAVE_WAYLAND_ONLY
# include <Ecore_X.h>
#endif

static int         ret = 0, sig = 0, exit_gdb = 0;
static pid_t       pid;
static Eina_Bool   tainted = EINA_FALSE;
static const char *backtrace_str = NULL;

//#define FONT           "TopazPlus_a500_v1.0.pcf"
//#define FONT           "terminus-16.pcf"
#define FONT           "Topaz_a500_v1.0.ttf"
#define FONT_FALLBACK  "Mono"
#define COL_BG         0,   0,   0, 255
#define COL_FG         255,   0,   0, 255
#define HDIV           10
#define PDIV           20
#define BLINK          0.8
#define RECOVER_BUTTON 1
#define RECOVER_KEY    "F1"
#define ABORT_BUTTON   3
#define ABORT_KEY      "F12"

static inline const char *title1(void) {
   return "Software Failure. Press left mouse button / F1 to recover, right mouse button / F12 to abort.";
}
static inline const char *title2(void) { static char buf[512];
   if (tainted) snprintf
     (buf, sizeof(buf), "Tainted by unsupported modules");
   else if (exit_gdb) snprintf
     (buf, sizeof(buf), "Couldn't run gdb to collect a backtrace");
   else if (backtrace_str) snprintf
     (buf, sizeof(buf), "Backtrace log: %s", backtrace_str);
   else snprintf
     (buf, sizeof(buf), " ");
   return buf;
}
static inline const char *title3(void) { static char buf[512];
   snprintf(buf, sizeof(buf), "Guru Meditation #%08d.%08d", pid, sig);
   return buf;
}

/////////////////////////////////////////////////////////////////////////////

static Evas_Object *obj_base = NULL;
static Evas_Object *obj_outer = NULL;
static Evas_Object *obj_inner = NULL;
static Evas_Object *obj_line1 = NULL;
static Evas_Object *obj_line2 = NULL;
static Evas_Object *obj_line3 = NULL;

static void
mouse_up(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *info)
{
   Evas_Event_Mouse_Up *ev = info;

   if (ev->button == RECOVER_BUTTON)
     {
        ret = 111;
        elm_exit();
     }
   else if (ev->button == ABORT_BUTTON)
     {
        ret = 101;
        elm_exit();
     }
}

static void
key_down(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *info)
{
   Evas_Event_Key_Down *ev = info;

   if (!strcmp(ev->key, RECOVER_KEY))
     {
        ret = 111;
        elm_exit();
     }
   else if (!strcmp(ev->key, ABORT_KEY))
     {
        ret = 101;
        elm_exit();
     }
}

static const char *
font_get(void)
{
   const char *s = getenv("E_ALERT_FONT_DIR");
   static char buf[4096];

   if (s) snprintf(buf, sizeof(buf), "%s/"FONT, s);
   else snprintf(buf, sizeof(buf), "%s", FONT_FALLBACK);
   return buf;
}

static void
resize(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *o, void *info EINA_UNUSED)
{
   Evas_Coord w, h, tw, th;
   const char *font = font_get();

   evas_object_geometry_get(o, NULL, NULL, &w, &h);
   h = w / HDIV;

   evas_object_geometry_set(obj_base, 0, 0, w, h * HDIV * 2);
   evas_object_geometry_set(obj_outer, (1 * h) / PDIV, (1 * h) / PDIV,
                            w - ((h * 2) / PDIV), h - ((h * 2) / PDIV));
   evas_object_geometry_set(obj_inner, (2 * h) / PDIV, (2 * h) / PDIV,
                            w - ((h * 4) / PDIV), h - ((h * 4) / PDIV));

   evas_object_text_font_set(obj_line1, font, h / 8);
   evas_object_geometry_get(obj_line1, NULL, NULL, &tw, &th);
   evas_object_move(obj_line1, (w - tw) / 2, (3 * h) / PDIV);

   evas_object_text_font_set(obj_line2, font, h / 8);
   evas_object_geometry_get(obj_line2, NULL, NULL, &tw, &th);
   evas_object_move(obj_line2, (w - tw) / 2, (h - th) / 2);

   evas_object_text_font_set(obj_line3, font, h / 8);
   evas_object_geometry_get(obj_line3, NULL, NULL, &tw, &th);
   evas_object_move(obj_line3, (w - tw) / 2, h - th - (3 * h) / PDIV);
}

static Eina_Bool
timer(void *data EINA_UNUSED)
{
   if (evas_object_visible_get(obj_outer)) evas_object_hide(obj_outer);
   else evas_object_show(obj_outer);
   return EINA_TRUE;
}

static Eina_Bool
setup_display(void)
{
   Evas *e;
   Evas_Object *win, *o;
   const char *font = font_get();

   win = o = elm_win_add(NULL, "e-alert", ELM_WIN_SPLASH);
   if (!win) return EINA_FALSE;
   e = evas_object_evas_get(win);
   elm_win_override_set(o, EINA_TRUE);

   obj_base = o = evas_object_rectangle_add(e);
   evas_object_color_set(o, COL_BG);
   evas_object_show(o);

   obj_outer = o = evas_object_rectangle_add(e);
   evas_object_color_set(o, COL_FG);
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_show(o);

   obj_inner = o = evas_object_rectangle_add(e);
   evas_object_color_set(o, COL_BG);
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_show(o);

   obj_line1 = o = evas_object_text_add(e);
   evas_object_color_set(o, COL_FG);
   evas_object_text_font_set(o, font, 10);
   evas_object_text_text_set(o, title1());
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_show(o);

   obj_line2 = o = evas_object_text_add(e);
   evas_object_color_set(o, COL_FG);
   evas_object_text_font_set(o, font, 10);
   evas_object_text_text_set(o, title2());
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_show(o);

   obj_line3 = o = evas_object_text_add(e);
   evas_object_color_set(o, COL_FG);
   evas_object_text_font_set(o, font, 10);
   evas_object_text_text_set(o, title3());
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_show(o);

   evas_object_event_callback_add(obj_base, EVAS_CALLBACK_MOUSE_UP, mouse_up, NULL);
   evas_object_event_callback_add(win, EVAS_CALLBACK_KEY_DOWN, key_down, NULL);
   evas_object_event_callback_add(win, EVAS_CALLBACK_RESIZE, resize, NULL);

   ecore_timer_add(BLINK, timer, NULL);

   resize(NULL, e, win, NULL);

#ifndef HAVE_WAYLAND_ONLY
   if (getenv("DISPLAY"))
     {
        Ecore_X_Window root = ecore_x_window_root_first_get();
        Ecore_X_Atom atom_composite_win = ecore_x_atom_get("_E_COMP_WINDOW");
        unsigned int id;
        if (ecore_x_window_prop_card32_get(root, atom_composite_win, &id, 1) > 0)
          {
             Ecore_X_Window elmwin = elm_win_xwindow_get(win);
             ecore_x_window_reparent(elmwin, id, 0, 0);
             Ecore_X_Randr_Output output = ecore_x_randr_primary_output_get(root);
             Ecore_X_Randr_Crtc crct = ecore_x_randr_output_crtc_get(root, output);
             int x = 0, y = 0, w = 1, h = 1;
             ecore_x_randr_crtc_geometry_get(root, crct, &x, &y, &w, &h);
             ecore_x_window_move_resize(elmwin, x, y, w, w / HDIV);
          }
     }
#endif

   evas_object_show(win);

#ifndef HAVE_WAYLAND_ONLY
   if (getenv("DISPLAY"))
     {
        Ecore_X_Window elmwin = elm_win_xwindow_get(win);
        ecore_x_pointer_grab(elmwin);
        ecore_x_keyboard_grab(elmwin);
     }
#endif
   return EINA_TRUE;
}

int
main(int argc, char **argv)
{
   const char *s;
   int i = 0;

   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-h")) ||
            (!strcmp(argv[i], "-help")) ||
            (!strcmp(argv[i], "--help")))
          {
             printf("This is an internal tool for Enlightenment.\n"
                    "do not use it.\n");
             exit(0);
          }
        else if (i == 1) sig           = atoi(argv[i]);  // signal
        else if (i == 2) pid           = atoi(argv[i]);  // E's pid
        else if (i == 3) exit_gdb      = atoi(argv[i]);
        else if (i == 4) backtrace_str = argv[i];
     }
   fprintf(stderr, "exit_gdb: %i\n", exit_gdb);

   s = getenv("E_TAINTED");
   if      (s && !strcmp(s, "NO")) tainted = EINA_FALSE;
   else if (s && !strcmp(s, "YES")) tainted = EINA_TRUE;

   ecore_app_no_system_modules();
   elm_init(argc, argv);

   if (setup_display())
     {
        s = getenv("E_ALERT_SYSTEM_BIN");
        if (s && s[0])
          {
             putenv("E_ALERT_BACKLIGHT_RESET=1");
             ecore_exe_pipe_run
               (s, ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_WRITE |
                ECORE_EXE_NOT_LEADER | ECORE_EXE_TERM_WITH_PARENT, NULL);
          }

        elm_run();
     }

   return ret;
}
#include "e.h"

/* Atoms */
#ifndef HAVE_WAYLAND_ONLY
E_API Ecore_X_Atom E_ATOM_MANAGED = 0;
E_API Ecore_X_Atom E_ATOM_ZONE = 0;
E_API Ecore_X_Atom E_ATOM_DESK = 0;
E_API Ecore_X_Atom E_ATOM_MAPPED = 0;
E_API Ecore_X_Atom E_ATOM_SHADE_DIRECTION = 0;
E_API Ecore_X_Atom E_ATOM_HIDDEN = 0;
E_API Ecore_X_Atom E_ATOM_BORDER_SIZE = 0;
E_API Ecore_X_Atom E_ATOM_WINDOW_STATE = 0;
E_API Ecore_X_Atom E_ATOM_WINDOW_STATE_CENTERED = 0;
E_API Ecore_X_Atom E_ATOM_DESKTOP_FILE = 0;
E_API Ecore_X_Atom E_ATOM_ZONE_GEOMETRY = 0;
E_API Ecore_X_Atom WL_SURFACE_ID = 0;
E_API Ecore_X_Atom E_ATOM_E_WAS_HERE = 0;
#endif

/* externally accessible functions */
EINTERN int
e_atoms_init(void)
{
#ifndef HAVE_WAYLAND_ONLY
   const char *atom_names[] = {
      "__E_WINDOW_MANAGED",
      "__E_WINDOW_ZONE",
      "__E_WINDOW_DESK",
      "__E_WINDOW_MAPPED",
      "__E_WINDOW_SHADE_DIRECTION",
      "__E_WINDOW_HIDDEN",
      "__E_WINDOW_BORDER_SIZE",
      "__E_ATOM_WINDOW_STATE",
      "__E_ATOM_WINDOW_STATE_CENTERED",
      "__E_ATOM_DESKTOP_FILE",
      "E_ZONE_GEOMETRY",
      "WL_SURFACE_ID",
      "__E_ATOM_E_WAS_HERE",
   };
   Ecore_X_Atom atoms[EINA_C_ARRAY_LENGTH(atom_names)];

   ecore_x_atoms_get(atom_names, EINA_C_ARRAY_LENGTH(atom_names), atoms);
   E_ATOM_MANAGED = atoms[0];
   E_ATOM_ZONE = atoms[1];
   E_ATOM_DESK = atoms[2];
   E_ATOM_MAPPED = atoms[3];
   E_ATOM_SHADE_DIRECTION = atoms[4];
   E_ATOM_HIDDEN = atoms[5];
   E_ATOM_BORDER_SIZE = atoms[6];
   E_ATOM_WINDOW_STATE = atoms[7];
   E_ATOM_WINDOW_STATE_CENTERED = atoms[8];
   E_ATOM_DESKTOP_FILE = atoms[9];
   E_ATOM_ZONE_GEOMETRY = atoms[10];
   WL_SURFACE_ID = atoms[11];
   E_ATOM_E_WAS_HERE = atoms[12];
#endif
   return 1;
}

EINTERN int
e_atoms_shutdown(void)
{
   /* Nothing really to do here yet, just present for consistency right now */
   return 1;
}

#include "e.h"

E_API int E_EVENT_AUTH_FPRINT_AVAILABLE = 0;
E_API int E_EVENT_AUTH_FPRINT_STATUS = 0;

static Eldbus_Connection *conn = NULL;

/////////////////////////////////////////////////////////////////////////////

EINTERN int
e_auth_init(void)
{
   E_EVENT_AUTH_FPRINT_AVAILABLE = ecore_event_type_new();
   E_EVENT_AUTH_FPRINT_STATUS = ecore_event_type_new();

   conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
   return 1;
}

EINTERN int
e_auth_shutdown(void)
{
   if (conn) eldbus_connection_unref(conn);
   conn = NULL;
   return 1;
}

/////////////////////////////////////////////////////////////////////////////

E_API int
e_auth_begin(char *passwd)
{
   char buf[PATH_MAX];
   Ecore_Exe *exe = NULL;
   int ret = 0;
   size_t pwlen, buflen = 0;

   pwlen = strlen(passwd);

   snprintf(buf, sizeof(buf),
            "%s/enlightenment/utils/enlightenment_ckpasswd pw",
            e_prefix_lib_get());
   exe = ecore_exe_pipe_run(buf, ECORE_EXE_PIPE_WRITE, NULL);
   if (!exe) goto out;
   snprintf(buf, sizeof(buf), "pw %s", passwd);
   buflen = strlen(buf);
   if (ecore_exe_send(exe, buf, buflen) != EINA_TRUE) goto out;
   ecore_exe_close_stdin(exe);

   ret = ecore_exe_pid_get(exe);
   if (ret == -1)
     {
        ret = 0;
        goto out;
     }

   exe = NULL;

out:
   if (exe) ecore_exe_free(exe);

   e_util_memclear(passwd, pwlen);
   e_util_memclear(buf, buflen);
   return ret;
}

E_API int
e_auth_polkit_begin(char *passwd, const char *cookie, unsigned int uid)
{
   char buf[PATH_MAX];
   Ecore_Exe *exe = NULL;
   int ret = 0;
   size_t pwlen, buflen = 0;

   pwlen = strlen(passwd);

   snprintf(buf, sizeof(buf),
            "%s/enlightenment/utils/enlightenment_ckpasswd pk",
            e_prefix_lib_get());
   exe = ecore_exe_pipe_run(buf, ECORE_EXE_PIPE_WRITE, NULL);
   if (!exe) goto out;
   snprintf(buf, sizeof(buf), "%s %u %s", cookie, uid, passwd);
   buflen = strlen(buf);
   if (ecore_exe_send(exe, buf, buflen) != EINA_TRUE) goto out;
   ecore_exe_close_stdin(exe);

   ret = ecore_exe_pid_get(exe);
   if (ret == -1)
     {
        ret = 0;
        goto out;
     }
   exe = NULL;

out:
   if (exe) ecore_exe_free(exe);

   e_util_memclear(passwd, pwlen);
   e_util_memclear(buf, buflen);
   return ret;
}

/////////////////////////////////////////////////////////////////////////////

static Eldbus_Object *obj_fprint = NULL;
static Eldbus_Proxy *proxy_fprint = NULL;

static Eldbus_Object *obj_fprint_device = NULL;
static Eldbus_Proxy *proxy_fprint_device = NULL;
static E_Auth_Fprint_Type finger_type = E_AUTH_FPRINT_TYPE_UNKNOWN;
static const char *finger_name = NULL;
static const char *user_name = NULL;

static void
_cb_event_free(void *data EINA_UNUSED, void *event)
{
   free(event);
}

static void
_cb_verify_start(void *data EINA_UNUSED, const Eldbus_Message *m,
                 Eldbus_Pending *p EINA_UNUSED)
{
   const char *name = NULL, *text = NULL;

   printf("FP: verify start...\n");
   if (eldbus_message_error_get(m, &name, &text))
     {
        fprintf(stderr, "FP: Fprint err: %s %s\n", name, text);
        return;
     }
}

static void
_cb_verify_stop(void *data EINA_UNUSED, const Eldbus_Message *m EINA_UNUSED,
                Eldbus_Pending *p EINA_UNUSED)
{
   Eldbus_Message *m2;
   Eldbus_Message_Iter *iter;

   printf("FP: verify stop.... finger_name=%s\n", finger_name ? finger_name : "NULL");
   if (!finger_name) finger_name = eina_stringshare_add("right-index-finger");
   m2 = eldbus_proxy_method_call_new(proxy_fprint_device, "VerifyStart");
   if (m2)
     {
        iter = eldbus_message_iter_get(m2);
        eldbus_message_iter_basic_append(iter, 's', "any");
        eldbus_proxy_send(proxy_fprint_device, m2, _cb_verify_start, NULL, -1);
     }
}

static void
_verify_begin(void)
{
   Eldbus_Message *m2;

   printf("FP: verify begin...\n");
   m2 = eldbus_proxy_method_call_new(proxy_fprint_device, "VerifyStop");
   if (m2)
     {
        eldbus_proxy_send(proxy_fprint_device, m2, _cb_verify_stop, NULL, -1);
     }
}

static void
_cb_verify(void *data EINA_UNUSED, const Eldbus_Message *m)
{
   Eina_Bool val = EINA_FALSE;
   const char *txt = NULL;
   const char *name = NULL, *text = NULL;
   E_Event_Auth_Fprint_Status *ev;

   printf("FP: verify ...\n");
   if (eldbus_message_error_get(m, &name, &text))
     {
        fprintf(stderr, "FP: Fprint err: %s %s\n", name, text);
        return;
     }
   if (!eldbus_message_arguments_get(m, "sb", &txt, &val)) return;
   printf("FP:   verify = [%s] %i\n", txt, val);
   if (!txt) return;

   ev = calloc(1, sizeof(E_Event_Auth_Fprint_Status));
   if (!ev) return;

   if      (!strcmp(txt, "verify-match")) ev->status = E_AUTH_FPRINT_STATUS_AUTH;
   else if (!strcmp(txt, "verify-no-match")) ev->status = E_AUTH_FPRINT_STATUS_NO_AUTH;
   else if (!strcmp(txt, "verify-swipe-too-short")) ev->status = E_AUTH_FPRINT_STATUS_SHORT_SWIPE;
   else if (!strcmp(txt, "verify-finger-not-centered")) ev->status = E_AUTH_FPRINT_STATUS_NO_CENTER;
   else if (!strcmp(txt, "verify-remove-and-retry")) ev->status = E_AUTH_FPRINT_STATUS_REMOVE_RETRY;
   else if (!strcmp(txt, "verify-retry-scan")) ev->status = E_AUTH_FPRINT_STATUS_RETRY;
   else if (!strcmp(txt, "verify-disconnected")) ev->status = E_AUTH_FPRINT_STATUS_DISCONNECT;
   else if (!strcmp(txt, "verify-unknown-error")) ev->status = E_AUTH_FPRINT_STATUS_ERROR;
   else ev->status = E_AUTH_FPRINT_STATUS_ERROR;
   ecore_event_add(E_EVENT_AUTH_FPRINT_STATUS, ev, _cb_event_free, NULL);
   // reset the verify and try again if verify end and NOT an auth ok
   if ((val) && // end of verify
       (ev->status != E_AUTH_FPRINT_STATUS_AUTH))
     {
        _verify_begin();
     }
   if (ev->status == E_AUTH_FPRINT_STATUS_AUTH)
     {
        Eldbus_Message *m2;

        m2 = eldbus_proxy_method_call_new(proxy_fprint_device, "Release");
        eldbus_proxy_send(proxy_fprint_device, m2, NULL, NULL, -1);
     }
}

static void
_cb_list_enrolled_fingers(void *data EINA_UNUSED, const Eldbus_Message *m,
                          Eldbus_Pending *p EINA_UNUSED)
{
   Eldbus_Message_Iter *array = NULL;
   const char *name = NULL, *text = NULL;

   printf("FP: list fingers...\n");
   if (eldbus_message_error_get(m, &name, &text))
     {
        fprintf(stderr, "FP: Fprint err: %s %s\n", name, text);
        return;
     }
   printf("FP: list fingers...\n");
   if (eldbus_message_arguments_get(m, "as", &array))
     {
        char *txt = NULL;

        printf("FP:  ...\n");
        while (eldbus_message_iter_get_and_next(array, 's', &txt))
          {
             eina_stringshare_replace(&finger_name, txt);
             printf("FP:  first finger is [%s]\n", txt);
             if (finger_type != E_AUTH_FPRINT_TYPE_UNKNOWN)
               {
                  E_Event_Auth_Fprint_Available *ev = calloc(1, sizeof(E_Event_Auth_Fprint_Available));
                  if (ev)
                    {
                       ev->type = finger_type;
                       ecore_event_add(E_EVENT_AUTH_FPRINT_AVAILABLE, ev,
                                       _cb_event_free, NULL);
                    }
               }
             _verify_begin();
             break;
          }
        // call VeriftyStart 'first finger found'
     }
}

static void
_cb_claim(void *data EINA_UNUSED, const Eldbus_Message *m EINA_UNUSED,
          Eldbus_Pending *p EINA_UNUSED)
{
   Eldbus_Proxy *proxy = proxy_fprint_device;
   Eldbus_Message *m2;
   Eldbus_Message_Iter *iter;
   const char *name = NULL, *text = NULL;

   printf("FP: claim\n");
   if (eldbus_message_error_get(m, &name, &text))
     {
        fprintf(stderr, "FP: Fprint err: %s %s\n", name, text);
        return;
     }
   // ListEnrolledFingrs '$USER' -> "as"
   printf("FP: claim ok\n");
   m2 = eldbus_proxy_method_call_new(proxy, "ListEnrolledFingers");
   iter = eldbus_message_iter_get(m2);
   eldbus_message_iter_basic_append(iter, 's', user_name);
   eldbus_proxy_send(proxy, m2, _cb_list_enrolled_fingers, NULL, -1);
}

static void
_cb_prop_entry(void *data EINA_UNUSED, const void *key, Eldbus_Message_Iter *var)
{
   const char *skey = key;

   if (!strcmp(skey, "scan-type"))
     {
        const char *val = NULL;
        if (eldbus_message_iter_arguments_get(var, "s", &val))
          {
             if      (!strcmp(val, "press")) finger_type = E_AUTH_FPRINT_TYPE_PRESS;
             else if (!strcmp(val, "swipe")) finger_type = E_AUTH_FPRINT_TYPE_SWIPE;
             printf("FP: type = %s\n", val);
             if (finger_name)
               {
                  E_Event_Auth_Fprint_Available *ev =
                    calloc(1, sizeof(E_Event_Auth_Fprint_Available));
                  if (ev)
                    {
                       ev->type = finger_type;
                       ecore_event_add(E_EVENT_AUTH_FPRINT_AVAILABLE, ev,
                                       _cb_event_free, NULL);
                    }
               }
          }
     }
}

static void
_cb_props(void *data EINA_UNUSED, const Eldbus_Message *m,
          Eldbus_Pending *pending EINA_UNUSED)
{
   Eldbus_Message_Iter *array = NULL;

   if (eldbus_message_error_get(m, NULL, NULL)) return;

   if (eldbus_message_arguments_get(m, "a{sv}", &array))
     eldbus_message_iter_dict_iterate(array, "sv", _cb_prop_entry, NULL);
}

static void
_cb_get_default_device(void *data EINA_UNUSED, const Eldbus_Message *m,
                       Eldbus_Pending *p EINA_UNUSED)
{
   Eldbus_Object *obj;
   Eldbus_Proxy *proxy;
   const char *dev = NULL;
   const char *name = NULL, *text = NULL;

   printf("FP: get default device...\n");
   if (eldbus_message_error_get(m, &name, &text))
     {
        fprintf(stderr, "FP: Fprint err: %s %s\n", name, text);
        return;
     }
   if (!eldbus_message_arguments_get(m, "o", &dev)) return;
   printf("FP: dev = %s\n", dev);
   obj = eldbus_object_get(conn, "net.reactivated.Fprint", dev);
   if (obj)
     {
        obj_fprint_device = obj;

        printf("FP: 22 obj = %p\n", obj);
        proxy = eldbus_proxy_get(obj, "net.reactivated.Fprint.Device");
        if (proxy)
          {
             Eldbus_Message *m2;
             Eldbus_Message_Iter *iter;

             printf("FP: 22 proxy = %p\n", proxy);
             proxy_fprint_device = proxy;

             eldbus_proxy_property_get_all(proxy, _cb_props, NULL);

             eldbus_proxy_signal_handler_add(proxy, "VerifyStatus",
                                             _cb_verify, NULL);
             // Claim '$USER'
             m2 = eldbus_proxy_method_call_new(proxy, "Claim");
             iter = eldbus_message_iter_get(m2);
             eldbus_message_iter_basic_append(iter, 's', user_name);
             eldbus_proxy_send(proxy, m2, _cb_claim, NULL, -1);
          }
     }
}

E_API void
e_auth_fprint_begin(const char *user)
{
   Eldbus_Object *obj;
   Eldbus_Proxy *proxy;

   printf("FP: e_auth_fprint_begin\n");
   if (conn)
     {
        eina_stringshare_replace(&user_name, user);
        obj = eldbus_object_get(conn, "net.reactivated.Fprint",
                                "/net/reactivated/Fprint/Manager");
        printf("FP: obj=%p\n", obj);
        if (obj)
          {
             obj_fprint = obj;

             proxy = eldbus_proxy_get(obj, "net.reactivated.Fprint.Manager");
             printf("FP: proxy=%p\n", proxy);
             if (proxy)
               {
                  Eldbus_Message *m;

                  proxy_fprint = proxy;
                  m = eldbus_proxy_method_call_new(proxy, "GetDefaultDevice");
                  eldbus_proxy_send(proxy, m, _cb_get_default_device, NULL, -1);
               }
          }
     }
}

E_API void
e_auth_fprint_end(void)
{
   if (conn)
     {
        if (obj_fprint_device)
          {
             Eldbus_Message *m2;

             m2 = eldbus_proxy_method_call_new(proxy_fprint_device, "Release");
             eldbus_proxy_send(proxy_fprint_device, m2, NULL, NULL, -1);
             if (proxy_fprint_device)
               {
                  eldbus_proxy_unref(proxy_fprint_device);
                  proxy_fprint_device = NULL;
               }
             eldbus_object_unref(obj_fprint_device);
             obj_fprint_device = NULL;
          }
        if (obj_fprint)
          {
             if (proxy_fprint)
               {
                  eldbus_proxy_unref(proxy_fprint);
                  proxy_fprint = NULL;
               }
             eldbus_object_unref(obj_fprint);
             obj_fprint = NULL;
          }
     }
   eina_stringshare_replace(&finger_name, NULL);
   eina_stringshare_replace(&user_name, NULL);
   finger_type = E_AUTH_FPRINT_TYPE_UNKNOWN;
}
#include "e.h"

typedef struct
{
   const char *dev;
   const char *output;
   const char *edid;
   double val, expected_val;
   double from_val, to_val;
   Ecore_Animator *anim;
   Ecore_Timer *retry_timer;
   int ddc_max;
   int retries;
} Backlight_Device;

E_API int E_EVENT_BACKLIGHT_CHANGE = -1;

static Eina_List *bl_devs = NULL;
static Eina_List *handlers = NULL;
static Ecore_Job *zone_change_job = NULL;
static Eina_List *_devices = NULL;
static int        _devices_pending_ops = 0;
static Eina_Bool  _devices_zones_update = EINA_FALSE;
static Eina_Bool  _own_vt = EINA_TRUE;
static Eina_Bool  _e_bl_suspend = EINA_FALSE;

static void _backlight_devices_device_set(Backlight_Device *bd, double val);
static void _backlight_devices_device_update(Backlight_Device *bd);

static Eina_Bool
_backlight_retry_timer_cb(void *data)
{
   Backlight_Device *bd = data;

   bd->retry_timer = NULL;
   _backlight_devices_device_set(bd, bd->expected_val);
   _backlight_devices_device_update(bd);
   return EINA_FALSE;
}

static void
_backlight_mismatch_retry(Backlight_Device *bd)
{
   if (// if want 1.0 or normal or dim
       ((fabs(bd->expected_val - 1.0) < DBL_EPSILON) ||
        (fabs(bd->expected_val - e_config->backlight.normal) < DBL_EPSILON) ||
        (fabs(bd->expected_val - e_config->backlight.dim) < DBL_EPSILON)) &&
       // and the delta between expected and val >= 0.05
       (fabs(bd->expected_val - bd->val) >= 0.05) &&
       // and we retried < 20 times
       (bd->retries < 10) &&
       (_own_vt))
     { // try again
        printf("RETRY backlight set as %1.2f != %1.2f (expected) try=%i\n",
               bd->val, bd->expected_val, bd->retries);
        bd->retries++;
        if (bd->retry_timer) ecore_timer_del(bd->retry_timer);
        bd->retry_timer = ecore_timer_add(0.1, _backlight_retry_timer_cb, bd);
     } // or give up
   else bd->retries = 0;
}

static void
_backlight_system_get_cb(void *data, const char *params)
{
   char dev[1024];
   int val = 0;
   double fval;
   Backlight_Device *bd = data;

   if (!params) return;
   if (sscanf(params, "%1023s %i", dev, &val) != 2) return;
   if (!!strcmp(bd->dev, dev)) return;
   e_system_handler_del("bklight-val", _backlight_system_get_cb, bd);
   fval = (double)val / 1000.0;
   if (fabs(fval - bd->val) >= DBL_EPSILON)
     {
        bd->val = fval;
        ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
        _backlight_mismatch_retry(bd);
     }
}

static void
_backlight_system_ddc_get_cb(void *data, const char *params)
{
   char edid[257];
   int id = -1, val = -1, max = -1;
   double fval;
   Backlight_Device *bd = data;

   if (!params) return;
   if (sscanf(params, "%256s %i %i %i", edid, &id, &val, &max) != 4) return;
   if (!bd->edid) return;
   if (!!strncmp(bd->edid, edid, strlen(edid))) return;
   e_system_handler_del("ddc-val-get", _backlight_system_ddc_get_cb, bd);
   if (val < 0) fval = -1.0;
   else fval = (double)val / 100.0;
   bd->ddc_max = max;
   if ((fabs(fval - bd->val) >= DBL_EPSILON) || (val == -1))
     {
        bd->val = fval;
        ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
        _backlight_mismatch_retry(bd);
     }
}

static void
_backlight_devices_clear(void)
{
   const char *s;
   Backlight_Device *bd;

   EINA_LIST_FREE(bl_devs, s)
     eina_stringshare_del(s);
   EINA_LIST_FREE(_devices, bd)
     {
        eina_stringshare_del(bd->dev);
        eina_stringshare_del(bd->output);
        eina_stringshare_del(bd->edid);
        if (bd->anim) ecore_animator_del(bd->anim);
        if (bd->retry_timer) ecore_timer_del(bd->retry_timer);
        e_system_handler_del("bklight-val", _backlight_system_get_cb, bd);
        e_system_handler_del("ddc-val-get", _backlight_system_ddc_get_cb, bd);
        free(bd);
     }
}

static Backlight_Device *
_backlight_devices_zone_device_find(E_Zone *zone)
{
   Eina_List *l;
   Backlight_Device *bd;
   char *tmp, *sep;
   const char *out, *edid, *id;

   id = zone->randr2_id;
   if (!id) id = "/";
   tmp = strdup(id);
   if (!tmp) return NULL;
   sep = strchr(tmp, '/');
   if (!sep)
     {
        free(tmp);
        return NULL;
     }
   *sep = '\0';
   out = tmp;
   edid = sep + 1;
   EINA_LIST_FOREACH(_devices, l, bd)
     {
        if ((bd->output) && (!strcmp(out, bd->output)))
          {
             if ((edid[0] && (!strcmp(edid, bd->edid))) || (!edid[0]))
               {
                  free(tmp);
                  return bd;
               }
          }
     }
   free(tmp);
   return NULL;
}

#ifndef HAVE_WAYLAND_ONLY
static const char *
_backlight_devices_randr_out_edid_str_get(Ecore_X_Window root, Ecore_X_Randr_Output *out)
{
   const char *str = NULL;
   unsigned long edid_len = 0;
   unsigned char *edid = ecore_x_randr_output_edid_get(root, *out, &edid_len);
   if (edid)
     {
        char *edid_str = malloc((edid_len * 2) + 1);

        if (edid_str)
          {
             const char *hexch = "0123456789abcdef";
             char *dc = edid_str;
             unsigned long i;

             for (i = 0; i < edid_len; i++)
               {
                  dc[0] = hexch[(edid[i] >> 4) & 0xf];
                  dc[1] = hexch[(edid[i]     ) & 0xf];
                  dc += 2;
               }
             *dc = 0;
             str = eina_stringshare_add(edid_str);
             free(edid_str);
          }
        free(edid);
     }
   return str;
}

static Ecore_X_Randr_Output
_backlight_devices_randr_output_get(Ecore_X_Window root, const char *output, const char *edid)
{
   Ecore_X_Randr_Output *out;
   int i, num = 0;

   out = ecore_x_randr_window_outputs_get(root, &num);
   if ((out) && (num > 0))
     {
        for (i = 0; i < num; i++)
          {
             char *name = ecore_x_randr_output_name_get(root, out[i], NULL);
             if (!name) continue;
             const char *edid_str = _backlight_devices_randr_out_edid_str_get(root, &(out[i]));
             if (edid_str)
               {
                  if ((!strcmp(output, name)) && (!strcmp(edid, edid_str)))
                    {
                       eina_stringshare_del(edid_str);
                       free(name);
                       return out[i];
                    }
                  eina_stringshare_del(edid_str);
               }
             free(name);
          }
     }
   return 0;
}
#endif

static void
_backlight_devices_device_set(Backlight_Device *bd, double val)
{
   if (fabs(bd->expected_val - val) > DBL_EPSILON) bd->retries = 0;
   bd->val = bd->expected_val = val;
   if (!_own_vt) return;
#ifndef HAVE_WAYLAND_ONLY
   if (!strcmp(bd->dev, "randr"))
     {
        if ((e_comp) && (e_comp->comp_type == E_PIXMAP_TYPE_X) && (e_comp->root))
          {
             Ecore_X_Randr_Output o = _backlight_devices_randr_output_get(e_comp->root, bd->output, bd->edid);
             if (o)
               {
//                  fprintf(stderr, "BL: randr bklight %1.3f @ %1.3f\n", bd->val, ecore_time_get());
                  ecore_x_randr_output_backlight_level_set(e_comp->root, o, bd->val);
                  ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
               }
          }
     }
   else
#endif
   if (!strncmp(bd->dev, "ddc:", 4))
     {
        double fval;

//        fprintf(stderr, "BL: ddc bklight %1.3f @ %1.3f\n", bd->val, ecore_time_get());
        if (bd->ddc_max) fval = bd->val * (double)bd->ddc_max;
        else fval = bd->val * 100.0;
        if (e_config->backlight.ddc)
          e_system_send("ddc-val-set", "%s %i %i", bd->dev + 4, 0x10, (int)(fval)); // backlight val in e_system_ddc.c
        ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
     }
   else
     {
//        fprintf(stderr, "BL: internal bklight %1.3f @ %1.3f\n", bd->val, ecore_time_get());
        e_system_send("bklight-set", "%s %i", bd->dev, (int)(bd->val * 1000.0));
        ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
     }
}

static E_Zone *
_backlight_devices_device_zone_get(Backlight_Device *bd)
{
   char buf[1024];
   Eina_List *l;
   E_Zone *zone;

   snprintf(buf, sizeof(buf), "%s/%s", bd->output? bd->output: "", bd->edid ? bd->edid : "");
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        const char *id = zone->randr2_id;
        if (!id)
          {
             const char *id2 = bd->edid;
             if (!id2) id2 = "";
             id = "";
             if (!strcmp(id, id2)) return zone;
          }
        else
          {
             if (!strcmp(id, buf)) return zone;
          }
     }
   return NULL;
}

static void
_backlight_devices_device_update(Backlight_Device *bd)
{
   if (!bd->dev) return;
#ifndef HAVE_WAYLAND_ONLY
   if (!strcmp(bd->dev, "randr"))
     {
        if ((e_comp) && (e_comp->comp_type == E_PIXMAP_TYPE_X) && (e_comp->root))
          {
             Ecore_X_Randr_Output o = _backlight_devices_randr_output_get(e_comp->root, bd->output, bd->edid);
             if (o)
               {
                  double x_bl = ecore_x_randr_output_backlight_level_get(e_comp->root, o);
                  if ((x_bl >= 0.0) && (fabs(x_bl - bd->val) >= DBL_EPSILON))
                    {
                       bd->val = x_bl;
                       E_Zone *zone = _backlight_devices_device_zone_get(bd);
                       if (zone) zone->bl = bd->val;
                       ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
                    }
               }
          }
     }
   else
#endif
   if (!strncmp(bd->dev, "ddc:", 4))
     {
        e_system_handler_del("ddc-val-get", _backlight_system_ddc_get_cb, bd);
        e_system_handler_add("ddc-val-get", _backlight_system_ddc_get_cb, bd);
        e_system_send("ddc-val-get", "%s %i", bd->dev + 4, 0x10); // backlight val in e_system_ddc.c
     }
   else
     {
        e_system_handler_del("bklight-val", _backlight_system_get_cb, bd);
        e_system_handler_add("bklight-val", _backlight_system_get_cb, bd);
        e_system_send("bklight-get", "%s", bd->dev);
     }
}

static void
_backlight_devices_update(void)
{
   Eina_List *l;
   Backlight_Device *bd;

   EINA_LIST_FOREACH(_devices, l, bd)
     _backlight_devices_device_update(bd);
}

static void
_backlight_devices_pending_done(void)
{
   _devices_pending_ops--;
   if (_devices_pending_ops > 0) return;
   e_backlight_level_set(NULL, e_config->backlight.normal, -1.0);
   _backlight_devices_update();
}

static E_Randr2_Screen *
_backlight_devices_screen_lid_get(void)
{
   Eina_List *l;
   E_Randr2_Screen *sc;

   if (!e_randr2) return NULL;
   EINA_LIST_FOREACH(e_randr2->screens, l, sc)
     {
        if (sc->info.is_lid) return sc;
     }
   EINA_LIST_FOREACH(e_randr2->screens, l, sc)
     {
        if (sc->info.backlight) return sc;
     }
   return NULL;
}

static E_Randr2_Screen *
_backlight_devices_screen_edid_get(const char *edid)
{
   Eina_List *l;
   E_Randr2_Screen *sc;
   const char *id;

   if (!e_randr2) return NULL;
   EINA_LIST_FOREACH(e_randr2->screens, l, sc)
     {
        id = sc->info.edid;
        if (!id) id = "";
        if (!strncmp(id, edid, strlen(edid))) return sc;
     }
   return NULL;
}

static Backlight_Device *
_backlight_devices_edid_find(const char *edid)
{
   Eina_List *l;
   Backlight_Device *bd;

   EINA_LIST_FOREACH(_devices, l, bd)
     {
        if ((bd->edid) && (edid) && (!strcmp(edid, bd->edid)))
          return bd;
     }
   return NULL;
}

static void
_backlight_devices_lid_register(const char *dev, Eina_Bool force)
{
   E_Randr2_Screen *sc = _backlight_devices_screen_lid_get();
   Backlight_Device *bd = NULL;
   const char *id;

   if (!sc) return;
   id = sc->info.edid;
   if (!id) id = "";
   bd = _backlight_devices_edid_find(id);
   if (!bd)
     {
        E_Zone *zone;

        bd = calloc(1, sizeof(Backlight_Device));
        if (!bd) return;
        bd->edid = eina_stringshare_add(id);
        bd->output = eina_stringshare_add(sc->info.name);
        _devices = eina_list_append(_devices, bd);
        zone = _backlight_devices_device_zone_get(bd);
        if (zone)
          {
             double bl = zone->bl;

             zone->bl = 0.0;
             e_backlight_level_set(zone, bl, -1.0);
          }
     }
   if (bd->dev)
     {
        if (!strcmp(bd->dev, "randr")) return; // randr devices win
        if (!strncmp(bd->dev, "ddc:", 4)) return; // ddc devices also beat these "sys" devices
     }
   // if bl device has a device assigned and we're not forcing a change
   // then skip assigning this device to the backlight device
   if ((bd->dev) && (!force)) return;
   eina_stringshare_replace(&(bd->dev), dev);
}

static void
_backlight_devices_edid_register(const char *dev, const char *edid)
{
   E_Randr2_Screen *sc = _backlight_devices_screen_edid_get(edid);
   Backlight_Device *bd;
   if (!sc) return;
   bd = _backlight_devices_edid_find(sc->info.edid);
   if (!bd)
     {
        E_Zone *zone;
        const char *id;

        bd = calloc(1, sizeof(Backlight_Device));
        if (!bd) return;
        id = sc->info.edid;
        if (!id) id = "";
        bd->edid = eina_stringshare_add(id);
        bd->output = eina_stringshare_add(sc->info.name);
        _devices = eina_list_append(_devices, bd);
        zone = _backlight_devices_device_zone_get(bd);
        if (zone)
          {
             double bl = zone->bl;

             zone->bl = 0.0;
             e_backlight_level_set(zone, bl, -1.0);
          }
     }
   if (bd->dev)
     {
        if (!strcmp(bd->dev, "randr")) return; // randr devices win
     }
   eina_stringshare_replace(&(bd->dev), dev);
}

static void
_backlight_system_list_cb(void *data EINA_UNUSED, const char *params)
{
   // params "dev flag dev flag ..."
   const char *p = params;
   char dev[1024], flag, devnum = 0;

   e_system_handler_del("bklight-list", _backlight_system_list_cb, NULL);
   while ((p) && (*p))
     {
        if (sscanf(p, "%1023s", dev) == 1)
          {
             p += strlen(dev);
             if (*p == ' ')
               {
                  p++;
                  flag = *p;
                  if (flag)
                    {
                       bl_devs = eina_list_append
                         (bl_devs, eina_stringshare_add(dev));
                       if ((devnum == 0) || (flag == 'p') ||
                           (strstr(dev, "/drm/")) ||
                           (strstr(dev, ".video.")))
                         {
                            _backlight_devices_lid_register(dev, EINA_TRUE);
                         }
                       else
                         {
                            // XXX: what to do with other bl devices?
                            // for now just assign spare bl devices to screens that do not have one already
                            _backlight_devices_lid_register(dev, EINA_FALSE);
                         }
                       devnum++;
                       // XXX: find bed bl dev for screens not supporting randr
                       // note that this is a vallback and ddc should take precedence
                       // it matched up to a screen over this, but randr should be
                       // the top priority if found
                    }
                  else break;
                  p++;
                  if (*p != ' ') break;
                  p++;
                  if (*p == '\0') break;
               }
             else break;
          }
        else break;
     }
   _backlight_devices_pending_done();
}

static void
_backlight_system_ddc_list_cb(void *data EINA_UNUSED, const char *params)
{
   const char *p = params;
   char dev[257], buf[343];
   Eina_Hash *tmphash;

   tmphash = eina_hash_string_superfast_new(NULL);
   e_system_handler_del("ddc-list", _backlight_system_ddc_list_cb, NULL);
   while ((p) && (*p))
     {
        if (sscanf(p, "%256s", dev) == 1)
          {
             p += strlen(dev);
             snprintf(buf, sizeof(buf), "ddc:%s", dev);
             bl_devs = eina_list_append
               (bl_devs, eina_stringshare_add(buf));
             _backlight_devices_edid_register(buf, dev);
             if (eina_hash_find(tmphash, dev))
               printf("BL: DDC ERROR: You have multiple DDC screens with the same EDID [%s] - this will lead to weirdness.\n", dev);
             eina_hash_add(tmphash, dev, dev);
             if (*p != ' ') break;
          }
        else break;
     }
   eina_hash_free(tmphash);
   _backlight_devices_pending_done();
}

static void
_backlight_devices_probe(Eina_Bool initial)
{
   _backlight_devices_clear();
#ifndef HAVE_WAYLAND_ONLY
   // pass 1: if in x look at all the randr outputs to see if they have some backlight
   // property to swizzle then make the randr device the kind of device to use -
   // record this in our devices list
   if ((e_comp) && (e_comp->comp_type == E_PIXMAP_TYPE_X))
     {
        Eina_Bool avail = ecore_x_randr_output_backlight_available();
        if ((avail) && (e_comp->root))
          {
             double x_bl;
             Ecore_X_Window root = e_comp->root;
             Ecore_X_Randr_Output *out;
             int i, num = 0;
             Eina_Bool found = EINA_FALSE;

             bl_devs = eina_list_append(bl_devs, eina_stringshare_add("randr"));
             out = ecore_x_randr_window_outputs_get(root, &num);
             if ((out) && (num > 0))
               {
                  for (i = 0; i < num; i++)
                    {
                       char *name = ecore_x_randr_output_name_get(root, out[i], NULL);
                       if (!name) continue;
                       x_bl = ecore_x_randr_output_backlight_level_get(root, out[0]);
                       if (x_bl >= 0.0)
                         {
                            Backlight_Device *bd = calloc(1, sizeof(Backlight_Device));
                            if (bd)
                              {
                                 const char *edid_str = _backlight_devices_randr_out_edid_str_get(root, &(out[i]));

                                 if (edid_str)
                                   {
                                      bd->dev = eina_stringshare_add("randr");
                                      bd->output = eina_stringshare_add(name);
                                      bd->edid = edid_str;
                                      _devices = eina_list_append(_devices, bd);
                                      found = EINA_TRUE;
                                   }
                                 else free(bd);
                              }
                         }
                       free(name);
                    }
               }
             free(out);
             if (found)
               e_backlight_level_set(NULL, e_config->backlight.normal, -1.0);
          }
     }
#endif
   // ask enlightenment_system to list backlight devices. this is async so we have
   // to respond to the device listing later
   _devices_pending_ops++;
   e_system_handler_del("bklight-list", _backlight_system_list_cb, NULL);
   e_system_handler_add("bklight-list", _backlight_system_list_cb, NULL);
   if (!initial) e_system_send("bklight-refresh", NULL);
   e_system_send("bklight-list", NULL);
   _devices_pending_ops++;
   e_system_handler_del("ddc-list", _backlight_system_ddc_list_cb, NULL);
   e_system_handler_add("ddc-list", _backlight_system_ddc_list_cb, NULL);
   if (!initial) e_system_send("ddc-refresh", NULL);
   e_system_send("ddc-list", NULL);
   // XXXX: add ddc to e_syystem and query that too
}

static Eina_Bool
_bl_anim(void *data, double pos)
{
   Backlight_Device *bd = data;

   // FIXME: if zone is deleted while anim going... bad things.
   pos = ecore_animator_pos_map(pos, ECORE_POS_MAP_DECELERATE, 0.0, 0.0);
   bd->val  = (bd->from_val * (1.0 - pos)) + (bd->to_val * pos);
   _backlight_devices_device_set(bd, bd->val);
   if (pos >= 1.0)
     {
        bd->anim = NULL;
        _backlight_devices_device_update(bd);
        return EINA_FALSE;
     }
   return EINA_TRUE;
}

static void
_cb_job_zone_change(void *data EINA_UNUSED)
{
   zone_change_job = NULL;
   _backlight_devices_probe(EINA_FALSE);
   e_backlight_update();
}

static Eina_Bool
_cb_handler_zone_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *info EINA_UNUSED)
{
   if (zone_change_job) ecore_job_del(zone_change_job);
   zone_change_job = ecore_job_add(_cb_job_zone_change, NULL);
   return EINA_TRUE;
}

#ifndef HAVE_WAYLAND_ONLY
static Eina_Bool
_cb_handler_x_window_property(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Window_Property *ev = event;

   if ((e_comp) && (e_comp->comp_type == E_PIXMAP_TYPE_X))
     {
        static Ecore_X_Atom atom_xfree86_has_vt = 0;

        if (!atom_xfree86_has_vt)
          atom_xfree86_has_vt = ecore_x_atom_get("XFree86_has_VT");
        if ((ev->win) && (e_comp->root) && (ev->win == e_comp->root) &&
            (ev->atom == atom_xfree86_has_vt))
          {
             int *val = NULL, num = 0;

             if (ecore_x_window_prop_property_get(ev->win, atom_xfree86_has_vt,
                                                  ECORE_X_ATOM_INTEGER, 32,
                                                  (unsigned char **)&val,
                                                  &num))
               {
                  if (num > 0)
                    {
                       if ((*val == 1) && (!_own_vt)) // have vt
                         {
                            printf("BL: gained VT\n");
                            _own_vt = EINA_TRUE;
                            // we just go back to normal backlight if we gain
                            // the vt again
                            e_backlight_mode_set(NULL, E_BACKLIGHT_MODE_NORMAL);
                         }
                       else if (_own_vt)
                         {
                            printf("BL: lost VT\n");
                            _own_vt = EINA_FALSE;
                         }
                    }
                  free(data);
               }
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}
#endif

EINTERN int
e_backlight_init(void)
{
   E_EVENT_BACKLIGHT_CHANGE = ecore_event_type_new();
   _backlight_devices_probe(EINA_TRUE);
#define H(ev, cb) \
   handlers = eina_list_append(handlers, \
                               ecore_event_handler_add(ev, cb, NULL));
   H(E_EVENT_ZONE_ADD, _cb_handler_zone_change);
   H(E_EVENT_ZONE_DEL, _cb_handler_zone_change);
   H(E_EVENT_ZONE_MOVE_RESIZE, _cb_handler_zone_change);
   H(E_EVENT_ZONE_STOW, _cb_handler_zone_change);
   H(E_EVENT_ZONE_UNSTOW, _cb_handler_zone_change);
#ifndef HAVE_WAYLAND_ONLY
   H(ECORE_X_EVENT_WINDOW_PROPERTY, _cb_handler_x_window_property);
#endif
   e_backlight_update();
   if (!getenv("E_RESTART"))
     e_backlight_level_set(NULL, e_config->backlight.normal, -1.0);
   return 1;
}

EINTERN int
e_backlight_shutdown(void)
{
   Ecore_Event_Handler *h;
   const char *s;

   if (zone_change_job)
     {
        ecore_job_del(zone_change_job);
        zone_change_job = NULL;
     }
   EINA_LIST_FREE(handlers, h)
     ecore_event_handler_del(h);
   EINA_LIST_FREE(bl_devs, s)
     eina_stringshare_del(s);
   _backlight_devices_clear();
   return 1;
}

E_API Eina_Bool
e_backlight_exists(void)
{
   // XXX: properly check backlight devices
   return EINA_TRUE;
}

E_API void
e_backlight_update(void)
{
   if (_devices_pending_ops > 0) _devices_zones_update = EINA_TRUE;
   else _backlight_devices_update();
}

E_API void
e_backlight_level_set(E_Zone *zone, double val, double tim)
{
   Backlight_Device *bd;
   double bl_now;

   if (!zone) // zone == NULL == everything
     {
        Eina_List *l;

        if (!e_comp) return;
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          e_backlight_level_set(zone, val, tim);
        return;
     }
   bl_now = zone->bl;
   zone->bl = val;

   bd = _backlight_devices_zone_device_find(zone);
   if (!bd) return;
   // set backlight associated with zone to val over period of tim
   // if tim == 0.0 - then do it instantnly, if time == -1 use some default
   // transition time
   if ((!e_comp->screen) || (!e_comp->screen->backlight_enabled)) return;
   if (val < 0.0) val = 0.0;
   else if (val > 1.0) val = 1.0;
   if ((fabs(val - bl_now) < DBL_EPSILON) && (!bd->anim)) return;

   if (fabs(tim) < DBL_EPSILON)
     {
        _backlight_devices_device_set(bd, val);
        _backlight_devices_device_update(bd);
        ecore_event_add(E_EVENT_BACKLIGHT_CHANGE, NULL, NULL, NULL);
        return;
     }
   if (zone->bl_mode == E_BACKLIGHT_MODE_NORMAL) tim = 0.5;
   else if (tim < 0.0) tim = e_config->backlight.transition;

   if ((bd->anim) && (fabs(bd->to_val - val) < DBL_EPSILON)) return;

   E_FREE_FUNC(bd->retry_timer, ecore_timer_del);
   E_FREE_FUNC(bd->anim, ecore_animator_del);
   bd->from_val = bl_now;
   bd->to_val = val;
   bd->anim = ecore_animator_timeline_add(tim, _bl_anim, bd);
}

E_API double
e_backlight_level_get(E_Zone *zone)
{
   Backlight_Device *bd;

   if (!zone)
     {
        zone = e_zone_current_get();
        if (!zone) return -1.0;
     }
   bd = _backlight_devices_zone_device_find(zone);
   if (!bd) return -1.0;
   zone->bl = bd->val;
   return bd->val;
}

E_API void
e_backlight_mode_set(E_Zone *zone, E_Backlight_Mode mode)
{
   E_Backlight_Mode pmode;

   if (!zone) // zone == NULL == everything
     {
        Eina_List *l;

        if (!e_comp) return;
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          e_backlight_mode_set(zone, mode);
        return;
     }
   if (zone->bl_mode == mode) return;
   if (eina_streq(ecore_evas_engine_name_get(e_comp->ee), "buffer") ||
       strstr(ecore_evas_engine_name_get(e_comp->ee), "wayland"))
     return;
   pmode = zone->bl_mode;
   zone->bl_mode = mode;
   if (zone->bl_mode == E_BACKLIGHT_MODE_NORMAL)
     {
        e_backlight_level_set(zone, e_config->backlight.normal, -1.0);
     }
   else if (zone->bl_mode == E_BACKLIGHT_MODE_OFF)
     {
        e_backlight_level_set(zone, 0.0, -1.0);
     }
   else if (zone->bl_mode == E_BACKLIGHT_MODE_DIM)
     {
        if ((pmode != E_BACKLIGHT_MODE_NORMAL) ||
            ((pmode == E_BACKLIGHT_MODE_NORMAL) &&
             (e_config->backlight.normal > e_config->backlight.dim)))
          e_backlight_level_set(zone, e_config->backlight.dim, -1.0);
     }
   else if (zone->bl_mode == E_BACKLIGHT_MODE_MAX)
     e_backlight_level_set(zone, 1.0, -1.0);
}

E_API E_Backlight_Mode
e_backlight_mode_get(E_Zone *zone)
{
   if (!zone)
     {
        zone = e_zone_current_get();
        if (!zone) return E_BACKLIGHT_MODE_NORMAL;
     }
   return zone->bl_mode;
}

E_API const Eina_List *
e_backlight_devices_get(void)
{
   return bl_devs;
}

E_API void
e_backlight_suspend_set(Eina_Bool suspend)
{
   if (_e_bl_suspend == suspend) return;
   _e_bl_suspend = suspend;
   _own_vt = !_e_bl_suspend;
   if (_own_vt)
     {
        // we just go back to normal backlight if we gain
        // the vt again
        e_backlight_mode_set(NULL, E_BACKLIGHT_MODE_NORMAL);
     }
}
#include "e.h"

/* local subsystem functions */
static void _e_bg_signal(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _e_bg_event_bg_update_free(void *data, void *event);
static void e_bg_handler_set(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path);
static int  e_bg_handler_test(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path);
static void _e_bg_handler_image_imported(const char *image_path, void *data);

/* local subsystem globals */
E_API int E_EVENT_BG_UPDATE = 0;
static E_Fm2_Mime_Handler *bg_hdl = NULL;

/* externally accessible functions */
EINTERN int
e_bg_init(void)
{
   Eina_List *l = NULL;
   E_Config_Desktop_Background *cfbg = NULL;

   /* Register mime handler */
   bg_hdl = e_fm2_mime_handler_new(_("Set As Background"),
                                   "preferences-desktop-wallpaper",
                                   e_bg_handler_set, NULL,
                                   e_bg_handler_test, NULL);
   if (bg_hdl)
     {
        e_fm2_mime_handler_glob_add(bg_hdl, "*.edj");
        e_fm2_mime_handler_mime_add(bg_hdl, "image/png");
        e_fm2_mime_handler_mime_add(bg_hdl, "image/jpeg");
     }

   /* Register files in use */
   if (e_config->desktop_default_background)
     e_filereg_register(e_config->desktop_default_background);

   EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cfbg)
     {
        if (!cfbg) continue;
        e_filereg_register(cfbg->file);
     }

   E_EVENT_BG_UPDATE = ecore_event_type_new();
   return 1;
}

EINTERN int
e_bg_shutdown(void)
{
   Eina_List *l = NULL;
   E_Config_Desktop_Background *cfbg = NULL;

   /* Deregister mime handler */
   if (bg_hdl)
     {
        e_fm2_mime_handler_glob_del(bg_hdl, "*.edj");
        e_fm2_mime_handler_free(bg_hdl);
     }

   /* Deregister files in use */
   if (e_config->desktop_default_background)
     e_filereg_deregister(e_config->desktop_default_background);

   EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cfbg)
     {
        if (!cfbg) continue;
        e_filereg_deregister(cfbg->file);
     }

   return 1;
}

/**
 * Find the configuration for a given desktop background
 * Use -1 as a wild card for each parameter.
 * The most specific match will be returned
 */
E_API const E_Config_Desktop_Background *
e_bg_config_get(int zone_num, int desk_x, int desk_y)
{
   Eina_List *l, *entries;
   E_Config_Desktop_Background *bg = NULL, *cfbg = NULL;
   const char *bgfile = "";
   char *entry;
   int current_spec = 0; /* how specific the setting is - we want the least general one that applies */

   /* look for desk specific background. */
   if (zone_num >= 0 || desk_x >= 0 || desk_y >= 0)
     {
        EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cfbg)
          {
             int spec;

             if (!cfbg) continue;
             spec = 0;
             if (cfbg->zone == zone_num) spec++;
             else if (cfbg->zone >= 0)
               continue;
             if (cfbg->desk_x == desk_x) spec++;
             else if (cfbg->desk_x >= 0)
               continue;
             if (cfbg->desk_y == desk_y) spec++;
             else if (cfbg->desk_y >= 0)
               continue;

             if (spec <= current_spec) continue;
             bgfile = cfbg->file;
             if (bgfile)
               {
                  if (bgfile[0] != '/')
                    {
                       const char *bf;

                       bf = e_path_find(path_backgrounds, bgfile);
                       if (bf) bgfile = bf;
                    }
               }
             if (eina_str_has_extension(bgfile, ".edj"))
               {
                  entries = edje_file_collection_list(bgfile);
                  EINA_LIST_FREE(entries, entry)
                    {
                       if (!strcmp(entry, "e/desktop/background"))
                         {
                            bg = cfbg;
                            current_spec = spec;
                            break;
                         }
                       eina_stringshare_del(entry);
                    }
                  E_FREE_LIST(entries, eina_stringshare_del);
               }
             else
               {
                  bg = cfbg;
                  current_spec = spec;
               }
          }
     }
   return bg;
}

E_API Eina_Stringshare *
e_bg_file_get(int zone_num, int desk_x, int desk_y)
{
   const E_Config_Desktop_Background *cfbg;
   const char *bgfile = NULL;
   int ok = 0;

   cfbg = e_bg_config_get(zone_num, desk_x, desk_y);

   /* fall back to default */
   if (cfbg)
     {
        const char *bf;

        bgfile = eina_stringshare_ref(cfbg->file);
        if (!bgfile) return NULL;
        if (bgfile[0] == '/') return bgfile;
        bf = e_path_find(path_backgrounds, bgfile);
        if (!bf) return bgfile;
        eina_stringshare_del(bgfile);
        return bf;
     }
   bgfile = e_config->desktop_default_background;
   if (bgfile)
     {
        if (bgfile[0] != '/')
          {
             const char *bf;

             bf = e_path_find(path_backgrounds, bgfile);
             if (bf) bgfile = bf;
          }
        else
          eina_stringshare_ref(bgfile);
     }
   if (bgfile && eina_str_has_extension(bgfile, ".edj"))
     {
        ok = edje_file_group_exists(bgfile, "e/desktop/background");
     }
   else if ((bgfile) && (bgfile[0]))
     ok = 1;
   if (!ok)
     eina_stringshare_replace(&bgfile, e_theme_edje_file_get("base/theme/background",
                                                             "e/desktop/background"));

   return bgfile;
}

E_API void
e_bg_zone_update(E_Zone *zone, E_Bg_Transition transition)
{
   Evas_Object *o;
   const char *bgfile = "";
   const char *trans = "";
   E_Desk *desk;

   if (transition == E_BG_TRANSITION_START) trans = e_config->transition_start;
   else if (transition == E_BG_TRANSITION_DESK)
     trans = e_config->transition_desk;
   else if (transition == E_BG_TRANSITION_CHANGE)
     trans = e_config->transition_change;
   if ((!trans) || (!trans[0])) transition = E_BG_TRANSITION_NONE;

   desk = e_desk_current_get(zone);
   if (desk)
     bgfile = e_bg_file_get(zone->num, desk->x, desk->y);
   else
     bgfile = e_bg_file_get(zone->num, -1, -1);

   if (zone->bg_object)
     {
        const char *pfile = NULL;

        edje_object_file_get(zone->bg_object, &pfile, NULL);
        if (!pfile) e_icon_file_get(zone->bg_object, &pfile, NULL);
        if (!pfile) pfile = e_video_file_get(zone->bg_object);
        if (pfile)
          {
             if (!e_util_strcmp(pfile, bgfile)) goto end;
          }
     }

   if (transition == E_BG_TRANSITION_NONE)
     E_FREE_FUNC(zone->bg_object, evas_object_del);
   else
     {
        char buf[4096];

        if (zone->bg_object)
          {
             E_FREE_FUNC(zone->prev_bg_object, evas_object_del);
             zone->prev_bg_object = zone->bg_object;
             zone->bg_object = NULL;
             E_FREE_FUNC(zone->transition_object, evas_object_del);
          }
        o = edje_object_add(e_comp->evas);
        evas_object_repeat_events_set(o, 1);
        zone->transition_object = o;
        evas_object_name_set(zone->transition_object, "zone->transition_object");
        /* FIXME: segv if zone is deleted while up??? */
        evas_object_data_set(o, "e_zone", zone);
        snprintf(buf, sizeof(buf), "e/transitions/%s", trans);
        e_theme_edje_object_set(o, "base/theme/transitions", buf);
        edje_object_preload(o, EINA_FALSE);
        edje_object_signal_callback_add(o, "e,state,done", "*", _e_bg_signal, zone);
        evas_object_move(o, zone->x, zone->y);
        evas_object_resize(o, zone->w, zone->h);
        evas_object_layer_set(o, E_LAYER_BG);
        evas_object_clip_set(o, zone->bg_clip_object);
        evas_object_show(o);
     }
   if (eina_str_has_extension(bgfile, ".edj"))
     {
        o = edje_object_add(e_comp->evas);
        edje_object_file_set(o, bgfile, "e/desktop/background");
        edje_object_preload(o, EINA_FALSE);
        if (edje_object_data_get(o, "noanimation"))
          edje_object_animation_set(o, EINA_FALSE);
     }
   else if ((eina_str_has_extension(bgfile, ".gif")) ||
            (eina_str_has_extension(bgfile, ".png")) ||
            (eina_str_has_extension(bgfile, ".jpg")) ||
            (eina_str_has_extension(bgfile, ".jpeg")) ||
            (eina_str_has_extension(bgfile, ".bmp")))
     {
        o = e_icon_add(e_comp->evas);
        if (!(eina_str_has_extension(bgfile, ".gif")))
          e_icon_preload_set(o, EINA_TRUE);
        e_icon_scale_size_set(o, 0);
        e_icon_fill_inside_set(o, 0);
        e_icon_file_key_set(o, bgfile, NULL);
     }
   else
     {
        o = e_video_add(e_comp->evas, bgfile, EINA_FALSE);
     }
   evas_object_data_set(o, "e_zone", zone);
   evas_object_repeat_events_set(o, 1);
   zone->bg_object = o;
   evas_object_name_set(zone->bg_object, "zone->bg_object");
   if (transition == E_BG_TRANSITION_NONE)
     {
        evas_object_move(o, zone->x, zone->y);
        evas_object_resize(o, zone->w, zone->h);
        evas_object_layer_set(o, E_LAYER_BG);
     }
   evas_object_clip_set(o, zone->bg_clip_object);
   evas_object_show(o);

   if (transition != E_BG_TRANSITION_NONE)
     {
        edje_object_part_swallow(zone->transition_object, "e.swallow.bg.old",
                                 zone->prev_bg_object);
        edje_object_part_swallow(zone->transition_object, "e.swallow.bg.new",
                                 zone->bg_object);
        edje_object_signal_emit(zone->transition_object, "e,action,start", "e");
        evas_object_name_set(zone->transition_object, "zone->transition_object");
        evas_object_move(zone->transition_object, zone->x, zone->y);
        evas_object_resize(zone->transition_object, zone->w, zone->h);
     }
   if (zone->bg_object) evas_object_name_set(zone->bg_object, "zone->bg_object");
   if (zone->prev_bg_object) evas_object_name_set(zone->prev_bg_object, "zone->prev_bg_object");
   e_comp_canvas_zone_update(zone);
end:
   eina_stringshare_del(bgfile);
}

E_API void
e_bg_default_set(const char *file)
{
   E_Event_Bg_Update *ev;
   Eina_Bool changed;

   file = eina_stringshare_add(file);
   changed = file != e_config->desktop_default_background;

   if (!changed)
     {
        eina_stringshare_del(file);
        return;
     }

   if (e_config->desktop_default_background)
     {
        e_filereg_deregister(e_config->desktop_default_background);
        eina_stringshare_del(e_config->desktop_default_background);
     }

   if (file)
     {
        e_filereg_register(file);
        e_config->desktop_default_background = file;
     }
   else
     e_config->desktop_default_background = NULL;

   ev = E_NEW(E_Event_Bg_Update, 1);
   ev->zone = -1;
   ev->desk_x = -1;
   ev->desk_y = -1;
   ecore_event_add(E_EVENT_BG_UPDATE, ev, _e_bg_event_bg_update_free, NULL);
}

E_API void
e_bg_add(int zone, int desk_x, int desk_y, const char *file)
{
   const Eina_List *l;
   E_Config_Desktop_Background *cfbg;
   E_Event_Bg_Update *ev;

   file = eina_stringshare_add(file);

   EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cfbg)
     {
        if ((cfbg) &&
            (cfbg->zone == zone) &&
            (cfbg->desk_x == desk_x) &&
            (cfbg->desk_y == desk_y) &&
            (cfbg->file == file))
          {
             eina_stringshare_del(file);
             return;
          }
     }

   e_bg_del(zone, desk_x, desk_y);
   cfbg = E_NEW(E_Config_Desktop_Background, 1);
   cfbg->zone = zone;
   cfbg->desk_x = desk_x;
   cfbg->desk_y = desk_y;
   cfbg->file = file;
   e_config->desktop_backgrounds = eina_list_append(e_config->desktop_backgrounds, cfbg);

   e_filereg_register(cfbg->file);

   ev = E_NEW(E_Event_Bg_Update, 1);
   ev->zone = zone;
   ev->desk_x = desk_x;
   ev->desk_y = desk_y;
   ecore_event_add(E_EVENT_BG_UPDATE, ev, _e_bg_event_bg_update_free, NULL);
}

E_API void
e_bg_del(int zone, int desk_x, int desk_y)
{
   Eina_List *l;
   E_Config_Desktop_Background *cfbg;
   E_Event_Bg_Update *ev;

   EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cfbg)
     {
        if (!cfbg) continue;
        if ((cfbg->zone == zone) && (cfbg->desk_x == desk_x) && (cfbg->desk_y == desk_y))
          {
             e_config->desktop_backgrounds = eina_list_remove_list(e_config->desktop_backgrounds, l);
             e_filereg_deregister(cfbg->file);
             if (cfbg->file) eina_stringshare_del(cfbg->file);
             free(cfbg);
             break;
          }
     }

   ev = E_NEW(E_Event_Bg_Update, 1);
   ev->zone = zone;
   ev->desk_x = desk_x;
   ev->desk_y = desk_y;
   ecore_event_add(E_EVENT_BG_UPDATE, ev, _e_bg_event_bg_update_free, NULL);
}

E_API void
e_bg_update(void)
{
   const Eina_List *l;
   E_Zone *zone;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     e_zone_bg_reconfigure(zone);
}

/* local subsystem functions */

/**
 * Set background to image, as required in e_fm2_mime_handler_new()
 */
static void
e_bg_handler_set(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path)
{
   char buf[4096];
   int copy = 1;

   if (!path) return;

   if (!eina_str_has_extension(path, "edj"))
     {
        e_import_config_dialog_show(NULL, path, (Ecore_End_Cb)_e_bg_handler_image_imported, NULL);
        return;
     }

   /* if not in system dir or user dir, copy to user dir */
   e_prefix_data_concat_static(buf, "data/backgrounds");
   if (!strncmp(buf, path, strlen(buf)))
     copy = 0;
   if (copy)
     {
        e_user_dir_concat_static(buf, "backgrounds");
        if (!strncmp(buf, path, strlen(buf)))
          copy = 0;
     }
   if (copy)
     {
        const char *file;
        char *name;

        file = ecore_file_file_get(path);
        name = ecore_file_strip_ext(file);

        e_user_dir_snprintf(buf, sizeof(buf), "backgrounds/%s-%f.edj", name, ecore_time_unix_get());
        free(name);

        if (!ecore_file_exists(buf))
          {
             ecore_file_cp(path, buf);
             e_bg_default_set(buf);
          }
        else
          e_bg_default_set(path);
     }
   else
     e_bg_default_set(path);

   e_bg_update();
   e_config_save_queue();
}

/**
 * Test if possible to set background to file, as required in
 * e_fm2_mime_handler_new()
 *
 * This handler tests for files that would be acceptable for setting
 * background.
 *
 * You should just register it with "*.edj" (glob matching extension)
 * or "image/" (mimetypes)that are acceptable with Evas loaders.
 *
 * Just edje files with "e/desktop/background" group are used.
 */
static int
e_bg_handler_test(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path)
{
   if (!path) return 0;

   if (eina_str_has_extension(path, "edj"))
     {
        if (edje_file_group_exists(path, "e/desktop/background")) return 1;
        return 0;
     }

   /* it's image/png or image/jpeg, we'll import it. */
   return 1;
}

static void
_e_bg_signal(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Zone *zone = data;

   E_FREE_FUNC(zone->prev_bg_object, evas_object_del);
   E_FREE_FUNC(zone->transition_object, evas_object_del);
   e_comp_canvas_zone_update(zone);
   evas_object_move(zone->bg_object, zone->x, zone->y);
   evas_object_resize(zone->bg_object, zone->w, zone->h);
   evas_object_layer_set(zone->bg_object, E_LAYER_BG);
   evas_object_clip_set(zone->bg_object, zone->bg_clip_object);
   evas_object_show(zone->bg_object);
}

static void
_e_bg_event_bg_update_free(void *data EINA_UNUSED, void *event)
{
   free(event);
}

static void
_e_bg_handler_image_imported(const char *image_path, void *data EINA_UNUSED)
{
   e_bg_default_set(image_path);
   e_bg_update();
   e_config_save_queue();
}

#include "e.h"

/* local subsystem functions */
static void               _e_bindings_mouse_free(E_Binding_Mouse *bind);
static void               _e_bindings_key_free(E_Binding_Key *bind);
static void               _e_bindings_edge_free(E_Binding_Edge *bind);
static void               _e_bindings_signal_free(E_Binding_Signal *bind);
static void               _e_bindings_wheel_free(E_Binding_Wheel *bind);
static void               _e_bindings_acpi_free(E_Binding_Acpi *bind);
static void               _e_bindings_swipe_free(E_Binding_Swipe *bind);
static Eina_Bool          _e_bindings_edge_cb_timer(void *data);

/* local subsystem globals */

static Eina_List *mouse_bindings = NULL;
static Eina_List *key_bindings = NULL;
static Eina_List *edge_bindings = NULL;
static Eina_List *signal_bindings = NULL;
static Eina_List *wheel_bindings = NULL;
static Eina_List *acpi_bindings = NULL;
static Eina_List *swipe_bindings = NULL;

static unsigned int bindings_disabled = 0;

EINTERN E_Action *(*e_binding_key_list_cb)(E_Binding_Context, Ecore_Event_Key*, E_Binding_Modifier, E_Binding_Key **);

typedef struct _E_Binding_Edge_Data E_Binding_Edge_Data;

struct _E_Binding_Edge_Data
{
   E_Binding_Edge    *bind;
   E_Event_Zone_Edge *ev;
   E_Action          *act;
   E_Object          *obj;
};

/* externally accessible functions */

EINTERN int
e_bindings_init(void)
{
   E_Config_Binding_Signal *ebs;
   E_Config_Binding_Mouse *ebm;
   E_Config_Binding_Wheel *ebw;
   E_Config_Binding_Edge *ebe;
   E_Config_Binding_Key *ebk;
   E_Config_Binding_Acpi *eba;
   Eina_List *l;

   EINA_LIST_FOREACH(e_bindings->mouse_bindings, l, ebm)
     e_bindings_mouse_add(ebm->context, ebm->button, ebm->modifiers,
                          ebm->any_mod, ebm->action, ebm->params);

   EINA_LIST_FOREACH(e_bindings->key_bindings, l, ebk)
     e_bindings_key_add(ebk->context, ebk->key, ebk->modifiers,
                        ebk->any_mod, ebk->action, ebk->params);

   EINA_LIST_FOREACH(e_bindings->edge_bindings, l, ebe)
     e_bindings_edge_add(ebe->context, ebe->edge, ebe->drag_only, ebe->modifiers,
                         ebe->any_mod, ebe->action, ebe->params, ebe->delay);

   EINA_LIST_FOREACH(e_bindings->signal_bindings, l, ebs)
     {
        e_bindings_signal_add(ebs->context, ebs->signal, ebs->source, ebs->modifiers,
                              ebs->any_mod, ebs->action, ebs->params);
        /* FIXME: Can this be solved in a generic way? */
        /* FIXME: Only change cursor if action is allowed! */
        if ((ebs->action) && (ebs->signal) && (ebs->source) &&
            (!strcmp(ebs->action, "window_resize")) &&
            (!strncmp(ebs->signal, "mouse,down,", 11)) &&
            (!strncmp(ebs->source, "e.event.resize.", 15)))
          {
             char params[32];

             snprintf(params, sizeof(params), "resize_%s", ebs->params);
             e_bindings_signal_add(ebs->context, "mouse,in", ebs->source, ebs->modifiers,
                                   ebs->any_mod, "pointer_resize_push", params);
             e_bindings_signal_add(ebs->context, "mouse,out", ebs->source, ebs->modifiers,
                                   ebs->any_mod, "pointer_resize_pop", params);
          }
     }

   EINA_LIST_FOREACH(e_bindings->wheel_bindings, l, ebw)
     e_bindings_wheel_add(ebw->context, ebw->direction, ebw->z, ebw->modifiers,
                          ebw->any_mod, ebw->action, ebw->params);

   EINA_LIST_FOREACH(e_bindings->acpi_bindings, l, eba)
     e_bindings_acpi_add(eba->context, eba->type, eba->status,
                         eba->action, eba->params);

   e_bindings_swipe_reset();

   return 1;
}

EINTERN int
e_bindings_shutdown(void)
{
   E_FREE_LIST(mouse_bindings, _e_bindings_mouse_free);
   E_FREE_LIST(key_bindings, _e_bindings_key_free);
   E_FREE_LIST(edge_bindings, _e_bindings_edge_free);
   E_FREE_LIST(signal_bindings, _e_bindings_signal_free);
   E_FREE_LIST(wheel_bindings, _e_bindings_wheel_free);
   E_FREE_LIST(acpi_bindings, _e_bindings_acpi_free);
   E_FREE_LIST(swipe_bindings, _e_bindings_swipe_free);

   return 1;
}

E_API int
e_bindings_modifiers_to_ecore_convert(E_Binding_Modifier modifiers)
{
   int mod = 0;

   if (modifiers & E_BINDING_MODIFIER_SHIFT) mod |= ECORE_EVENT_MODIFIER_SHIFT;
   if (modifiers & E_BINDING_MODIFIER_CTRL) mod |= ECORE_EVENT_MODIFIER_CTRL;
   if (modifiers & E_BINDING_MODIFIER_ALT) mod |= ECORE_EVENT_MODIFIER_ALT;
   if (modifiers & E_BINDING_MODIFIER_WIN) mod |= ECORE_EVENT_MODIFIER_WIN;
   /* see comment in e_bindings on numlock
      if (modifiers & ECORE_X_LOCK_NUM) mod |= ECORE_X_LOCK_NUM;
    */

   return mod;
}

E_API void
e_bindings_ecore_event_mouse_wheel_convert(const Ecore_Event_Mouse_Wheel *ev, E_Binding_Event_Wheel *event)
{
   memset(event, 0, sizeof(E_Binding_Event_Wheel));
   event->direction = ev->direction;
   event->z = ev->z;
   event->canvas.x = e_comp_canvas_x_root_adjust(ev->root.x);
   event->canvas.y = e_comp_canvas_y_root_adjust(ev->root.y);
   event->timestamp = ev->timestamp;
   event->modifiers = e_bindings_modifiers_from_ecore(ev->modifiers);
}

E_API void
e_bindings_ecore_event_mouse_button_convert(const Ecore_Event_Mouse_Button *ev, E_Binding_Event_Mouse_Button *event)
{
   memset(event, 0, sizeof(E_Binding_Event_Mouse_Button));
   event->button = ev->buttons;
   event->canvas.x = e_comp_canvas_x_root_adjust(ev->root.x);
   event->canvas.y = e_comp_canvas_y_root_adjust(ev->root.y);
   event->timestamp = ev->timestamp;
   event->modifiers = e_bindings_modifiers_from_ecore(ev->modifiers);

   event->double_click = !!ev->double_click;
   event->triple_click = !!ev->triple_click;
}

E_API void
e_bindings_evas_event_mouse_wheel_convert(const Evas_Event_Mouse_Wheel *ev, E_Binding_Event_Wheel *event)
{
   memset(event, 0, sizeof(E_Binding_Event_Wheel));
   event->direction = ev->direction;
   event->z = ev->z;
   event->canvas.x = ev->output.x, event->canvas.y = ev->output.y;
   event->timestamp = ev->timestamp;

   event->modifiers |= (E_BINDING_MODIFIER_SHIFT * evas_key_modifier_is_set(ev->modifiers, "Shift"));
   event->modifiers |= (E_BINDING_MODIFIER_CTRL * evas_key_modifier_is_set(ev->modifiers, "Control"));
   event->modifiers |= (E_BINDING_MODIFIER_ALT * evas_key_modifier_is_set(ev->modifiers, "Alt"));
   event->modifiers |= (E_BINDING_MODIFIER_WIN * evas_key_modifier_is_set(ev->modifiers, "Super"));
   event->modifiers |= (E_BINDING_MODIFIER_WIN * evas_key_modifier_is_set(ev->modifiers, "Hyper"));
   event->modifiers |= (E_BINDING_MODIFIER_ALTGR * evas_key_modifier_is_set(ev->modifiers, "AltGr"));

/* TODO
   if (modifiers & ECORE_EVENT_LOCK_SCROLL)
     evas_key_lock_on(e, "Scroll_Lock");
   else evas_key_lock_off(e, "Scroll_Lock");

   if (modifiers & ECORE_EVENT_LOCK_NUM)
     evas_key_lock_on(e, "Num_Lock");
   else evas_key_lock_off(e, "Num_Lock");

   if (modifiers & ECORE_EVENT_LOCK_CAPS)
     evas_key_lock_on(e, "Caps_Lock");
   else evas_key_lock_off(e, "Caps_Lock");

   if (modifiers & ECORE_EVENT_LOCK_SHIFT)
     evas_key_lock_on(e, "Shift_Lock");
   else evas_key_lock_off(e, "Shift_Lock");
*/
}

E_API int
e_bindings_evas_modifiers_convert(Evas_Modifier *modifiers)
{
   int mod = 0;

   mod |= (E_BINDING_MODIFIER_SHIFT * evas_key_modifier_is_set(modifiers, "Shift"));
   mod |= (E_BINDING_MODIFIER_CTRL * evas_key_modifier_is_set(modifiers, "Control"));
   mod |= (E_BINDING_MODIFIER_ALT * evas_key_modifier_is_set(modifiers, "Alt"));
   mod |= (E_BINDING_MODIFIER_WIN * evas_key_modifier_is_set(modifiers, "Super"));
   mod |= (E_BINDING_MODIFIER_WIN * evas_key_modifier_is_set(modifiers, "Hyper"));
   mod |= (E_BINDING_MODIFIER_ALTGR * evas_key_modifier_is_set(modifiers, "AltGr"));
   return mod;
}

E_API void
e_bindings_evas_event_mouse_button_convert(const Evas_Event_Mouse_Down *ev, E_Binding_Event_Mouse_Button *event)
{
   memset(event, 0, sizeof(E_Binding_Event_Mouse_Button));
   event->button = ev->button;
   event->canvas.x = ev->output.x, event->canvas.y = ev->output.y;
   event->timestamp = ev->timestamp;

   event->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);

   event->hold = (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD);
   event->scroll = (ev->event_flags & EVAS_EVENT_FLAG_ON_SCROLL);

   event->double_click = (ev->flags & EVAS_BUTTON_DOUBLE_CLICK);
   event->triple_click = (ev->flags & EVAS_BUTTON_TRIPLE_CLICK);

/* TODO
   if (modifiers & ECORE_EVENT_LOCK_SCROLL)
     evas_key_lock_on(e, "Scroll_Lock");
   else evas_key_lock_off(e, "Scroll_Lock");

   if (modifiers & ECORE_EVENT_LOCK_NUM)
     evas_key_lock_on(e, "Num_Lock");
   else evas_key_lock_off(e, "Num_Lock");

   if (modifiers & ECORE_EVENT_LOCK_CAPS)
     evas_key_lock_on(e, "Caps_Lock");
   else evas_key_lock_off(e, "Caps_Lock");

   if (modifiers & ECORE_EVENT_LOCK_SHIFT)
     evas_key_lock_on(e, "Shift_Lock");
   else evas_key_lock_off(e, "Shift_Lock");
*/
}

E_API void
e_bindings_signal_reset(void)
{
   E_Config_Binding_Signal *ebs;
   Eina_List *l;
   E_FREE_LIST(signal_bindings, _e_bindings_signal_free);

   EINA_LIST_FOREACH(e_bindings->signal_bindings, l, ebs)
     {
        e_bindings_signal_add(ebs->context, ebs->signal, ebs->source, ebs->modifiers,
                              ebs->any_mod, ebs->action, ebs->params);
        /* FIXME: Can this be solved in a generic way? */
        /* FIXME: Only change cursor if action is allowed! */
        if ((ebs->action) && (ebs->signal) && (ebs->source) &&
            (!strcmp(ebs->action, "window_resize")) &&
            (!strncmp(ebs->signal, "mouse,down,", 11)) &&
            (!strncmp(ebs->source, "e.event.resize.", 15)))
          {
             char params[32];

             snprintf(params, sizeof(params), "resize_%s", ebs->params);
             e_bindings_signal_add(ebs->context, "mouse,in", ebs->source, ebs->modifiers,
                                   ebs->any_mod, "pointer_resize_push", params);
             e_bindings_signal_add(ebs->context, "mouse,out", ebs->source, ebs->modifiers,
                                   ebs->any_mod, "pointer_resize_pop", params);
          }
     }
}

E_API void
e_bindings_acpi_reset(void)
{
   E_Config_Binding_Acpi *eba;
   Eina_List *l;

   E_FREE_LIST(acpi_bindings, _e_bindings_acpi_free);

   EINA_LIST_FOREACH(e_bindings->acpi_bindings, l, eba)
     e_bindings_acpi_add(eba->context, eba->type, eba->status,
                         eba->action, eba->params);
}

E_API void
e_bindings_wheel_reset(void)
{
   E_Config_Binding_Wheel *ebw;
   Eina_List *l;

   E_FREE_LIST(wheel_bindings, _e_bindings_wheel_free);

   EINA_LIST_FOREACH(e_bindings->wheel_bindings, l, ebw)
     e_bindings_wheel_add(ebw->context, ebw->direction, ebw->z, ebw->modifiers,
                          ebw->any_mod, ebw->action, ebw->params);
}

E_API void
e_bindings_edge_reset(void)
{
   E_Config_Binding_Edge *ebe;
   Eina_List *l;

   E_FREE_LIST(edge_bindings, _e_bindings_edge_free);

   EINA_LIST_FOREACH(e_bindings->edge_bindings, l, ebe)
     e_bindings_edge_add(ebe->context, ebe->edge, ebe->drag_only, ebe->modifiers,
                         ebe->any_mod, ebe->action, ebe->params, ebe->delay);
}

E_API void
e_bindings_mouse_reset(void)
{
   E_Config_Binding_Mouse *ebm;
   Eina_List *l;

   E_FREE_LIST(mouse_bindings, _e_bindings_mouse_free);

   EINA_LIST_FOREACH(e_bindings->mouse_bindings, l, ebm)
     e_bindings_mouse_add(ebm->context, ebm->button, ebm->modifiers,
                          ebm->any_mod, ebm->action, ebm->params);
}

E_API void
e_bindings_key_reset(void)
{
   E_Config_Binding_Key *ebk;
   Eina_List *l;

   e_comp_canvas_keys_ungrab();
   E_FREE_LIST(key_bindings, _e_bindings_key_free);

   EINA_LIST_FOREACH(e_bindings->key_bindings, l, ebk)
     e_bindings_key_add(ebk->context, ebk->key, ebk->modifiers,
                        ebk->any_mod, ebk->action, ebk->params);
   e_comp_canvas_keys_grab();
}

E_API void
e_bindings_swipe_reset(void)
{
   E_Config_Binding_Swipe *eswipe;
   Eina_List *l;

   E_FREE_LIST(swipe_bindings, _e_bindings_swipe_free);

   EINA_LIST_FOREACH(e_bindings->swipe_bindings, l, eswipe)
     e_bindings_swipe_add(eswipe->context, eswipe->direction, eswipe->length, eswipe->fingers, eswipe->error, eswipe->action, eswipe->params);
}

E_API void
e_bindings_reset(void)
{
   e_bindings_signal_reset();
   e_bindings_mouse_reset();
   e_bindings_wheel_reset();
   e_bindings_edge_reset();
   e_bindings_key_reset();
   e_bindings_swipe_reset();
}

E_API void
e_bindings_mouse_add(E_Binding_Context ctxt, int button, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Mouse *binding;

   binding = calloc(1, sizeof(E_Binding_Mouse));
   binding->ctxt = ctxt;
   binding->button = button;
   binding->mod = mod;
   binding->any_mod = any_mod;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   mouse_bindings = eina_list_append(mouse_bindings, binding);
}

E_API void
e_bindings_mouse_del(E_Binding_Context ctxt, int button, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Mouse *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(mouse_bindings, l, binding)
     {
        if ((binding->ctxt == ctxt) &&
            (binding->button == button) &&
            (binding->mod == mod) &&
            (binding->any_mod == any_mod) &&
            (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
             ((!binding->action) && (!action))) &&
            (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
             ((!binding->params) && (!params))))
          {
             _e_bindings_mouse_free(binding);
             mouse_bindings = eina_list_remove_list(mouse_bindings, l);
             break;
          }
     }
}

E_API void
e_bindings_mouse_grab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Mouse *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(mouse_bindings, l, binding)
     {
        if (binding->ctxt == E_BINDING_CONTEXT_ANY) continue;
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
#ifndef HAVE_WAYLAND_ONLY
             ecore_x_window_button_grab(win, binding->button,
                                        ECORE_X_EVENT_MASK_MOUSE_DOWN |
                                        ECORE_X_EVENT_MASK_MOUSE_UP |
                                        ECORE_X_EVENT_MASK_MOUSE_MOVE,
                                        e_bindings_modifiers_to_ecore_convert(binding->mod),
                                        binding->any_mod);
#endif
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

E_API void
e_bindings_mouse_ungrab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Mouse *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(mouse_bindings, l, binding)
     {
        if (binding->ctxt == E_BINDING_CONTEXT_ANY) continue;
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
#ifndef HAVE_WAYLAND_ONLY
             ecore_x_window_button_ungrab(win, binding->button,
                                          e_bindings_modifiers_to_ecore_convert(binding->mod), binding->any_mod);
#endif
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

E_API E_Action *
e_bindings_mouse_button_find(E_Binding_Context ctxt, E_Binding_Event_Mouse_Button *ev, E_Binding_Mouse **bind_ret)
{
   E_Binding_Mouse *binding;
   Eina_List *start = NULL, *l;
   E_Action *act = NULL;

   if (bind_ret && *bind_ret)
     start = eina_list_data_find_list(mouse_bindings, *bind_ret);
   if (start)
     {
        start = start->next;
        if (!start)
          {
             *bind_ret = NULL;
             return NULL;
          }
     }
   EINA_LIST_FOREACH(start ?: mouse_bindings, l, binding)
     {
        if ((binding->button == (int)ev->button) &&
            ((binding->any_mod) || (binding->mod == ev->modifiers)))
          {
             if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
             if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
             act = e_action_find(binding->action);
             if (bind_ret) *bind_ret = binding;
             if (!act) continue;
             if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
          }
     }
   return act;
}

E_API char *
e_bindings_mouse_action_modifiers_text_generate(E_Binding_Context ctxt, const char *action, unsigned int *modifiers, unsigned int *button)
{
   Eina_Strbuf *sbuf;
   Eina_Bool shift;
   Eina_Bool ctrl;
   Eina_Bool alt;
   Eina_Bool win;
   Eina_Bool altgr;
   static const char *names[] =
   {
      "Shift",
      "Control",
      "Alt",
      "Win",
      "AltGr",
   };

   struct
   {
      Eina_Bool *val;
      const char *name;
   } keys[5];
   unsigned int current, i;
   Eina_Bool found = EINA_FALSE;
   Eina_List *l;
   char *ret;
   E_Config_Binding_Mouse *ebm;

   keys[0].val = &shift;
   keys[1].val = &ctrl;
   keys[2].val = &alt;
   keys[3].val = &win;
   keys[4].val = &altgr;

   EINA_LIST_FOREACH(e_bindings->mouse_bindings, l, ebm)
     if ((ebm->context == (int)ctxt) && eina_streq(ebm->action, action))
       {
          current = ebm->modifiers;
          if (modifiers) *modifiers = current;
          if (button) *button = ebm->button;
          found = EINA_TRUE;
          break;
       }
   if (!found) return NULL;
   sbuf = eina_strbuf_new();
   for (i = 0; i < 5; i++)
     {
        keys[i].name = names[i];
        *keys[i].val = (current & (1 << i));
        if (!*keys[i].val) continue;
        if (eina_strbuf_length_get(sbuf))
          eina_strbuf_append_char(sbuf, '+');
        eina_strbuf_append_printf(sbuf, "<hilight>%s</hilight>", _(keys[i].name));
     }
   ret = eina_strbuf_string_steal(sbuf);
   eina_strbuf_free(sbuf);
   return ret;
}

E_API E_Action *
e_bindings_mouse_down_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Binding_Event_Mouse_Button *ev)
{
   E_Action *act;
   E_Binding_Mouse *binding = NULL;

   if (bindings_disabled) return NULL;
   while (1)
     {
        act = e_bindings_mouse_button_find(ctxt, ev, &binding);
        if (!act) break;
        if (act->func.go_mouse)
          {
             if (!act->func.go_mouse(obj, binding->params, ev))
               continue;
          }
        else if (act->func.go)
          act->func.go(obj, binding->params);
        break;
     }
   return act;
}

E_API E_Action *
e_bindings_mouse_down_evas_event_handle(E_Binding_Context ctxt, E_Object *obj, Evas_Event_Mouse_Down *ev)
{
   E_Binding_Event_Mouse_Button event;

   e_bindings_evas_event_mouse_button_convert(ev, &event);

   return e_bindings_mouse_down_event_handle(ctxt, obj, &event);
}

E_API E_Action *
e_bindings_mouse_down_ecore_event_handle(E_Binding_Context ctxt, E_Object *obj, Ecore_Event_Mouse_Button *ev)
{
   E_Binding_Event_Mouse_Button event;

   e_bindings_ecore_event_mouse_button_convert(ev, &event);

   return e_bindings_mouse_down_event_handle(ctxt, obj, &event);
}

E_API E_Action *
e_bindings_mouse_up_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Binding_Event_Mouse_Button *ev)
{
   E_Action *act;
   E_Binding_Mouse *binding = NULL;

   if (bindings_disabled) return NULL;
   while (1)
     {
        act = e_bindings_mouse_button_find(ctxt, ev, &binding);
        if (!act) break;
        if (act->func.end_mouse)
          {
             if (!act->func.end_mouse(obj, binding->params, ev))
               continue;
          }
        else if (act->func.end)
          act->func.end(obj, binding->params);
        break;
     }
   return act;
}

E_API E_Action *
e_bindings_mouse_up_evas_event_handle(E_Binding_Context ctxt, E_Object *obj, Evas_Event_Mouse_Up *ev)
{
   E_Binding_Event_Mouse_Button event;

   e_bindings_evas_event_mouse_button_convert((Evas_Event_Mouse_Down*)ev, &event);

   return e_bindings_mouse_up_event_handle(ctxt, obj, &event);
}

E_API E_Action *
e_bindings_mouse_up_ecore_event_handle(E_Binding_Context ctxt, E_Object *obj, Ecore_Event_Mouse_Button *ev)
{
   E_Binding_Event_Mouse_Button event;

   e_bindings_ecore_event_mouse_button_convert(ev, &event);

   return e_bindings_mouse_up_event_handle(ctxt, obj, &event);
}

E_API void
e_bindings_key_add(E_Binding_Context ctxt, const char *key, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Key *binding;

   binding = calloc(1, sizeof(E_Binding_Key));
   binding->ctxt = ctxt;
   binding->key = eina_stringshare_add(key);
   binding->mod = mod;
   binding->any_mod = any_mod;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   key_bindings = eina_list_append(key_bindings, binding);
}

E_API E_Binding_Key *
e_bindings_key_get(const char *action)
{
   E_Binding_Key *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if (binding->action && action && !strcmp(action, binding->action))
          return binding;
     }
   return NULL;
}

E_API E_Binding_Key *
e_bindings_key_find(const char *key, E_Binding_Modifier mod, int any_mod)
{
   E_Binding_Key *binding;
   Eina_List *l;

   if (!key) return NULL;

   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if ((binding->key) && (!strcmp(key, binding->key)) &&
            (binding->mod == mod) && (binding->any_mod == any_mod))
          return binding;
     }

   return NULL;
}

E_API void
e_bindings_key_del(E_Binding_Context ctxt, const char *key, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Key *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if ((binding->ctxt == ctxt) &&
            (key) && (binding->key) && (!strcmp(binding->key, key)) &&
            (binding->mod == mod) &&
            (binding->any_mod == any_mod) &&
            (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
             ((!binding->action) && (!action))) &&
            (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
             ((!binding->params) && (!params))))
          {
             _e_bindings_key_free(binding);
             key_bindings = eina_list_remove_list(key_bindings, l);
             break;
          }
     }
}

E_API void
e_bindings_key_grab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Key *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
             if (e_bindings_key_allowed(binding->key))
               {
#ifndef HAVE_WAYLAND_ONLY
                  ecore_x_window_key_grab(win, binding->key,
                                          e_bindings_modifiers_to_ecore_convert(binding->mod), binding->any_mod);
#endif
               }
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

E_API void
e_bindings_key_ungrab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Key *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
             if (e_bindings_key_allowed(binding->key))
               {
#ifndef HAVE_WAYLAND_ONLY
                  ecore_x_window_key_ungrab(win, binding->key,
                                            e_bindings_modifiers_to_ecore_convert(binding->mod), binding->any_mod);
#endif
               }
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

EINTERN E_Binding_Key *e_binding_key_current;

E_API E_Action *
e_bindings_key_down_event_handle(E_Binding_Context ctxt, E_Object *obj, Ecore_Event_Key *ev)
{
   E_Binding_Key *binding;
   E_Action *act;

   if (bindings_disabled) return NULL;
   act = e_bindings_key_event_find(ctxt, ev, &binding);
   if (!act) return NULL;
   e_binding_key_current = binding;
   if (act->func.go_key)
     act->func.go_key(obj, binding->params, ev);
   else if (act->func.go)
     act->func.go(obj, binding->params);
   e_binding_key_current = NULL;
   return act;
}

E_API E_Action *
e_bindings_key_up_event_handle(E_Binding_Context ctxt, E_Object *obj, Ecore_Event_Key *ev)
{
   E_Binding_Key *binding;
   E_Action *act;

   if (bindings_disabled) return NULL;
   act = e_bindings_key_event_find(ctxt, ev, &binding);
   if (!act) return NULL;
   e_binding_key_current = binding;
   if (act->func.end_key)
     act->func.end_key(obj, binding->params, ev);
   else if (act->func.end)
     act->func.end(obj, binding->params);
   e_binding_key_current = NULL;
   return act;
}

E_API E_Action *
e_bindings_key_event_find(E_Binding_Context ctxt, Ecore_Event_Key *ev, E_Binding_Key **bind_ret)
{
   E_Binding_Modifier mod = 0;
   E_Binding_Key *binding;
   Eina_List *l;
   E_Action *act = NULL;

   mod = e_bindings_modifiers_from_ecore(ev->modifiers);
   if (e_binding_key_list_cb)
     {
        act = e_binding_key_list_cb(ctxt, ev, mod, bind_ret);
        if (act) return act;
        if (bind_ret) *bind_ret = NULL;
     }
   EINA_LIST_FOREACH(key_bindings, l, binding)
     {
        if ((binding->key) && ((!strcmp(binding->key, ev->key)) || (!strcmp(binding->key, ev->keyname))) &&
            ((binding->any_mod) || (binding->mod == mod)))
          {
             if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
             if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
             act = e_action_find(binding->action);
             if (bind_ret) *bind_ret = binding;
             if (!act) continue;
             if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
          }
     }
   return act;
}

E_API Eina_Bool
e_bindings_key_allowed(const char *key)
{
   if (!key) return EINA_FALSE;
   if ((!strcmp(key, "Shift_L")) ||
       (!strcmp(key, "Shift_R")) ||
       (!strcmp(key, "Control_L")) ||
       (!strcmp(key, "Control_R")) ||
       (!strcmp(key, "Alt_L")) ||
       (!strcmp(key, "Alt_R")) ||
       (!strcmp(key, "Meta_L")) ||
       (!strcmp(key, "Meta_R")) ||
       (!strcmp(key, "Hyper_L")) ||
       (!strcmp(key, "Hyper_R")) ||
       (!strcmp(key, "Super_L")) ||
       (!strcmp(key, "Super_R")) ||
       (!strcmp(key, "AltGr")) ||
       (!strcmp(key, "Caps_Lock")) ||
       (!strcmp(key, "Shift_Lock")) ||
       (!strcmp(key, "Kana_Lock")) ||
       (!strcmp(key, "Num_Lock")) ||
       (!strcmp(key, "Scroll_Lock")))
     return EINA_FALSE;
   return EINA_TRUE;
}


E_API void
e_bindings_edge_add(E_Binding_Context ctxt, E_Zone_Edge edge, Eina_Bool drag_only, E_Binding_Modifier mod, int any_mod, const char *action, const char *params, float delay)
{
   E_Binding_Edge *binding;

   binding = calloc(1, sizeof(E_Binding_Edge));
   binding->ctxt = ctxt;
   binding->edge = edge;
   binding->mod = mod;
   binding->any_mod = any_mod;
   binding->delay = delay;
   binding->drag_only = drag_only;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   edge_bindings = eina_list_append(edge_bindings, binding);

   e_zone_edge_new(edge);
}

E_API Eina_Bool
e_bindings_edge_flippable_get(E_Zone_Edge edge)
{
   E_Binding_Edge *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(edge_bindings, l, binding)
     {
        if ((binding->edge == edge) && (binding->action))
          {
             if ((!strcmp(binding->action, "desk_flip_in_direction")) ||
                 (!strcmp(binding->action, "desk_flip_by")))
               return EINA_TRUE;
          }
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_bindings_edge_non_flippable_get(E_Zone_Edge edge)
{
   E_Binding_Edge *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(edge_bindings, l, binding)
     {
        if ((binding->edge == edge) && (binding->action))
          {
             if ((!strcmp(binding->action, "desk_flip_in_direction")) ||
                 (!strcmp(binding->action, "desk_flip_by")))
               continue;
             return EINA_TRUE;
          }
     }
   return EINA_FALSE;
}

E_API E_Binding_Edge *
e_bindings_edge_get(const char *action, E_Zone_Edge edge, int click)
{
   E_Binding_Edge *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(edge_bindings, l, binding)
     {
        if ((binding->edge == edge) &&
            ((click && EINA_FLT_EQ(binding->delay, -1.0 * click))
             || (!click && (binding->delay >= 0.0))) &&
            (binding->action) && (action) &&
            (!strcmp(action, binding->action)))
          return binding;
     }
   return NULL;
}

E_API void
e_bindings_edge_del(E_Binding_Context ctxt, E_Zone_Edge edge, Eina_Bool drag_only, E_Binding_Modifier mod, int any_mod, const char *action, const char *params, float delay)
{
   E_Binding_Edge *binding;
   Eina_List *l, *ll;
   int ref_count = 0;

   EINA_LIST_FOREACH_SAFE(edge_bindings, l, ll, binding)
     {
        if (binding->edge == edge)
          {
             if ((binding->ctxt == ctxt) &&
                 (binding->mod == mod) &&
                 EINA_FLT_EQ(binding->delay, delay) &&
                 (binding->any_mod == any_mod) &&
                 (binding->drag_only == drag_only) &&
                 (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
                  ((!binding->action) && (!action))) &&
                 (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
                  ((!binding->params) && (!params))))
               {
                  _e_bindings_edge_free(binding);
                  edge_bindings = eina_list_remove_list(edge_bindings, l);
               }
             else ref_count++;
          }
     }

   if (!ref_count)
     e_zone_edge_free(edge);
}

E_API E_Action *
e_bindings_edge_event_find(E_Binding_Context ctxt, E_Event_Zone_Edge *ev, Eina_Bool click, E_Binding_Edge **bind_ret)
{
   E_Binding_Edge *binding;
   E_Binding_Modifier mod = 0;
   E_Action *act = NULL;
   Eina_List *l;

   mod = e_bindings_modifiers_from_ecore(ev->modifiers);
   EINA_LIST_FOREACH(edge_bindings, l, binding)
     /* A value of <= -1.0 for the delay indicates it as a mouse-click binding on that edge */
     if (((binding->edge == ev->edge)) &&
         ((click && EINA_FLT_EQ(binding->delay, -1.0 * ev->button)) || (!click && (binding->delay >= 0.0))) &&
         ((binding->drag_only == ev->drag) || ev->drag) &&
         ((binding->any_mod) || (binding->mod == mod)))
       {
          if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
          if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
          act = e_action_find(binding->action);
          if (bind_ret) *bind_ret = binding;
          if (!act) continue;
          if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
       }
   return act;
}

E_API E_Action *
e_bindings_edge_in_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Event_Zone_Edge *ev)
{
   E_Binding_Edge *binding = NULL;
   E_Desk *current = NULL;
   E_Action *act = NULL;
   E_Binding_Edge_Data *ed;
   E_Event_Zone_Edge *ev2;

   if (bindings_disabled) return NULL;
   current = e_desk_at_xy_get(ev->zone, ev->zone->desk_x_current, ev->zone->desk_y_current);
   if (current->fullscreen_clients && (!e_config->fullscreen_flip)) return NULL;
   act = e_bindings_edge_event_find(ctxt, ev, 0, &binding);
   if ((!act) || (!binding)) return NULL;
   ed = E_NEW(E_Binding_Edge_Data, 1);
   ev2 = E_NEW(E_Event_Zone_Edge, 1);

   /* The original event will be freed before it can be
    * used again */
   ev2->zone = ev->zone;
   ev2->edge = ev->edge;
   ev2->x = ev->x;
   ev2->y = ev->y;

   ed->bind = binding;
   ed->obj = obj;
   ed->act = act;
   ed->ev = ev2;
   binding->timer = ecore_timer_loop_add(((double)binding->delay), _e_bindings_edge_cb_timer, ed);
   return act;
}

E_API E_Action *
e_bindings_edge_out_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Event_Zone_Edge *ev)
{
   E_Binding_Edge *binding = NULL;
   E_Action *act = NULL;
   E_Desk *current = NULL;

   if (bindings_disabled) return NULL;
   current = e_desk_at_xy_get(ev->zone, ev->zone->desk_x_current, ev->zone->desk_y_current);
   if (current->fullscreen_clients && (!e_config->fullscreen_flip)) return NULL;
   act = e_bindings_edge_event_find(ctxt, ev, 0, &binding);
   if ((!act) || (!binding)) return NULL;
   if (binding->timer)
     {
        E_Binding_Edge_Data *ed;

        ed = ecore_timer_del(binding->timer);
        if (ed)
          {
             E_FREE(ed->ev);
             E_FREE(ed);
          }
     }
   binding->timer = NULL;

   act = e_action_find(binding->action);
   if (act && act->func.end)
     act->func.end(obj, binding->params);
   return act;
}

E_API E_Action *
e_bindings_edge_down_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Event_Zone_Edge *ev)
{
   E_Binding_Edge *binding = NULL;
   E_Desk *current = NULL;
   E_Action *act = NULL;

   if (bindings_disabled) return NULL;
   current = e_desk_at_xy_get(ev->zone, ev->zone->desk_x_current, ev->zone->desk_y_current);
   if (current->fullscreen_clients && (!e_config->fullscreen_flip)) return NULL;
   act = e_bindings_edge_event_find(ctxt, ev, 1, &binding);
   if ((!act) || (!binding)) return NULL;
   if (act->func.go_edge)
     act->func.go_edge(obj, binding->params, ev);
   else if (act->func.go)
     act->func.go(obj, binding->params);
   return act;
}

E_API E_Action *
e_bindings_edge_up_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Event_Zone_Edge *ev)
{
   E_Binding_Edge *binding = NULL;
   E_Action *act = NULL;
   E_Desk *current = NULL;

   if (bindings_disabled) return NULL;
   current = e_desk_at_xy_get(ev->zone, ev->zone->desk_x_current, ev->zone->desk_y_current);
   if (current->fullscreen_clients && (!e_config->fullscreen_flip)) return NULL;
   act = e_bindings_edge_event_find(ctxt, ev, 1, &binding);
   if ((!act) || (!binding)) return NULL;
   act = e_action_find(binding->action);
   if (act && act->func.end)
     act->func.end(obj, binding->params);
   return act;
}

E_API void
e_bindings_signal_add(E_Binding_Context ctxt, const char *sig, const char *src, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Signal *binding;

   binding = calloc(1, sizeof(E_Binding_Signal));
   binding->ctxt = ctxt;
   if (sig) binding->sig = eina_stringshare_add(sig);
   if (src) binding->src = eina_stringshare_add(src);
   binding->mod = mod;
   binding->any_mod = any_mod;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   signal_bindings = eina_list_append(signal_bindings, binding);
}

E_API void
e_bindings_signal_del(E_Binding_Context ctxt, const char *sig, const char *src, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Signal *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(signal_bindings, l, binding)
     {
        if ((binding->ctxt == ctxt) &&
            (((binding->sig) && (sig) && (!strcmp(binding->sig, sig))) ||
             ((!binding->sig) && (!sig))) &&
            (((binding->src) && (src) && (!strcmp(binding->src, src))) ||
             ((!binding->src) && (!src))) &&
            (binding->mod == mod) &&
            (binding->any_mod == any_mod) &&
            (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
             ((!binding->action) && (!action))) &&
            (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
             ((!binding->params) && (!params))))
          {
             _e_bindings_signal_free(binding);
             signal_bindings = eina_list_remove_list(signal_bindings, l);
             break;
          }
     }
}

E_API E_Action *
e_bindings_signal_find(E_Binding_Context ctxt, const char *sig, const char *src, E_Binding_Signal **bind_ret)
{
   E_Binding_Modifier mod = 0;
   E_Binding_Signal *binding;
   Eina_List *l;
   E_Action *act = NULL;

   if (strstr(sig, "MOD:Shift")) mod |= E_BINDING_MODIFIER_SHIFT;
   if (strstr(sig, "MOD:Control")) mod |= E_BINDING_MODIFIER_CTRL;
   if (strstr(sig, "MOD:Alt")) mod |= E_BINDING_MODIFIER_ALT;
   if (strstr(sig, "MOD:Super")) mod |= E_BINDING_MODIFIER_WIN;
   EINA_LIST_FOREACH(signal_bindings, l, binding)
     {
        if ((e_util_glob_match(sig, binding->sig)) &&
            (e_util_glob_match(src, binding->src)) &&
            ((binding->any_mod) || (binding->mod == mod)))
          {
             if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
             if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
             act = e_action_find(binding->action);
             if (bind_ret) *bind_ret = binding;
             if (!act) continue;
             if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
          }
     }
   return act;
}

E_API E_Action *
e_bindings_signal_handle(E_Binding_Context ctxt, E_Object *obj, const char *sig, const char *src)
{
   E_Action *act;
   E_Binding_Signal *binding;

   if (bindings_disabled) return NULL;
   if ((!sig) || (sig && (sig[0] == 0)))
     return NULL;
   if (src && (src[0] == 0)) src = NULL;
   act = e_bindings_signal_find(ctxt, sig, src, &binding);
   if (act)
     {
        if (act->func.go_signal)
          act->func.go_signal(obj, binding->params, sig, src);
        else if (act->func.go)
          act->func.go(obj, binding->params);
        return act;
     }
   return act;
}

E_API void
e_bindings_wheel_add(E_Binding_Context ctxt, int direction, int z, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Wheel *binding;

   binding = calloc(1, sizeof(E_Binding_Wheel));
   binding->ctxt = ctxt;
   binding->direction = direction;
   binding->z = z;
   binding->mod = mod;
   binding->any_mod = any_mod;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   wheel_bindings = eina_list_append(wheel_bindings, binding);
}

E_API void
e_bindings_wheel_del(E_Binding_Context ctxt, int direction, int z, E_Binding_Modifier mod, int any_mod, const char *action, const char *params)
{
   E_Binding_Wheel *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(wheel_bindings, l, binding)
     {
        if ((binding->ctxt == ctxt) &&
            (binding->direction == direction) &&
            (binding->z == z) &&
            (binding->mod == mod) &&
            (binding->any_mod == any_mod) &&
            (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
             ((!binding->action) && (!action))) &&
            (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
             ((!binding->params) && (!params))))
          {
             _e_bindings_wheel_free(binding);
             wheel_bindings = eina_list_remove_list(wheel_bindings, l);
             break;
          }
     }
}

E_API void
e_bindings_wheel_grab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Wheel *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(wheel_bindings, l, binding)
     {
        if (binding->ctxt == E_BINDING_CONTEXT_ANY) continue;
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
             int button = 0;

             if (binding->direction == 0)
               {
                  if (binding->z < 0) button = 4;
                  else if (binding->z > 0)
                    button = 5;
               }
             else if (binding->direction == 1)
               {
                  if (binding->z < 0) button = 6;
                  else if (binding->z > 0)
                    button = 7;
               }
             if (button != 0)
               {
#ifndef HAVE_WAYLAND_ONLY
                  ecore_x_window_button_grab(win, button,
                                             ECORE_X_EVENT_MASK_MOUSE_DOWN,
                                             e_bindings_modifiers_to_ecore_convert(binding->mod), binding->any_mod);
#endif
               }
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

E_API void
e_bindings_wheel_ungrab(E_Binding_Context ctxt, Ecore_X_Window win)
{
   E_Binding_Wheel *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(wheel_bindings, l, binding)
     {
        if (binding->ctxt == E_BINDING_CONTEXT_ANY) continue;
        if (e_bindings_context_match(binding->ctxt, ctxt))
          {
             int button = 0;

             if (binding->direction == 0)
               {
                  if (binding->z < 0) button = 4;
                  else if (binding->z > 0)
                    button = 5;
               }
             else if (binding->direction == 1)
               {
                  if (binding->z < 0) button = 6;
                  else if (binding->z > 0)
                    button = 7;
               }
             if (button != 0)
               {
#ifndef HAVE_WAYLAND_ONLY
                  ecore_x_window_button_ungrab(win, button,
                                               e_bindings_modifiers_to_ecore_convert(binding->mod), binding->any_mod);
#endif
               }
          }
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)win;
#endif
}

E_API E_Action *
e_bindings_wheel_find(E_Binding_Context ctxt, E_Binding_Event_Wheel *ev, E_Binding_Wheel **bind_ret)
{
   E_Binding_Wheel *binding;
   Eina_List *start = NULL, *l;
   E_Action *act = NULL;

   if (bind_ret && *bind_ret)
     start = eina_list_data_find_list(wheel_bindings, *bind_ret);
   if (start)
     {
        start = start->next;
        if (!start)
          {
             *bind_ret = NULL;
             return NULL;
          }
     }
   EINA_LIST_FOREACH(start ?: wheel_bindings, l, binding)
     {
        if ((binding->direction == ev->direction) &&
            (((binding->z < 0) && (ev->z < 0)) || ((binding->z > 0) && (ev->z > 0))) &&
            ((binding->any_mod) || (binding->mod == ev->modifiers)))
          {
             if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
             if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
             act = e_action_find(binding->action);
             if (bind_ret) *bind_ret = binding;
             if (!act) continue;
             if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
          }
     }
   return act;
}

E_API E_Action *
e_bindings_wheel_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Binding_Event_Wheel *ev)
{
   E_Action *act;
   E_Binding_Wheel *binding = NULL;

   if (bindings_disabled) return NULL;
   while (1)
     {
        act = e_bindings_wheel_find(ctxt, ev, &binding);
        if (!act) break;
        if (act->func.go_wheel)
          {
             if (!act->func.go_wheel(obj, binding->params, ev))
               continue;
          }
        else if (act->func.go)
          act->func.go(obj, binding->params);
        break;
     }
   return act;
}

E_API E_Action *
e_bindings_wheel_evas_event_handle(E_Binding_Context ctxt, E_Object *obj, Evas_Event_Mouse_Wheel *ev)
{
   E_Binding_Event_Wheel event;

   e_bindings_evas_event_mouse_wheel_convert(ev, &event);

   return e_bindings_wheel_event_handle(ctxt, obj, &event);
}

E_API E_Action *
e_bindings_wheel_ecore_event_handle(E_Binding_Context ctxt, E_Object *obj, Ecore_Event_Mouse_Wheel *ev)
{
   E_Binding_Event_Wheel event;

   e_bindings_ecore_event_mouse_wheel_convert(ev, &event);

   return e_bindings_wheel_event_handle(ctxt, obj, &event);
}

E_API void
e_bindings_acpi_add(E_Binding_Context ctxt, int type, int status, const char *action, const char *params)
{
   E_Binding_Acpi *binding;

   binding = E_NEW(E_Binding_Acpi, 1);
   binding->ctxt = ctxt;
   binding->type = type;
   binding->status = status;
   if (action) binding->action = eina_stringshare_add(action);
   if (params) binding->params = eina_stringshare_add(params);
   acpi_bindings = eina_list_append(acpi_bindings, binding);
}

E_API void
e_bindings_acpi_del(E_Binding_Context ctxt, int type, int status, const char *action, const char *params)
{
   E_Binding_Acpi *binding;
   Eina_List *l;

   EINA_LIST_FOREACH(acpi_bindings, l, binding)
     {
        if ((binding->ctxt == ctxt) &&
            (binding->type == type) && (binding->status == status) &&
            (((binding->action) && (action) && (!strcmp(binding->action, action))) ||
             ((!binding->action) && (!action))) &&
            (((binding->params) && (params) && (!strcmp(binding->params, params))) ||
             ((!binding->params) && (!params))))
          {
             _e_bindings_acpi_free(binding);
             acpi_bindings = eina_list_remove_list(acpi_bindings, l);
             break;
          }
     }
}

E_API E_Action *
e_bindings_acpi_find(E_Binding_Context ctxt, E_Event_Acpi *ev, E_Binding_Acpi **bind_ret)
{
   E_Binding_Acpi *binding;
   Eina_List *l;
   E_Action *act = NULL;

   EINA_LIST_FOREACH(acpi_bindings, l, binding)
     {
        if (binding->type == ev->type)
          {
             /* if binding status is -1, then we don't compare event status */
             if (binding->status != -1)
               {
                  /* binding status is set to something, compare event status */
                  if (binding->status != ev->status) continue;
               }
             if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
             if (act && (binding->ctxt == E_BINDING_CONTEXT_ANY)) continue;
             act = e_action_find(binding->action);
             if (bind_ret) *bind_ret = binding;
             if (!act) continue;
             if (binding->ctxt != E_BINDING_CONTEXT_ANY) break;
          }
     }
   return act;
}

E_API E_Action *
e_bindings_acpi_event_handle(E_Binding_Context ctxt, E_Object *obj, E_Event_Acpi *ev)
{
   E_Action *act;
   E_Binding_Acpi *binding = NULL;

   act = e_bindings_acpi_find(ctxt, ev, &binding);
   if ((act) && (binding))
     {
        if (act->func.go_acpi)
          act->func.go_acpi(obj, binding->params, ev);
        else if (act->func.go)
          act->func.go(obj, binding->params);
        return act;
     }
   return act;
}

E_API void
e_bindings_disabled_set(Eina_Bool disabled)
{
   E_Client *ec;
   Ecore_Window win;

   if (disabled)
     {
        if ((!bindings_disabled) && (e_comp->comp_type == E_PIXMAP_TYPE_X))
          {
             e_bindings_key_ungrab(E_BINDING_CONTEXT_ANY, e_comp->root);
             E_CLIENT_FOREACH(ec)
               {
                  if (e_client_util_ignored_get(ec)) continue;
                  win = e_client_util_win_get(ec);
                  e_bindings_mouse_ungrab(E_BINDING_CONTEXT_WINDOW, win);
                  e_bindings_wheel_ungrab(E_BINDING_CONTEXT_WINDOW, win);
               }
          }
        bindings_disabled++;
     }
   else if (bindings_disabled)
     bindings_disabled--;
   if (bindings_disabled || (e_comp->comp_type != E_PIXMAP_TYPE_X)) return;
   e_bindings_key_grab(E_BINDING_CONTEXT_ANY, e_comp->root);
   E_CLIENT_FOREACH(ec)
     {
        if (e_client_util_ignored_get(ec)) continue;
        win = e_client_util_win_get(ec);
        e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, win);
        e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, win);
     }
}

static void
_e_bindings_mouse_free(E_Binding_Mouse *binding)
{
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   free(binding);
}

static void
_e_bindings_key_free(E_Binding_Key *binding)
{
   if (binding->key) eina_stringshare_del(binding->key);
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   free(binding);
}

static void
_e_bindings_edge_free(E_Binding_Edge *binding)
{
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   if (binding->timer)
     {
        E_Binding_Edge_Data *ed;

        ed = ecore_timer_del(binding->timer);
        E_FREE(ed);
     }
   free(binding);
}

static void
_e_bindings_signal_free(E_Binding_Signal *binding)
{
   if (binding->sig) eina_stringshare_del(binding->sig);
   if (binding->src) eina_stringshare_del(binding->src);
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   free(binding);
}

static void
_e_bindings_wheel_free(E_Binding_Wheel *binding)
{
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   free(binding);
}

static void
_e_bindings_acpi_free(E_Binding_Acpi *binding)
{
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   E_FREE(binding);
}

static void
_e_bindings_swipe_free(E_Binding_Swipe *binding)
{
   if (binding->action) eina_stringshare_del(binding->action);
   if (binding->params) eina_stringshare_del(binding->params);
   E_FREE(binding);
}

E_API int
e_bindings_context_match(E_Binding_Context bctxt, E_Binding_Context ctxt)
{
   if (bctxt == E_BINDING_CONTEXT_ANY &&
       !(ctxt == E_BINDING_CONTEXT_ZONE)) return 1;
   if (ctxt == E_BINDING_CONTEXT_UNKNOWN) return 0;
   if (bctxt == ctxt) return 1;
   return 0;
}

E_API E_Binding_Modifier
e_bindings_modifiers_from_ecore(unsigned int modifiers)
{
   E_Binding_Modifier mod = 0;


   mod |= (E_BINDING_MODIFIER_SHIFT * !!(modifiers & ECORE_EVENT_MODIFIER_SHIFT));
   mod |= (E_BINDING_MODIFIER_CTRL * !!(modifiers & ECORE_EVENT_MODIFIER_CTRL));
   mod |= (E_BINDING_MODIFIER_ALT * !!(modifiers & ECORE_EVENT_MODIFIER_ALT));
   mod |= (E_BINDING_MODIFIER_WIN * !!(modifiers & ECORE_EVENT_MODIFIER_WIN));
   mod |= (E_BINDING_MODIFIER_ALTGR * !!(modifiers & ECORE_EVENT_MODIFIER_ALTGR));
   /* FIXME: there is a good reason numlock was ignored. sometimes people
    * have it on, sometimes they don't, and often they have no idea. waaaay
    * back in E 0.1->0.13 or so days this caused issues thus numlock,
    * scrollock and capslock are not usable modifiers.
    *
    * if we REALLY want to be able to use numlock we need to add more binding
    * flags and config that says "REALLY pay attention to numlock for this
    * binding" field in the binding (like there is a "any_mod" flag - we need a
    * "num_lock_respect" field)
    *
    * also it should be an E_BINDING_MODIFIER_LOCK_NUM as the ecore lock flag
    * may vary from system to system as different xservers may have differing
    * modifier masks for numlock (it is queried at startup).
    *
      if (ev->modifiers & ECORE_X_LOCK_NUM) mod |= ECORE_X_LOCK_NUM;
    */

   return mod;
}

static Eina_Bool
_e_bindings_edge_cb_timer(void *data)
{
   E_Binding_Edge_Data *ed;
   E_Event_Zone_Edge *ev;
   E_Binding_Edge *binding;
   E_Action *act;
   E_Object *obj;

   ed = data;
   binding = ed->bind;
   act = ed->act;
   obj = ed->obj;
   ev = ed->ev;

   E_FREE(ed);
   binding->timer = NULL;

   if (act->func.go_edge)
     act->func.go_edge(obj, binding->params, ev);
   else if (act->func.go)
     act->func.go(obj, binding->params);

   /* Duplicate event */
   E_FREE(ev);

   return ECORE_CALLBACK_CANCEL;
}

E_API void
e_bindings_swipe_add(E_Binding_Context ctxt, double direction, double length, unsigned int fingers, double error, const char *action, const char *params)
{
   E_Binding_Swipe *binding = E_NEW(E_Binding_Swipe, 1);

   binding->ctxt = ctxt;
   binding->direction = direction;
   binding->length = length;
   binding->fingers = fingers;
   binding->error = error;
   if (action)
     binding->action = eina_stringshare_add(action);
   if (params)
     binding->params = eina_stringshare_add(params);

   swipe_bindings = eina_list_append(swipe_bindings, binding);
}

E_API void
e_bindings_swipe_del(E_Binding_Context ctxt, double direction, double length, unsigned int fingers, double error, const char *action, const char *params)
{
   E_Binding_Swipe *binding;
   Eina_List *n;

   EINA_LIST_FOREACH(swipe_bindings, n, binding)
     {
        if (binding->ctxt == ctxt &&
            binding->action == action &&
            binding->params == params &&
            EINA_DBL_EQ(direction, binding->direction) &&
            EINA_DBL_EQ(length, binding->length) &&
            EINA_DBL_EQ(fingers, binding->fingers) &&
            EINA_DBL_EQ(error, binding->error))
          {
             _e_bindings_swipe_free(binding);
             swipe_bindings = eina_list_remove_list(swipe_bindings, n);
             break;
          }
     }
}

E_API Eina_Bool
e_bindings_swipe_available(void)
{
   return eina_list_count(swipe_bindings) > 0;
}


static Eina_Bool
angle_accepted(double min, double max, double direction)
{
   if (min < direction && max > direction) return EINA_TRUE;
   if (max > 2.0*M_PI && (max - 2.0*M_PI) > direction) return EINA_TRUE;

   return EINA_FALSE;
}

E_API E_Action*
e_bindings_swipe_handle(E_Binding_Context ctxt, E_Object *obj, double direction, double length, unsigned int fingers)
{
   E_Binding_Swipe *binding;
   Eina_List *n;
   E_Action *act = NULL;

   EINA_LIST_FOREACH(swipe_bindings, n, binding)
     {
        if (binding->ctxt == ctxt &&
            binding->length < length &&
            EINA_DBL_EQ(fingers, binding->fingers) &&
            angle_accepted(binding->direction - binding->error, binding->direction + binding->error, direction))
          {
             act = e_action_find(binding->action);
             if (!act)
               {
                  ERR("Action %s cannot be found!", binding->action);
                  return NULL;
               }
             act->func.go(obj, binding->params);
          }
     }
   return act;
}

E_API Eina_Inarray*
e_bindings_swipe_find_candidates(E_Binding_Context ctxt, double direction, double length, unsigned int fingers)
{
   Eina_Inarray *ret = eina_inarray_new(sizeof(E_Binding_Swipe_Candidate), 10);
   E_Binding_Swipe *binding;
   Eina_List *n;

   EINA_LIST_FOREACH(swipe_bindings, n, binding)
     {
        if (binding->ctxt == ctxt &&
            EINA_DBL_EQ(fingers, binding->fingers) &&
            angle_accepted(binding->direction - binding->error, binding->direction + binding->error, direction))
          {
             E_Binding_Swipe_Candidate cad = {binding->action, length / binding->length};
             eina_inarray_push(ret, &cad);
          }
     }

   return ret;
}
#include "e_util_suid.h"

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif

#include <Eina.h>
#include <Ecore.h>
#include <Eldbus.h>

static uid_t uid = -1; // uid of person running me
static gid_t gid = -1; // gid of person running me
static char *user_name = NULL;
static char *group_name = NULL;

#if defined(__OpenBSD__)

static int
_check_auth(uid_t id, const char *guess)
{
   struct passwd *pwent;

   pwent = getpwuid_shadow(id);
   if (!pwent) return -1;
   if (!pwent->pw_passwd) return -1;

   return crypt_checkpass(guess, pwent->pw_passwd);
}





#elif defined(__FreeBSD__)
#include <security/pam_constants.h>

static int
_check_auth(uid_t id, const char *pw)
{
   struct passwd *pwent = getpwuid(id);

   if (!pwent) return -1;
   if (!pwent->pw_passwd) return -1;

   if (!strcmp(crypt(pw, pwent->pw_passwd), pwent->pw_passwd)) return 0;
   return -1;
}





#elif defined(HAVE_PAM)
# include <security/pam_appl.h>

typedef struct
{
   const char *user;
   const char *pw;
} Authinfo;

static int
_conv_cb(int num, const struct pam_message **msg, struct pam_response **resp, void *data)
{
   Authinfo *ai = data;
   int replies;
   struct pam_response *reply = NULL;

   reply = malloc(sizeof(struct pam_response) * num);
   if (!reply) return PAM_CONV_ERR;

   for (replies = 0; replies < num; replies++)
     {
        switch (msg[replies]->msg_style)
          {
           case PAM_PROMPT_ECHO_ON:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = strdup(ai->user);
             break;
           case PAM_PROMPT_ECHO_OFF:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = strdup(ai->pw);
             break;
           case PAM_ERROR_MSG:
           case PAM_TEXT_INFO:
             reply[replies].resp_retcode = PAM_SUCCESS;
             reply[replies].resp = NULL;
             break;
           default:
             free(reply);
             return PAM_CONV_ERR;
          }
     }
   *resp = reply;
   return PAM_SUCCESS;
}

static int
_check_auth(uid_t id, const char *pw)
{
   Authinfo ai;
   struct passwd *pwent;
   const char *user;
   const char *prof;
   const char *host;
   struct stat st;
   pam_handle_t *handle;
   int pamerr;
   struct pam_conv conv;

   pwent = getpwuid(id);
   if (!pwent) return -1;
   user = pwent->pw_name;
   if (!user) return -1;

   host = "localhost";

   prof = "login";
   if      (!stat("/etc/pam.d/enlightenment", &st)) prof = "enlightenment";
   else if (!stat("/etc/pam.d/xscreensaver", &st))  prof = "xscreensaver";
   else if (!stat("/etc/pam.d/kscreensaver", &st))  prof = "kscreensaver";
   else if (!stat("/etc/pam.d/system-auth", &st))   prof = "system-auth";
   else if (!stat("/etc/pam.d/system", &st))        prof = "system";
   else if (!stat("/etc/pam.d/xdm", &st))           prof = "xdm";
   else if (!stat("/etc/pam.d/gdm", &st))           prof = "gdm";
   else if (!stat("/etc/pam.d/kdm", &st))           prof = "kdm";

   ai.user = user;
   ai.pw = pw;

   conv.conv = _conv_cb;
   conv.appdata_ptr = &ai;
   if (pam_start(prof, user, &conv, &handle) != PAM_SUCCESS) return -1;
   if (pam_set_item(handle, PAM_USER, user) != PAM_SUCCESS) return -1;
   if (pam_set_item(handle, PAM_RHOST, host) != PAM_SUCCESS) return -1;

   pamerr = pam_authenticate(handle, 0);
   pam_end(handle, pamerr);

   if (pamerr != PAM_SUCCESS) return -1;

   return 0;
}





#else

static int
_check_auth(uid_t id, const char *pw)
{
   return -1;
}





#endif

static int polkit_auth_ok = -1;

static void
polkit_agent_response(void *data EINA_UNUSED, const Eldbus_Message *msg,
                      Eldbus_Pending *pending EINA_UNUSED)
{
   const char *name, *text;

   ecore_main_loop_quit();
   if (eldbus_message_error_get(msg, &name, &text))
     {
        fprintf(stderr, "AUTH: Could not respond to auth.\n %s:\n %s\n", name, text);
        return;
     }
   polkit_auth_ok = 0;
   fprintf(stderr, "AUTH: OK\n");
}

int
polkit_auth(const char *cookie, unsigned int auth_uid)
{
   Eldbus_Connection *c;
   Eldbus_Object *obj;
   Eldbus_Proxy *proxy;
   Eldbus_Message *m;
   Eldbus_Message_Iter *iter, *subj, *array, *dict, *vari;

   eina_init();
   ecore_init();
   eldbus_init();
   c = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
#define BARF(str) do { \
   fprintf(stderr, "AUTH: POLKIT: %s\n", str); \
   return -1; \
} while (0)
   if (!c) BARF("Cannot get session dbus");
   obj = eldbus_object_get(c, "org.freedesktop.PolicyKit1",
                           "/org/freedesktop/PolicyKit1/Authority");
   if (!obj) BARF("Cannot get obj: org.freedesktop.PolicyKit1 /org/freedesktop/PolicyKit1/Authority");
   proxy = eldbus_proxy_get(obj, "org.freedesktop.PolicyKit1.Authority");
   if (!proxy) BARF("Cannot proxy: org.freedesktop.PolicyKit1.Authority");
   m = eldbus_proxy_method_call_new(proxy, "AuthenticationAgentResponse2");
   if (!m) BARF("Cannot get method call: AuthenticationAgentResponse2");
   iter = eldbus_message_iter_get(m);
   if (!iter) BARF("Cannot set iter on proxy");
   if (!eldbus_message_iter_arguments_append(iter, "us", uid, cookie))
     BARF("Cannot append 'us' args");
   if (!eldbus_message_iter_arguments_append(iter, "(sa{sv})", &subj))
     BARF("Cannot append '(sa{sv})' args");
   if (!eldbus_message_iter_basic_append(subj, 's', "unix-user"))
     BARF("Cannot append 's' arg for unix-user");
   if (!eldbus_message_iter_arguments_append(subj, "a{sv}", &array))
     BARF("Cannot append 'a{sv}' args");
   if (!eldbus_message_iter_arguments_append(array, "{sv}", &dict))
     BARF("Cannot append '{sv}' args");
   if (!eldbus_message_iter_basic_append(dict, 's', "uid"))
     BARF("Cannot append 's' arg for uid");
   vari = eldbus_message_iter_container_new(dict, 'v', "u");
   if (!vari)
     BARF("Cannot create new iter container");
   if (!eldbus_message_iter_basic_append(vari, 'u', auth_uid))
     BARF("Cannot append 'u' arg for auth_uid");
   eldbus_message_iter_container_close(dict, vari);
   eldbus_message_iter_container_close(array, dict);
   eldbus_message_iter_container_close(subj, array);
   eldbus_message_iter_container_close(iter, subj);
   eldbus_proxy_send(proxy, m, polkit_agent_response, NULL, -1);

   ecore_main_loop_begin();

   eldbus_connection_unref(c);
   eldbus_shutdown();
   ecore_shutdown();
   eina_shutdown();
   return polkit_auth_ok;
}

int
main(int argc, char **argv)
{
   ssize_t rd;
   char pw[4096], *p;
   int polkit_mode = 0;
   char polkit_cookie[4096];
   unsigned int polkit_uid = 0;

   if (argc < 2)
     {
        fprintf(stderr, "This is an internal tool for Enlightenment\n");
        fprintf(stderr, "Options: pw | pk\n");
        goto err;
     }
   if      (!strcmp(argv[1], "pw")) polkit_mode = 0;
   else if (!strcmp(argv[1], "pk")) polkit_mode = 1;

   // read passwd from stdin
   if (polkit_mode == 0)
     {
        rd = read(0, pw, 3);
        if (rd != 3)
          {
             fprintf(stderr, "AUTH: Error. Can't read passwd preable\n");
             goto err;
          }
        if (!((pw[0] == 'p') && (pw[1] == 'w') && (pw[2] == ' ')))
          {
             fprintf(stderr, "AUTH: Error. Preable is wrong\n");
             goto err;
          }
        rd = read(0, pw, sizeof(pw) - 1);
        if (rd < 0)
          {
             fprintf(stderr, "AUTH: Error. Can't read passwd on stdin\n");
             goto err;
          }
        pw[rd] = 0;
        for (p = pw; *p; p++)
          {
             if ((*p == '\r') || (*p == '\n'))
               {
                  *p = 0;
                  break;
               }
          }
     }
   else if (polkit_mode == 1)
     {
        unsigned int pos = 0;

        // read "cookie-string uid-string password...[\r|\n|EOF]"
        for (;;) // cookie
          {
             rd = read(0, pw + pos, 1);
             if (rd < 0)
               {
                  fprintf(stderr, "AUTH: POLKIT: Error. Can't read polkit cookie on stdin\n");
                  goto err;
               }
             if (pw[pos] == ' ')
               {
                  memcpy(polkit_cookie, pw, pos);
                  polkit_cookie[pos] = 0;
                  fprintf(stderr, "AUTH: POLKIT: [%s]\n", polkit_cookie);
                  pos = 0;
                  break;
               }
             else
               {
                  pos++;
                  if (pos > 4000)
                    {
                       fprintf(stderr, "AUTH: POLKIT: Error. Polkit cookie too long\n");
                       return -10;
                    }
               }
          }
        for (;;) // uid
          {
             rd = read(0, pw + pos, 1);
             if (rd < 0)
               {
                  fprintf(stderr, "AUTH: Error. Can't read polkit uid on stdin\n");
                  goto err;
               }
             if (pw[pos] == ' ')
               {
                  pw[pos] = 0;
                  polkit_uid = atoi(pw);
                  fprintf(stderr, "AUTH: UID: [%u]\n", polkit_uid);
                  break;
               }
             else
               {
                  pos++;
                  if (pos > 4000)
                    {
                       fprintf(stderr, "AUTH: Error. Polkit uid too long\n");
                       return -11;
                    }
               }
          }
        // password
        fprintf(stderr, "AUTH: readpass...\n");
        rd = read(0, pw, sizeof(pw) - 1);
        if (rd < 0)
          {
             fprintf(stderr, "AUTH: Error. Can't read passwd on stdin\n");
             goto err;
          }
        pw[rd] = 0;
        for (p = pw; *p; p++)
          {
             if ((*p == '\r') || (*p == '\n'))
               {
                  *p = 0;
                  break;
               }
          }
     }

   // ok to fail - auth will just possibly fail then
   e_setuid_setup(&uid, &gid, &user_name, &group_name);

   if (_check_auth(polkit_mode ? polkit_uid : uid, pw) == 0)
     {
        fprintf(stderr, "AUTH: Password OK\n");
        if (polkit_mode == 1)
          {
             if (polkit_auth(polkit_cookie, polkit_uid) == 0)
               {
                  fprintf(stderr, "AUTH: Polkit AuthenticationAgentResponse2 success\n");
                  return 0;
               }
             fprintf(stderr, "AUTH: Polkit AuthenticationAgentResponse2 failure\n");
             return -2;
          }
        return 0;
     }
err:
   fprintf(stderr, "AUTH: Password auth fail\n");
   return -1;
}
#include "e.h"

static int _e_client_hooks_delete = 0;
static int _e_client_hooks_walking = 0;

E_API int E_EVENT_CLIENT_ADD = -1;
E_API int E_EVENT_CLIENT_REMOVE = -1;
E_API int E_EVENT_CLIENT_ZONE_SET = -1;
E_API int E_EVENT_CLIENT_DESK_SET = -1;
E_API int E_EVENT_CLIENT_RESIZE = -1;
E_API int E_EVENT_CLIENT_MOVE = -1;
E_API int E_EVENT_CLIENT_SHOW = -1;
E_API int E_EVENT_CLIENT_HIDE = -1;
E_API int E_EVENT_CLIENT_ICONIFY = -1;
E_API int E_EVENT_CLIENT_UNICONIFY = -1;
E_API int E_EVENT_CLIENT_STACK = -1;
E_API int E_EVENT_CLIENT_FOCUS_IN = -1;
E_API int E_EVENT_CLIENT_FOCUS_OUT = -1;
E_API int E_EVENT_CLIENT_PROPERTY = -1;
E_API int E_EVENT_CLIENT_FULLSCREEN = -1;
E_API int E_EVENT_CLIENT_UNFULLSCREEN = -1;

static Eina_Hash *clients_hash[2] = {NULL}; // pixmap->client

static unsigned int focus_track_frozen = 0;

static int warp_to = 0;
static int warp_to_x = 0;
static int warp_to_y = 0;
static int warp_x[2] = {0}; //{cur,prev}
static int warp_y[2] = {0}; //{cur,prev}
static Ecore_Timer *warp_timer = NULL;

static E_Client *focused = NULL;
static E_Client *warp_client = NULL;
static E_Client *ecmove = NULL;
static E_Client *ecresize = NULL;
static E_Client *action_client = NULL;
static E_Drag *client_drag = NULL;

static Eina_List *focus_stack = NULL;
static Eina_List *raise_stack = NULL;

static Eina_Bool comp_grabbed = EINA_FALSE;
static Evas_Object *action_rect;

static Eina_List *handlers = NULL;
//static Eina_Bool client_grabbed = EINA_FALSE;

static Ecore_Event_Handler *action_handler_key = NULL;
static Ecore_Event_Handler *action_handler_mouse = NULL;
static Ecore_Timer *action_timer = NULL;
static Eina_Rectangle action_orig = {0, 0, 0, 0};

static E_Client_Layout_Cb _e_client_layout_cb = NULL;

EINTERN void e_client_focused_set(E_Client *ec);

static Eina_Inlist *_e_client_hooks[E_CLIENT_HOOK_LAST] = {NULL};

///////////////////////////////////////////

static Eina_Bool
_e_client_cb_efreet_cache_update(void *data EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   const Eina_List *l;
   E_Client *ec;

   /* mark all clients for desktop/icon updates */
   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        E_FREE_FUNC(ec->desktop, efreet_desktop_free);
        if (e_object_is_del(E_OBJECT(ec))) continue;
        ec->changes.icon = 1;
        EC_CHANGED(ec);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_client_cb_config_icon_theme(void *data EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   const Eina_List *l;
   E_Client *ec;

   /* mark all clients for desktop/icon updates */
   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (e_object_is_del(E_OBJECT(ec))) continue;
        ec->changes.icon = 1;
        EC_CHANGED(ec);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_client_cb_config_mode(void *data EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   const Eina_List *l;
   E_Client *ec;
   E_Layer layer;

   /* move fullscreen borders above everything */

   if (!e_config->allow_above_fullscreen)
     layer = E_LAYER_CLIENT_FULLSCREEN;
   else
     return ECORE_CALLBACK_RENEW;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (e_object_is_del(E_OBJECT(ec))) continue;
        if ((ec->fullscreen) || (ec->need_fullscreen))
          {
             ec->fullscreen = 0;
             evas_object_layer_set(ec->frame, layer);
             ec->fullscreen = 1;
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_client_cb_pointer_warp(void *data EINA_UNUSED, int type EINA_UNUSED, E_Event_Pointer_Warp *ev)
{
   if (ecmove)
     evas_object_move(ecmove->frame, ecmove->x + (ev->curr.x - ev->prev.x), ecmove->y + (ev->curr.y - ev->prev.y));
   return ECORE_CALLBACK_RENEW;
}


static Eina_Bool
_e_client_cb_desk_window_profile_change(void *data EINA_UNUSED, int type EINA_UNUSED, E_Event_Desk_Window_Profile_Change *ev EINA_UNUSED)
{
   const Eina_List *l;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (e_object_is_del(E_OBJECT(ec))) continue;
        ec->e.fetch.profile = 1;
        EC_CHANGED(ec);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_client_cb_drag_finished(E_Drag *drag, int dropped EINA_UNUSED)
{
   E_Client *ec;

   ec = drag->data;
   UNREFD(ec, 1);
   e_object_unref(E_OBJECT(ec));
   client_drag = NULL;
}

static void
_e_client_desk_window_profile_wait_desk_delfn(void *data, void *obj)
{
   E_Client *ec = data;
   E_Desk *desk = obj, *new_desk;
   const char *p;
   int i;

   if (e_object_is_del(E_OBJECT(ec))) return;

   ec->e.state.profile.wait_desk_delfn = NULL;
   eina_stringshare_replace(&ec->e.state.profile.wait, NULL);
   if (ec->e.state.profile.wait_desk)
     e_object_unref(E_OBJECT(ec->e.state.profile.wait_desk));
   ec->e.state.profile.wait_desk = NULL;
   ec->e.state.profile.wait_for_done = 0;

   if (!ec->e.state.profile.use) return;

   new_desk = e_comp_desk_window_profile_get(desk->window_profile);
   if (new_desk)
     e_client_desk_set(ec, new_desk);
   else
     {
        for (i = 0; i < ec->e.state.profile.num; i++)
          {
             p = ec->e.state.profile.available_list[i];
             new_desk = e_comp_desk_window_profile_get(p);
             if (new_desk)
               {
                  e_client_desk_set(ec, new_desk);
                  break;
               }
          }
     }
}
////////////////////////////////////////////////


static Eina_Bool
_e_client_pointer_warp_to_center_timer(void *data EINA_UNUSED)
{
   if (warp_to && warp_client)
     {
        int x, y;
        double spd;

        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        /* move hasn't happened yet */
        if ((x == warp_x[1]) && (y == warp_y[1]))
           return EINA_TRUE;
        if ((abs(x - warp_x[0]) > 5) || (abs(y - warp_y[0]) > 5))
          {
             /* User moved the mouse, so stop warping */
             warp_to = 0;
             goto cleanup;
          }

        spd = e_config->pointer_warp_speed;
        warp_x[1] = x = warp_x[0];
        warp_y[1] = y = warp_y[0];
        warp_x[0] = (x * (1.0 - spd)) + (warp_to_x * spd);
        warp_y[0] = (y * (1.0 - spd)) + (warp_to_y * spd);
        if ((warp_x[0] == x) && (warp_y[0] == y))
          {
             warp_x[0] = warp_to_x;
             warp_y[0] = warp_to_y;
             warp_to = 0;
             goto cleanup;
          }
        ecore_evas_pointer_warp(e_comp->ee, warp_x[0], warp_y[0]);
        return ECORE_CALLBACK_RENEW;
     }
cleanup:
   E_FREE_FUNC(warp_timer, ecore_timer_del);
   if (warp_client)
     {
        warp_x[0] = warp_x[1] = warp_y[0] = warp_y[1] = -1;
        if (warp_client->modal)
          {
             warp_client = NULL;
             return ECORE_CALLBACK_CANCEL;
          }
        e_focus_event_mouse_in(warp_client);
        if (warp_client->iconic)
          {
             if (!warp_client->lock_user_iconify)
               e_client_uniconify(warp_client);
          }
        if (warp_client->shaded)
          {
             if (!warp_client->lock_user_shade)
               e_client_unshade(warp_client, warp_client->shade_dir);
          }

        if (!warp_client->lock_focus_out)
          {
             evas_object_focus_set(warp_client->frame, 1);
             e_client_focus_latest_set(warp_client);
          }
        warp_client = NULL;
     }
   return ECORE_CALLBACK_CANCEL;
}

////////////////////////////////////////////////

static void
_e_client_hooks_clean(void)
{
   Eina_Inlist *l;
   E_Client_Hook *ch;
   unsigned int x;

   for (x = 0; x < E_CLIENT_HOOK_LAST; x++)
     EINA_INLIST_FOREACH_SAFE(_e_client_hooks[x], l, ch)
       {
          if (!ch->delete_me) continue;
          _e_client_hooks[x] = eina_inlist_remove(_e_client_hooks[x], EINA_INLIST_GET(ch));
          free(ch);
       }
}

static Eina_Bool
_e_client_hook_call(E_Client_Hook_Point hookpoint, E_Client *ec)
{
   E_Client_Hook *ch;

   e_object_ref(E_OBJECT(ec));
   _e_client_hooks_walking++;
   EINA_INLIST_FOREACH(_e_client_hooks[hookpoint], ch)
     {
        if (ch->delete_me) continue;
        ch->func(ch->data, ec);
        if ((hookpoint != E_CLIENT_HOOK_DEL) &&
          (hookpoint != E_CLIENT_HOOK_MOVE_END) &&
          (hookpoint != E_CLIENT_HOOK_RESIZE_END) &&
          (hookpoint != E_CLIENT_HOOK_FOCUS_UNSET) &&
          e_object_is_del(E_OBJECT(ec)))
          break;
     }
   _e_client_hooks_walking--;
   if ((_e_client_hooks_walking == 0) && (_e_client_hooks_delete > 0))
     _e_client_hooks_clean();
   return !!e_object_unref(E_OBJECT(ec));
}

///////////////////////////////////////////

static void
_e_client_event_simple_free(void *d EINA_UNUSED, E_Event_Client *ev)
{
   UNREFD(ev->ec, 3);
   e_object_unref(E_OBJECT(ev->ec));
   free(ev);
}

static void
_e_client_event_simple(E_Client *ec, int type)
{
   E_Event_Client *ev;

   ev = E_NEW(E_Event_Client, 1);
   ev->ec = ec;
   REFD(ec, 3);
   e_object_ref(E_OBJECT(ec));
   ecore_event_add(type, ev, (Ecore_End_Cb)_e_client_event_simple_free, NULL);
}

static void
_e_client_event_property(E_Client *ec, int prop)
{
   E_Event_Client_Property *ev;

   ev = E_NEW(E_Event_Client_Property, 1);
   ev->ec = ec;
   ev->property = prop;
   REFD(ec, 33);
   e_object_ref(E_OBJECT(ec));
   ecore_event_add(E_EVENT_CLIENT_PROPERTY, ev, (Ecore_End_Cb)_e_client_event_simple_free, NULL);
}

static void
_e_client_event_desk_set_free(void *d EINA_UNUSED, E_Event_Client_Desk_Set *ev)
{
   UNREFD(ev->ec, 4);
   e_object_unref(E_OBJECT(ev->ec));
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_client_event_zone_set_free(void *d EINA_UNUSED, E_Event_Client_Zone_Set *ev)
{
   UNREFD(ev->ec, 5);
   e_object_unref(E_OBJECT(ev->ec));
   e_object_unref(E_OBJECT(ev->zone));
   free(ev);
}

////////////////////////////////////////////////

static int
_e_client_action_input_win_del(void)
{
   if (!comp_grabbed) return 0;

   comp_grabbed = 0;
   E_FREE_FUNC(action_rect, evas_object_del);
   e_comp_shape_queue();
   e_comp_ungrab_input(1, 1);
   return 1;
}

static void
_e_client_action_finish(void)
{
   if (comp_grabbed)
     _e_client_action_input_win_del();

   if (action_handler_key && action_client)
     evas_object_freeze_events_set(action_client->frame, 0);
   E_FREE_FUNC(action_timer, ecore_timer_del);
   E_FREE_FUNC(action_handler_key,  ecore_event_handler_del);
   E_FREE_FUNC(action_handler_mouse, ecore_event_handler_del);
   if (action_client)
     {
        action_client->keyboard_resizing = 0;
        if (action_client->internal_elm_win)
          ecore_event_window_ignore_events(elm_win_window_id_get(action_client->internal_elm_win), 0);
     }
   action_client = NULL;
}

static void
_e_client_mouse_action_end(E_Client *ec)
{
   if (!ec->cur_mouse_action) return;
   if (ec->cur_mouse_action->func.end_mouse)
     ec->cur_mouse_action->func.end_mouse(E_OBJECT(ec), "", NULL);
   else if (ec->cur_mouse_action->func.end)
     ec->cur_mouse_action->func.end(E_OBJECT(ec), "");
   E_FREE_FUNC(ec->cur_mouse_action, e_object_unref);
}

E_API void
e_client_revert_focus(E_Client *ec)
{
   E_Client *pec;
   E_Desk *desk;

   if (stopping) return;

   if (!ec->focused) return;
   if (!ec->zone) return;

   desk = e_desk_current_get(ec->zone);
   if (ec->desk == desk)
     evas_object_focus_set(ec->frame, 0);

   if (ec->stack.prev)
     {
        ec->stack.focus_skip = 1;
        pec = e_client_stack_active_adjust(ec);
        ec->stack.focus_skip = 0;
        if ((pec != ec) && (!pec->iconic))
          evas_object_focus_set(pec->frame, 1);
        else
          {
             if ((e_object_is_del(E_OBJECT(ec))) || (ec->iconic))
               {
                  Eina_Bool unlock = ec->lock_focus_out;
                  ec->lock_focus_out = 1;
                  pec = e_desk_last_focused_focus(desk);
                  ec->lock_focus_out = unlock;
               }
          }
     }
   else if ((ec->parent) && (ec->parent->desk == desk))
     {
        E_Client *goal_ec = NULL;
        if (ec->parent->modal != ec && ec->parent->modal) {
          goal_ec = ec->parent->modal;
        } else {
          goal_ec = ec->parent;
        }
        evas_object_focus_set(goal_ec->frame, 1);
        if (e_config->raise_on_revert_focus)
          evas_object_raise(goal_ec->frame);
     }
   else if (e_config->focus_revert_on_hide_or_close)
     {
        Eina_Bool unlock = ec->lock_focus_out;
        ec->lock_focus_out = 1;
        pec = e_desk_last_focused_focus(desk);
        ec->lock_focus_out = unlock;
     }
   else if (e_config->focus_policy == E_FOCUS_MOUSE)
     {
        pec = e_client_under_pointer_get(desk, ec);
        if (pec)
          evas_object_focus_set(pec->frame, 1);
        /* no autoraise/revert here because it's probably annoying */
     }
}

static void
_e_client_free(E_Client *ec)
{
   if (focused == ec)
     {
        focused = NULL;
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
           ecore_x_window_focus(e_comp->root);
#endif
     }
   if (ec->desk)
     ec->desk->fullscreen_clients = eina_list_remove(ec->desk->fullscreen_clients, ec);
   if (ec->restore_zone_id)
     {
        eina_stringshare_del(ec->restore_zone_id);
        ec->restore_zone_id = NULL;
     }
   if (ec->pixmap)
     {
        if (e_pixmap_free(ec->pixmap))
          e_pixmap_client_set(ec->pixmap, NULL);
        ec->pixmap = NULL;
     }
   if (ec->frame)
     {
        e_comp_object_redirected_set(ec->frame, 0);
        e_comp_object_render_update_del(ec->frame);
     }
   E_OBJECT(ec)->references++;
   if (ec->fullscreen)
     {
        if (!ec->desk->fullscreen_clients) e_comp_render_queue();
     }
   if (ec->new_client)
     e_comp->new_clients--;
   if (ec->e.state.profile.use)
     {
        e_client_desk_window_profile_wait_desk_set(ec, NULL);

        if (ec->e.state.profile.available_list)
          {
             int i;
             for (i = 0; i < ec->e.state.profile.num; i++)
               eina_stringshare_replace(&ec->e.state.profile.available_list[i], NULL);
             E_FREE(ec->e.state.profile.available_list);
          }

        ec->e.state.profile.num = 0;

        eina_stringshare_replace(&ec->e.state.profile.set, NULL);
        eina_stringshare_replace(&ec->e.state.profile.wait, NULL);
        eina_stringshare_replace(&ec->e.state.profile.name, NULL);
        ec->e.state.profile.wait_for_done = 0;
        ec->e.state.profile.use = 0;
     }

   if (ec->e.state.video_parent && ec->e.state.video_parent_client)
     {
        ec->e.state.video_parent_client->e.state.video_child =
          eina_list_remove(ec->e.state.video_parent_client->e.state.video_child, ec);
     }
   if (ec->e.state.video_child)
     {
        E_Client *tmp;

        EINA_LIST_FREE(ec->e.state.video_child, tmp)
          tmp->e.state.video_parent_client = NULL;
     }
   E_FREE_FUNC(ec->internal_elm_win, evas_object_del);
   E_FREE_FUNC(ec->desktop, efreet_desktop_free);
   E_FREE_FUNC(ec->post_job, ecore_idle_enterer_del);

   E_FREE_FUNC(ec->kill_timer, ecore_timer_del);
   E_FREE_LIST(ec->pending_resize, free);

   if (ec->remember)
     {
        E_Remember *rem;

        rem = ec->remember;
        ec->remember = NULL;
        e_remember_unuse(rem);
     }
   ec->group = eina_list_free(ec->group);
   ec->transients = eina_list_free(ec->transients);
   ec->stick_desks = eina_list_free(ec->stick_desks);

   E_FREE(ec->netwm.extra_types);
   eina_stringshare_replace(&ec->border.name, NULL);
   eina_stringshare_replace(&ec->bordername, NULL);
   eina_stringshare_replace(&ec->icccm.name, NULL);
   eina_stringshare_replace(&ec->icccm.class, NULL);
   eina_stringshare_replace(&ec->icccm.title, NULL);
   eina_stringshare_replace(&ec->icccm.icon_name, NULL);
   eina_stringshare_replace(&ec->icccm.machine, NULL);
   eina_stringshare_replace(&ec->icccm.window_role, NULL);
   if ((ec->icccm.command.argc > 0) && (ec->icccm.command.argv))
     {
        int i;

        for (i = 0; i < ec->icccm.command.argc; i++)
          free(ec->icccm.command.argv[i]);
        E_FREE(ec->icccm.command.argv);
     }
   eina_stringshare_replace(&ec->netwm.name, NULL);
   eina_stringshare_replace(&ec->netwm.icon_name, NULL);
   eina_stringshare_replace(&ec->internal_icon, NULL);
   eina_stringshare_replace(&ec->internal_icon_key, NULL);
   eina_stringshare_replace(&ec->uuid, NULL);

   focus_stack = eina_list_remove(focus_stack, ec);
   raise_stack = eina_list_remove(raise_stack, ec);

   e_hints_client_list_set();
   if (ec->e.state.profile.wait_desk)
     {
        e_object_delfn_del(E_OBJECT(ec->e.state.profile.wait_desk),
                           ec->e.state.profile.wait_desk_delfn);
        ec->e.state.profile.wait_desk_delfn = NULL;
        e_object_unref(E_OBJECT(ec->e.state.profile.wait_desk));
     }
   if (ec->stack.prev) ec->stack.prev->stack.next = ec->stack.next;
   if (ec->stack.next) ec->stack.next->stack.prev = ec->stack.prev;

   ec->e.state.profile.wait_desk = NULL;
   evas_object_del(ec->frame);
   E_OBJECT(ec)->references--;

   free(ec);
}

static void
_e_client_del(E_Client *ec)
{
   E_Client *child;
   E_Client_Volume_Sink *sink;

   EINA_LIST_FREE(ec->sinks, sink)
     e_client_volume_sink_remove(ec, sink);
   for (child = ec->stack.next; child; child = child->stack.next)
     e_client_act_close_begin(child);
   ec->changed = 0;
   focus_stack = eina_list_remove(focus_stack, ec);
   raise_stack = eina_list_remove(raise_stack, ec);
   if (ec->exe_inst)
     {
        if (ec->exe_inst->phony && (eina_list_count(ec->exe_inst->clients) == 1))
          {
             if (e_exec_phony_del(ec->exe_inst))
               ec->exe_inst = NULL;
          }
        else
          {
             if (!ec->exe_inst->deleted)
               {
                  ec->exe_inst->clients = eina_list_remove(ec->exe_inst->clients, ec);
                  ec->exe_inst = NULL;
               }
          }
     }

   _e_client_mouse_action_end(ec);
   if (action_client == ec) _e_client_action_finish();
   e_pointer_type_pop(e_comp->pointer, ec, NULL);

   if (warp_client == ec)
     {
        E_FREE_FUNC(warp_timer, ecore_timer_del);
        warp_client = NULL;
     }

   if ((client_drag) && (client_drag->data == ec))
     {
        e_object_del(E_OBJECT(client_drag));
        client_drag = NULL;
     }
   if (ec->border_menu) e_menu_deactivate(ec->border_menu);
   if (!stopping)
     {
        e_client_comp_hidden_set(ec, 1);
        if (ec->frame) evas_object_pass_events_set(ec->frame, 1);
     }

   E_FREE_FUNC(ec->border_locks_dialog, e_object_del);
   E_FREE_FUNC(ec->border_remember_dialog, e_object_del);
   E_FREE_FUNC(ec->border_border_dialog, e_object_del);
   E_FREE_FUNC(ec->border_prop_dialog, e_object_del);
   E_FREE_FUNC(ec->color_editor, evas_object_del);
   e_int_client_menu_del(ec);
   E_FREE_FUNC(ec->raise_timer, ecore_timer_del);

   if (ec->internal_elm_win)
     evas_object_hide(ec->internal_elm_win);

   if (ec->focused)
     e_client_revert_focus(ec);
   if (ec->frame) evas_object_focus_set(ec->frame, 0);

   E_FREE_FUNC(ec->ping_poller, ecore_poller_del);
   eina_hash_del_by_key(clients_hash[e_pixmap_type_get(ec->pixmap)], &ec->pixmap);
   /* must be called before parent/child clear */
   _e_client_hook_call(E_CLIENT_HOOK_DEL, ec);
   E_FREE(ec->comp_data);

   if ((!ec->new_client) && (!stopping))
     _e_client_event_simple(ec, E_EVENT_CLIENT_REMOVE);

   if (ec->parent)
     {
        ec->parent->transients = eina_list_remove(ec->parent->transients, ec);
        ec->parent = NULL;
     }
   EINA_LIST_FREE(ec->transients, child)
     child->parent = NULL;

   if (ec->leader)
     {
        ec->leader->group = eina_list_remove(ec->leader->group, ec);
        if (ec->leader->modal == ec)
          ec->leader->modal = NULL;
        ec->leader = NULL;
     }
   EINA_LIST_FREE(ec->group, child)
     child->leader = NULL;

   e_comp->clients = eina_list_remove(e_comp->clients, ec);
   if (ec->frame) e_comp_object_render_update_del(ec->frame);
}

///////////////////////////////////////////

static Eina_Bool
_e_client_cb_kill_timer(void *data)
{
   E_Client *ec = data;

// dont wait until it's hung -
//   if (ec->hung)
//     {
   if (ec->netwm.pid > 1)
     kill(ec->netwm.pid, SIGKILL);
//     }
   ec->kill_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_client_cb_ping_poller(void *data)
{
   E_Client *ec;

   ec = data;
   if (ec->ping_ok)
     {
        if (ec->hung)
          {
             ec->hung = 0;
             evas_object_smart_callback_call(ec->frame, "unhung", NULL);
             E_FREE_FUNC(ec->kill_timer, ecore_timer_del);
          }
     }
   else
     {
        /* if time between last ping and now is greater
         * than half the ping interval... */
        if ((ecore_loop_time_get() - ec->ping) >
            ((e_config->ping_clients_interval *
              ecore_poller_poll_interval_get(ECORE_POLLER_CORE)) / 2.0))
          {
             if (!ec->hung)
               {
                  ec->hung = 1;
                  evas_object_smart_callback_call(ec->frame, "hung", NULL);
                  /* FIXME: if below dialog is up - hide it now */
               }
             if (ec->delete_requested)
               {
                  /* FIXME: pop up dialog saying app is hung - kill client, or pid */
                  e_client_act_kill_begin(ec);
               }
          }
     }
   ec->ping_poller = NULL;
   e_client_ping(ec);
   return ECORE_CALLBACK_CANCEL;
}

///////////////////////////////////////////

static int
_e_client_action_input_win_new(void)
{
   if (comp_grabbed)
     {
        CRI("DOUBLE COMP GRAB! ACK!!!!");
        return 1;
     }
   comp_grabbed = e_comp_grab_input(1, 1);
   if (!comp_grabbed) _e_client_action_input_win_del();
   return comp_grabbed;
}

static void
_e_client_action_event_grabber_mouse_up(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Event_Mouse_Down *ev = event_info;
   E_Binding_Event_Mouse_Button ev2;

   if (!action_client) return;
   e_bindings_evas_event_mouse_button_convert(ev, &ev2);
   e_client_mouse_up(action_client, ev->button, &ev->output, &ev2);
   if (!action_client) return;
   if (action_client->moving)
     e_client_act_move_end(action_client, NULL);
   if (!action_client) return;
   if (e_client_util_resizing_get(action_client))
     e_client_act_resize_end(action_client, NULL);
}

static void
_e_client_action_event_grabber_mouse_move(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev = event_info;
   if (action_client)
     e_client_mouse_move(action_client, &ev->cur.output);
}

static void
_e_client_action_event_grabber_init(E_Client *ec)
{
   action_rect = e_comp_canvas_event_grabber_add();
   evas_object_event_callback_add(action_rect, EVAS_CALLBACK_MOUSE_UP, _e_client_action_event_grabber_mouse_up, NULL);
   evas_object_event_callback_add(action_rect, EVAS_CALLBACK_MOUSE_MOVE, _e_client_action_event_grabber_mouse_move, NULL);
   evas_object_smart_member_add(ec->frame, action_rect);
   evas_object_resize(action_rect, e_comp->w, e_comp->h);
   evas_object_layer_set(action_rect, EVAS_LAYER_MAX - 100);
   evas_object_show(action_rect);
   evas_object_event_grabber_freeze_when_visible_set(action_rect, 1);
   e_comp_shape_queue();
}

static void
_e_client_action_init(E_Client *ec)
{
   action_orig.x = ec->x;
   action_orig.y = ec->y;
   action_orig.w = ec->w;
   action_orig.h = ec->h;

   if (action_client)
     {
        action_client->keyboard_resizing = 0;
        if (action_client->internal_elm_win)
          ecore_event_window_ignore_events(elm_win_window_id_get(action_client->internal_elm_win), 0);
     }
   action_client = ec;
   if (ec->internal_elm_win)
     ecore_event_window_ignore_events(elm_win_window_id_get(ec->internal_elm_win), 1);
}

static void
_e_client_action_restore_orig(E_Client *ec)
{
   if (action_client != ec)
     return;

   evas_object_geometry_set(ec->frame, action_orig.x, action_orig.y, action_orig.w, action_orig.h);
}

static int
_e_client_key_down_modifier_apply(int modifier, int value)
{
   if (modifier & ECORE_EVENT_MODIFIER_CTRL) return value * 5;
   else if (modifier & ECORE_EVENT_MODIFIER_ALT)
     {
        value /= 5;
        if (value) return value;
        else return 1;
     }

   return value;
}


static int
_e_client_move_begin(E_Client *ec)
{
   if ((ec->fullscreen) || (ec->lock_user_location))
     return 0;

/*
   if (client_grabbed && !e_grabinput_get(e_client_util_pwin_get(ec), 0, e_client_util_pwin_get(ec)))
     {
        client_grabbed = 0;
        return 0;
     }
*/
   if (!_e_client_action_input_win_new()) return 0;
   ec->moving = 1;
   ecmove = ec;
   if (!ec->lock_user_stacking)
     {
        if (e_config->border_raise_on_mouse_action)
          evas_object_raise(ec->frame);
     }

   _e_client_hook_call(E_CLIENT_HOOK_MOVE_BEGIN, ec);
   if (!ec->moving)
     {
        if (ecmove == ec) ecmove = NULL;
        _e_client_action_input_win_del();
        return 0;
     }
   E_FREE_FUNC(ec->raise_timer, ecore_timer_del);
   _e_client_action_event_grabber_init(ec);
   return 1;
}

static int
_e_client_move_end(E_Client *ec)
{
   //if (client_grabbed)
     //{
        //e_grabinput_release(e_client_util_pwin_get(ec), e_client_util_pwin_get(ec));
        //client_grabbed = 0;
     //}
   _e_client_action_input_win_del();
   e_pointer_mode_pop(ec, E_POINTER_MOVE);
   ec->moving = 0;
   _e_client_hook_call(E_CLIENT_HOOK_MOVE_END, ec);

   ecmove = NULL;
   return 1;
}

static Eina_Bool
_e_client_action_move_timeout(void *data EINA_UNUSED)
{
   _e_client_move_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_client_action_move_timeout_add(void)
{
   double timeout = e_config->border_keyboard.timeout;
   E_FREE_FUNC(action_timer, ecore_timer_del);
   if (!EINA_DBL_NONZERO(timeout)) timeout = 5.0;
   action_timer = ecore_timer_loop_add(timeout, _e_client_action_move_timeout, NULL);
}

static Eina_Bool
_e_client_move_key_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Key *ev = event;
   int x, y, dx, dy;

   if (!comp_grabbed) return ECORE_CALLBACK_RENEW;
   if (!action_client)
     {
        ERR("no action_client!");
        goto stop;
     }

   x = action_client->x;
   y = action_client->y;

   dx = e_config->border_keyboard.move.dx;
   dx = _e_client_key_down_modifier_apply(ev->modifiers, dx);
   dy = e_config->border_keyboard.move.dy;
   dy = _e_client_key_down_modifier_apply(ev->modifiers, dy);

   switch (e_util_key_geometry_action_get(ev->key, &x, &y, dx, dy))
     {
      case E_UTIL_ACTION_DONE:
        goto stop;
        break;
      case E_UTIL_ACTION_ABORT:
        _e_client_action_restore_orig(action_client);
        goto stop;
        break;
      case E_UTIL_ACTION_DO:
        evas_object_move(action_client->frame, x, y);
        _e_client_action_move_timeout_add();
        break;
      case E_UTIL_ACTION_NONE:
      default:
        break;
     }

   return ECORE_CALLBACK_PASS_ON;

stop:
   _e_client_move_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_e_client_move_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (!comp_grabbed) return ECORE_CALLBACK_RENEW;

   if (!action_client)
     ERR("no action_client!");

   _e_client_move_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_DONE;
}

static void
_e_client_moveinfo_gather(E_Client *ec, const char *source)
{
   if (e_util_glob_match(source, "mouse,*,1"))
     ec->moveinfo.down.button = 1;
   else if (e_util_glob_match(source, "mouse,*,2"))
     ec->moveinfo.down.button = 2;
   else if (e_util_glob_match(source, "mouse,*,3"))
     ec->moveinfo.down.button = 3;
   else
     ec->moveinfo.down.button = 0;
   if ((ec->moveinfo.down.button >= 1) && (ec->moveinfo.down.button <= 3))
     {
        ec->moveinfo.down.mx = ec->mouse.last_down[ec->moveinfo.down.button - 1].mx;
        ec->moveinfo.down.my = ec->mouse.last_down[ec->moveinfo.down.button - 1].my;
     }
   else
     {
        ec->moveinfo.down.mx = ec->mouse.current.mx;
        ec->moveinfo.down.my = ec->mouse.current.my;
     }
}

static void
_e_client_resize_handle(E_Client *ec)
{
   int x, y, w, h;
   int new_x, new_y, new_w, new_h;
   int tw, th;
   Eina_List *skiplist = NULL;

   if (e_comp->updating) return;
   x = ec->x;
   y = ec->y;
   w = ec->w;
   h = ec->h;

   if ((ec->resize_mode == E_POINTER_RESIZE_TR) ||
       (ec->resize_mode == E_POINTER_RESIZE_R) ||
       (ec->resize_mode == E_POINTER_RESIZE_BR))
     {
        if ((ec->moveinfo.down.button >= 1) &&
            (ec->moveinfo.down.button <= 3))
          w = ec->mouse.last_down[ec->moveinfo.down.button - 1].w +
            (ec->mouse.current.mx - ec->moveinfo.down.mx);
        else
          w = ec->moveinfo.down.w + (ec->mouse.current.mx - ec->moveinfo.down.mx);
     }
   else if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
            (ec->resize_mode == E_POINTER_RESIZE_L) ||
            (ec->resize_mode == E_POINTER_RESIZE_BL))
     {
        if ((ec->moveinfo.down.button >= 1) &&
            (ec->moveinfo.down.button <= 3))
          w = ec->mouse.last_down[ec->moveinfo.down.button - 1].w -
            (ec->mouse.current.mx - ec->moveinfo.down.mx);
        else
          w = ec->moveinfo.down.w - (ec->mouse.current.mx - ec->moveinfo.down.mx);
     }

   if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
       (ec->resize_mode == E_POINTER_RESIZE_T) ||
       (ec->resize_mode == E_POINTER_RESIZE_TR))
     {
        if ((ec->moveinfo.down.button >= 1) &&
            (ec->moveinfo.down.button <= 3))
          h = ec->mouse.last_down[ec->moveinfo.down.button - 1].h -
            (ec->mouse.current.my - ec->moveinfo.down.my);
        else
          h = ec->moveinfo.down.h - (ec->mouse.current.my - ec->moveinfo.down.my);
     }
   else if ((ec->resize_mode == E_POINTER_RESIZE_BL) ||
            (ec->resize_mode == E_POINTER_RESIZE_B) ||
            (ec->resize_mode == E_POINTER_RESIZE_BR))
     {
        if ((ec->moveinfo.down.button >= 1) &&
            (ec->moveinfo.down.button <= 3))
          h = ec->mouse.last_down[ec->moveinfo.down.button - 1].h +
            (ec->mouse.current.my - ec->moveinfo.down.my);
        else
          h = ec->moveinfo.down.h + (ec->mouse.current.my - ec->moveinfo.down.my);
     }

   tw = ec->w;
   th = ec->h;

   if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
       (ec->resize_mode == E_POINTER_RESIZE_L) ||
       (ec->resize_mode == E_POINTER_RESIZE_BL))
     x += (tw - w);
   if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
       (ec->resize_mode == E_POINTER_RESIZE_T) ||
       (ec->resize_mode == E_POINTER_RESIZE_TR))
     y += (th - h);

   skiplist = eina_list_append(skiplist, ec);
   e_resist_client_position(skiplist,
                                      ec->x, ec->y, ec->w, ec->h,
                                      x, y, w, h,
                                      &new_x, &new_y, &new_w, &new_h);
   eina_list_free(skiplist);

   w = new_w;
   h = new_h;
   if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
     {
        if (ec->zone)
          {
             w = MIN(w, ec->zone->w);
             h = MIN(h, ec->zone->h);
          }
     }
   e_client_resize_limit(ec, &new_w, &new_h);
   if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
       (ec->resize_mode == E_POINTER_RESIZE_L) ||
       (ec->resize_mode == E_POINTER_RESIZE_BL))
     new_x += (w - new_w);
   if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
       (ec->resize_mode == E_POINTER_RESIZE_T) ||
       (ec->resize_mode == E_POINTER_RESIZE_TR))
     new_y += (h - new_h);

   evas_object_geometry_set(ec->frame, new_x, new_y, new_w, new_h);
}

static int
_e_client_resize_end(E_Client *ec)
{
   _e_client_action_input_win_del();
   e_pointer_mode_pop(ec, ec->resize_mode);
   ec->resize_mode = E_POINTER_RESIZE_NONE;

   /* If this border was maximized, we need to unset Maximized state or
    * on restart, E still thinks it's maximized */
   if (ec->maximized != E_MAXIMIZE_NONE)
     e_hints_window_maximized_set(ec, ec->maximized & E_MAXIMIZE_HORIZONTAL,
                                  ec->maximized & E_MAXIMIZE_VERTICAL);

   _e_client_hook_call(E_CLIENT_HOOK_RESIZE_END, ec);

   ecresize = NULL;

   return 1;
}


static Eina_Bool
_e_client_action_resize_timeout(void *data EINA_UNUSED)
{
   _e_client_resize_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_client_action_resize_timeout_add(void)
{
   double timeout = e_config->border_keyboard.timeout;
   E_FREE_FUNC(action_timer, ecore_timer_del);
   if (!EINA_DBL_NONZERO(timeout)) timeout = 5.0;
   action_timer = ecore_timer_loop_add(timeout, _e_client_action_resize_timeout, NULL);
}

static Eina_Bool
_e_client_resize_key_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Key *ev = event;
   int w, h, dx, dy;

   if (!comp_grabbed) return ECORE_CALLBACK_RENEW;
   if (!action_client)
     {
        ERR("no action_client!");
        goto stop;
     }

   w = action_client->w;
   h = action_client->h;

   dx = e_config->border_keyboard.resize.dx;
   if (dx < action_client->icccm.step_w) dx = action_client->icccm.step_w;
   dx = _e_client_key_down_modifier_apply(ev->modifiers, dx);
   if (dx < action_client->icccm.step_w) dx = action_client->icccm.step_w;

   dy = e_config->border_keyboard.resize.dy;
   if (dy < action_client->icccm.step_h) dy = action_client->icccm.step_h;
   dy = _e_client_key_down_modifier_apply(ev->modifiers, dy);
   if (dy < action_client->icccm.step_h) dy = action_client->icccm.step_h;

   switch (e_util_key_geometry_action_get(ev->key, &w, &h, dx, dy))
     {
      case E_UTIL_ACTION_DONE:
        goto stop;
        break;
      case E_UTIL_ACTION_ABORT:
        _e_client_action_restore_orig(action_client);
        goto stop;
        break;
      case E_UTIL_ACTION_DO:
        if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
          {
             if (action_client->zone)
               {
                  w = MIN(w, action_client->zone->w);
                  h = MIN(h, action_client->zone->h);
               }
          }
        e_client_resize_limit(action_client, &w, &h);
        evas_object_resize(action_client->frame, w, h);
        _e_client_action_resize_timeout_add();
        break;
      case E_UTIL_ACTION_NONE:
      default:
        break;
     }

   return ECORE_CALLBACK_PASS_ON;

stop:
   _e_client_resize_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_e_client_resize_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (!comp_grabbed) return ECORE_CALLBACK_RENEW;

   if (!action_client)
     ERR("no action_client!");

   _e_client_resize_end(action_client);
   _e_client_action_finish();
   return ECORE_CALLBACK_DONE;
}

////////////////////////////////////////////////

static E_Client *
_e_client_under_pointer_helper(E_Desk *desk, E_Client *exclude, int x, int y)
{
   E_Client *ec = NULL, *cec;

   E_CLIENT_REVERSE_FOREACH(cec)
     {
        /* If a border was specified which should be excluded from the list
         * (because it will be closed shortly for example), skip */
        if (e_client_util_ignored_get(cec) || (!e_client_util_desk_visible(cec, desk))) continue;
        if (!evas_object_visible_get(cec->frame)) continue;
        if ((exclude) && (cec == exclude)) continue;
        if (!E_INSIDE(x, y, cec->x, cec->y, cec->w, cec->h))
          continue;
        /* If the layer is higher, the position of the window is higher
         * (always on top vs always below) */
        if (!ec || (cec->layer > ec->layer))
          ec = cec;
     }
   return ec;
}

////////////////////////////////////////////////

static void
_e_client_zones_layout_calc(E_Client *ec, int *zx, int *zy, int *zw, int *zh)
{
   int x, y, w, h;
   E_Zone *zone_above, *zone_below, *zone_left, *zone_right;

   if (!ec->zone) return;
   x = ec->zone->x;
   y = ec->zone->y;
   w = ec->zone->w;
   h = ec->zone->h;

   if (eina_list_count(e_comp->zones) == 1)
     {
        if (zx) *zx = x;
        if (zy) *zy = y;
        if (zw) *zw = w;
        if (zh) *zh = h;
        return;
     }

   zone_left = e_comp_zone_xy_get((x - w + 5), y);
   zone_right = e_comp_zone_xy_get((x + w + 5), y);
   zone_above = e_comp_zone_xy_get(x, (y - h + 5));
   zone_below = e_comp_zone_xy_get(x, (y + h + 5));

   if (!(zone_above) && (y))
     zone_above = e_comp_zone_xy_get(x, (h - 5));

   if (!(zone_left) && (x))
     zone_left = e_comp_zone_xy_get((x - 5), y);

   if (zone_right)
     w = zone_right->x + zone_right->w;

   if (zone_left)
     w = ec->zone->x + ec->zone->w;

   if (zone_below)
     h = zone_below->y + zone_below->h;

   if (zone_above)
     h = ec->zone->y + ec->zone->h;

   if ((zone_left) && (zone_right))
     w = ec->zone->w + zone_right->x;

   if ((zone_above) && (zone_below))
     h = ec->zone->h + zone_below->y;

   if (x) x -= ec->zone->w;
   if (y) y -= ec->zone->h;

   if (zx) *zx = x > 0 ? x : 0;
   if (zy) *zy = y > 0 ? y : 0;
   if (zw) *zw = w;
   if (zh) *zh = h;
}

static void
_e_client_stay_within_canvas(E_Client *ec, int x, int y, int *new_x, int *new_y)
{
   int new_x_max, new_y_max;
   int zw, zh;
   Eina_Bool lw, lh;

   if (!ec->zone)
     {
        if (new_x) *new_x = x;
        if (new_y) *new_y = y;
        return;
     }

   _e_client_zones_layout_calc(ec, NULL, NULL, &zw, &zh);

   new_x_max = zw - ec->w;
   new_y_max = zh - ec->h;
   lw = ec->w > zw ? EINA_TRUE : EINA_FALSE;
   lh = ec->h > zh ? EINA_TRUE : EINA_FALSE;

   if (lw)
     {
        if (x <= new_x_max)
          *new_x = new_x_max;
        else if (x >= 0)
          *new_x = 0;
     }
   else
     {
        if (x >= new_x_max)
          *new_x = new_x_max;
        else if (x <= 0)
          *new_x = 0;
     }

   if (lh)
     {
        if (y <= new_y_max)
          *new_y = new_y_max;
        else if (y >= 0)
          *new_y = 0;
     }
   else
     {
        if (y >= new_y_max)
          *new_y = new_y_max;
        else if (y <= 0)
          *new_y = 0;
     }
}

static void
_e_client_reset_lost_window(E_Client *ec)
{
   E_OBJECT_CHECK(ec);

   if (ec->during_lost) return;
   ec->during_lost = EINA_TRUE;

   if (ec->iconic) e_client_uniconify(ec);
   if (!ec->moving) e_comp_object_util_center(ec->frame);

   evas_object_raise(ec->frame);
   if (!ec->lock_focus_out)
     evas_object_focus_set(ec->frame, 1);

   e_client_pointer_warp_to_center(ec);
   ec->during_lost = EINA_FALSE;
}

static void
_e_client_move_lost_window_to_center(E_Client *ec)
{
   int loss_overlap = 5;
   int zw, zh, zx, zy;

   if (ec->during_lost) return;
   if (!ec->zone) return;

   _e_client_zones_layout_calc(ec, &zx, &zy, &zw, &zh);

   if (!E_INTERSECTS(zx + loss_overlap,
                     zy + loss_overlap,
                     zw - (2 * loss_overlap),
                     zh - (2 * loss_overlap),
                     ec->x, ec->y, ec->w, ec->h))
     {
        if (e_config->edge_flip_dragging)
          {
             Eina_Bool lf, rf, tf, bf;

             lf = rf = tf = bf = EINA_TRUE;

             if (ec->zone->desk_x_count <= 1) lf = rf = EINA_FALSE;
             else if (!e_config->desk_flip_wrap)
               {
                  if (ec->zone->desk_x_current == 0) lf = EINA_FALSE;
                  if (ec->zone->desk_x_current == (ec->zone->desk_x_count - 1)) rf = EINA_FALSE;
               }

             if (ec->zone->desk_y_count <= 1) tf = bf = EINA_FALSE;
             else if (!e_config->desk_flip_wrap)
               {
                  if (ec->zone->desk_y_current == 0) tf = EINA_FALSE;
                  if (ec->zone->desk_y_current == (ec->zone->desk_y_count - 1)) bf = EINA_FALSE;
               }

             if (!(lf) && (ec->x <= loss_overlap) && !(ec->zone->flip.switching))
               _e_client_reset_lost_window(ec);

             if (!(rf) && (ec->x >= (ec->zone->w - loss_overlap)) && !(ec->zone->flip.switching))
               _e_client_reset_lost_window(ec);

             if (!(tf) && (ec->y <= loss_overlap) && !(ec->zone->flip.switching))
               _e_client_reset_lost_window(ec);

             if (!(bf) && (ec->y >= (ec->zone->h - loss_overlap)) && !(ec->zone->flip.switching))
               _e_client_reset_lost_window(ec);
          }

        if (!e_config->edge_flip_dragging)
          _e_client_reset_lost_window(ec);
     }
}

////////////////////////////////////////////////
static void
_e_client_zone_update(E_Client *ec)
{
   Eina_List *l;
   E_Zone *zone;

   /* still within old zone - leave it there */
   if (ec->zone && E_INTERSECTS(ec->x, ec->y, ec->w, ec->h,
                    ec->zone->x, ec->zone->y, ec->zone->w, ec->zone->h))
     return;
   /* find a new zone */
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (E_INTERSECTS(ec->x, ec->y, ec->w, ec->h,
                         zone->x, zone->y, zone->w, zone->h))
          {
             e_client_zone_set(ec, zone);
             return;
          }
     }
}

////////////////////////////////////////////////

E_API Eina_List *
e_client_stack_list_prepare(E_Client *ec)
{
   E_Client *ec2;
   Eina_List *list = NULL;

   for (ec2 = ec->stack.prev; ec2; ec2 = ec2->stack.prev)
     {
        ec2->stack.ignore++;
        list = eina_list_prepend(list, ec2);
     }
   ec->stack.ignore++;
   list = eina_list_append(list, ec);
   for (ec2 = ec->stack.next; ec2; ec2 = ec2->stack.next)
     {
        ec2->stack.ignore++;
        list = eina_list_append(list, ec2);
     }
   return list;
}

E_API void
e_client_stack_list_finish(Eina_List *list)
{
   E_Client *ec;

   EINA_LIST_FREE(list, ec) ec->stack.ignore--;
}

E_API E_Client *
e_client_stack_top_get(E_Client *ec)
{
   E_Client *ec2;

   for (ec2 = ec; ec2; ec2 = ec2->stack.next)
     {
        if (!ec2->stack.next) return ec2;
     }
   return ec;
}

E_API E_Client *
e_client_stack_bottom_get(E_Client *ec)
{
   E_Client *ec2;

   for (ec2 = ec; ec2; ec2 = ec2->stack.prev)
     {
        if (!ec2->stack.prev) return ec2;
     }
   return ec;
}

E_API E_Client *
e_client_stack_active_adjust(E_Client *ec)
{
   E_Client *pec = ec;
   if ((!ec->stack.prev) && (!ec->stack.next)) return ec;
   ec = e_client_stack_top_get(ec);
   for (; ec; ec = ec->stack.prev)
     {
        if (e_object_is_del(E_OBJECT(ec))) continue;
        if (ec->stack.focus_skip) continue;
        if (ec->iconic) continue;
        if (ec->visible) break;
     }
   if (!ec) ec = pec;
   return ec;
}

E_API Eina_Bool
e_client_stack_focused_get(E_Client *ec)
{
  E_Client *ec2;

   ec2 = e_client_stack_bottom_get(ec);
   for (; ec2; ec2 = ec2->stack.next)
     {
        if (ec2->focused) return EINA_TRUE;
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_client_stack_iconified_get(E_Client *ec)
{
   E_Client *ec2;

   ec2 = e_client_stack_bottom_get(ec);
   for (; ec2; ec2 = ec2->stack.next)
     {
        if (ec2->iconic) return EINA_TRUE;
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_client_stack_urgent_get(E_Client *ec)
{
   E_Client *ec2;

   ec2 = e_client_stack_bottom_get(ec);
   for (; ec2; ec2 = ec2->stack.next)
     {
        if (ec2->urgent) return EINA_TRUE;
     }
   return EINA_FALSE;
}

////////////////////////////////////////////////

static void
_e_client_cb_evas_hide(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (stopping) return; //ignore all of this if we're shutting down!
   if (e_object_is_del(data)) return; //client is about to die
   _e_client_mouse_action_end(ec);
   if (action_client == ec) _e_client_action_finish();
   if (!evas_object_pass_events_get(ec->frame))
     e_pointer_type_pop(e_comp->pointer, ec, NULL);

   if (!ec->hidden)
     {
        if (ec->focused)
          e_client_revert_focus(ec);
     }
   ec->want_focus = ec->take_focus = 0;

   ec->post_show = 0;

   if (ec->new_client || ec->iconic) return;
   _e_client_event_simple(ec, E_EVENT_CLIENT_HIDE);
}

static void
_e_client_cb_evas_shade_done(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   ec->shading = 0;
   ec->shaded = !(ec->shaded);
   ec->changes.shaded = 1;
   ec->changes.shading = 1;
   e_client_comp_hidden_set(ec, ec->shaded);
}

static void
_e_client_cb_evas_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;
   Evas_Coord x, y;

   ec->pre_res_change.valid = 0;
   if (ec->internal_elm_win)
     {
        EC_CHANGED(ec);
        ec->changes.pos = 1;
     }

   _e_client_event_simple(ec, E_EVENT_CLIENT_MOVE);

   if (!ec->ignored) _e_client_zone_update(ec);
   evas_object_geometry_get(ec->frame, &x, &y, NULL, NULL);
   if (ec->stack.prev || ec->stack.next)
     {
        // do nothing - handled by idle enterer eval
     }
   else
     {
        if ((e_config->transient.move) && (ec->transients))
          {
             Eina_List *list = eina_list_clone(ec->transients);
             E_Client *child;

             EINA_LIST_FREE(list, child)
               {
                  if (child->placed)
                    evas_object_move(child->frame,
                                     child->x + x - ec->pre_cb.x,
                                     child->y + y - ec->pre_cb.y);
               }
          }
     }
   if (ec->moving || (ecmove == ec))
     _e_client_hook_call(E_CLIENT_HOOK_MOVE_UPDATE, ec);
   e_remember_update(ec);
   if (ec->fullscreen || (ec->maximized & E_MAXIMIZE_DIRECTION))
     e_hints_window_size_set(ec);
   ec->pre_cb.x = x; ec->pre_cb.y = y;
}

static void
_e_client_cb_evas_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;
   Evas_Coord x, y, w, h;

   ec->pre_res_change.valid = 0;

   _e_client_event_simple(ec, E_EVENT_CLIENT_RESIZE);

   evas_object_geometry_get(ec->frame, &x, &y, &w, &h);
   if (ec->stack.prev || ec->stack.next)
     {
        // do nothing - handled by idle enterer eval
     }
   else
     {
        if ((e_config->transient.resize) && (ec->transients))
          {
             Eina_List *list = eina_list_clone(ec->transients);
             E_Client *child;

             EINA_LIST_FREE(list, child)
               {
                  Evas_Coord nx, ny, nw, nh;

                  if (!child->placed) continue;

                  if ((ec->pre_cb.w > 0) && (ec->pre_cb.h > 0))
                    {
                       nx = x + (((child->x - x) * w) / ec->pre_cb.w);
                       ny = y + (((child->y - y) * h) / ec->pre_cb.h);
                       nw = (child->w * w) / ec->pre_cb.w;
                       nh = (child->h * h) / ec->pre_cb.h;
                       nx += ((nw - child->w) / 2);
                       ny += ((nh - child->h) / 2);
                       evas_object_move(child->frame, nx, ny);
                    }
               }
          }
     }

   if (e_client_util_resizing_get(ec) || (ecresize == ec))
     _e_client_hook_call(E_CLIENT_HOOK_RESIZE_UPDATE, ec);
   e_remember_update(ec);
   if (ec->fullscreen || (ec->maximized & E_MAXIMIZE_DIRECTION))
     e_hints_window_size_set(ec);
   ec->pre_cb.w = w; ec->pre_cb.h = h;
}

static void
_e_client_cb_evas_show(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_client_event_simple(data, E_EVENT_CLIENT_SHOW);
}

static void
_e_client_cb_evas_restack(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (ec->layer_block) return;
   if (ec->stack.prev || ec->stack.next)
     {
        if (ec->stack.ignore == 0)
          {
             Eina_List *l, *list = e_client_stack_list_prepare(ec);
             E_Client *child;

             EINA_LIST_FOREACH(list, l, child)
               {
                  if (child == ec) break;
                  evas_object_stack_below(child->frame, ec->frame);
               }
             EINA_LIST_REVERSE_FOREACH(list, l, child)
               {
                  if (child == ec) break;
                  evas_object_stack_above(child->frame, ec->frame);
               }
             e_client_stack_list_finish(list);
          }
     }
   else
     {
        if (e_config->transient.raise && ec->transients)
          e_client_transients_restack(ec);
     }
   if (ec->unredirected_single) return;
   e_remember_update(ec);
   _e_client_event_simple(ec, E_EVENT_CLIENT_STACK);
}

////////////////////////////////////////////////

static void
_e_client_maximize_done(void *data, E_Efx_Map_Data *emd EINA_UNUSED, Evas_Object *obj)
{
   E_Client *ec = data;
   ec->maximize_override = 0;
   ec->agent = NULL;
   evas_object_del(obj);
}

static Eina_Bool
_e_client_maximize_run(E_Client *ec, int x, int y, int w, int h)
{
   int pw, ph;
   Eina_Bool disabled = EINA_FALSE;
   if (e_pixmap_size_get(ec->pixmap, &pw, &ph))
      {
         e_comp_object_frame_wh_adjust(ec->frame, pw, ph, &pw, &ph);
         disabled = (w == pw) && (h == ph);
      }
   if ((!disabled) && e_config->window_maximize_animate && (!ec->maximize_anims_disabled) &&
       (!starting) && (!ec->changes.need_maximize))
     {
        evas_object_del(ec->agent);
        ec->agent = e_comp_object_agent_add(ec->frame);
        e_efx_resize(ec->agent, e_config->window_maximize_transition, E_EFX_POINT(x, y),
          w, h, e_config->window_maximize_time, _e_client_maximize_done, ec);
        return EINA_TRUE;
     }
   evas_object_geometry_set(ec->frame, x, y, w, h);
   return EINA_FALSE;
}

////////////////////////////////////////////////

static void
_e_client_eval(E_Client *ec)
{
   int rem_change = 0;
   int send_event = 1;
   unsigned int prop = 0;
   int zx = 0, zy = 0, zw = 0, zh = 0;


   if (e_object_is_del(E_OBJECT(ec)))
     {
        CRI("_e_client_eval(%p) with deleted border! - %d\n", ec, ec->new_client);
        ec->changed = 0;
        return;
     }

   if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_PRE_NEW_CLIENT, ec)) return;

   if ((ec->new_client) && (!e_client_util_ignored_get(ec)) && (ec->zone))
     {
        if (e_comp->frozen)
          {
             ec->frozen = EINA_TRUE;
             e_hints_window_state_set(ec);
          }
        _e_client_event_simple(ec, E_EVENT_CLIENT_ADD);
        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
        /* enforce wm size hints for initial sizing */
        if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
          {
             ec->w = MIN(ec->w, ec->zone->w);
             ec->h = MIN(ec->h, ec->zone->h);
          }
        e_client_resize_limit(ec, &ec->w, &ec->h);

        if (ec->re_manage && e_comp_object_frame_exists(ec->frame))
          {
             int x = ec->x, y = ec->y;
             if (ec->x) e_comp_object_frame_xy_adjust(ec->frame, ec->x, 0, &ec->x, NULL);
             if (ec->y) e_comp_object_frame_xy_adjust(ec->frame, 0, ec->y, NULL, &ec->y);
             if ((x != ec->x) || (y != ec->y)) ec->changes.pos = 1;
             ec->placed = 1;
             ec->pre_cb.x = ec->x; ec->pre_cb.y = ec->y;
          }

        if ((ec->stack.prev) && (!ec->dialog))
          {
             E_Client *ec2 = e_client_stack_bottom_get(ec);

             ec->stack.ignore++;
             evas_object_move(ec->frame, ec2->x, ec2->y);
             evas_object_resize(ec->frame, ec2->w, ec2->h);
             ec->stack.ignore--;
          }
     }
   if (ec->new_client && (!ec->override))
     e_hints_window_init(ec);
   if ((!e_client_util_ignored_get(ec)) && ec->zone && ec->visible && (!ec->placed))
     {
        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
        if (ec->parent)
          {
             Eina_Bool centered = EINA_FALSE;
             if (ec->parent->zone != e_zone_current_get())
               {
                  e_client_zone_set(ec, ec->parent->zone);
                  e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
               }

             if (evas_object_visible_get(ec->parent->frame))
               {
                  if ((!E_CONTAINS(ec->x, ec->y, ec->w, ec->h, zx, zy, zw, zh)) ||
                     (!E_CONTAINS(ec->x, ec->y, ec->w, ec->h, ec->parent->x, ec->parent->y, ec->parent->w, ec->parent->h)))
                    {
                       int x, y;

                       e_comp_object_util_center_pos_get(ec->parent->frame, &x, &y);
                       if (E_CONTAINS(x, y, ec->w, ec->h, zx, zy, zw, zh))
                         {
                            ec->x = x, ec->y = y;
                         }
                       else
                         {
                            x = ec->parent->x;
                            y = ec->parent->y;
                            if (!E_CONTAINS(x, y, ec->w, ec->h, zx, zy, zw, zh))
                              {
                                 e_comp_object_util_center_on(ec->frame,
                                                              ec->parent->frame);
                                 centered = 1;
                              }
                         }
                       ec->changes.pos = 1;
                    }
               }
             else
               {
                  e_comp_object_util_center_on(ec->frame,
                                               ec->parent->frame);
                  centered = 1;
               }
             if (centered) //test for offscreen
               {
                  if (!E_CONTAINS(ec->x, ec->y, ec->w, ec->h, zx, zy, zw, zh))
                    {
                       if (ec->x < zx)
                         ec->x = ec->parent->x;
                       if (ec->y < zy)
                         ec->y = ec->parent->y;
                       if (ec->x + ec->w > zx + zw)
                         ec->x = ec->parent->x + ec->parent->w - ec->w;
                       if (ec->y + ec->h > zy + zh)
                         ec->y = ec->parent->y + ec->parent->h - ec->h;
                       ec->changes.pos = 1;
                    }
               }
             ec->placed = 1;
             ec->pre_cb.x = ec->x; ec->pre_cb.y = ec->y;
          }
#if 0
        else if ((ec->leader) && (ec->dialog))
          {
             /* TODO: Place in center of group */
          }
#endif
        else if (ec->dialog)
          {
             E_Client *trans_ec = NULL;

             if (ec->icccm.transient_for)
               trans_ec = e_pixmap_find_client(E_PIXMAP_TYPE_X, ec->icccm.transient_for);
             if (trans_ec)
               {
                  // if transient for a window and not placed, center on
                  // transient parent if found
                  ec->x = trans_ec->x + ((trans_ec->w - ec->w) / 2);
                  ec->y = trans_ec->y + ((trans_ec->h - ec->h) / 2);
               }
             else
               {
                  ec->x = zx + ((zw - ec->w) / 2);
                  ec->y = zy + ((zh - ec->h) / 2);
               }
             ec->changes.pos = 1;
             ec->placed = 1;
             ec->pre_cb.x = ec->x; ec->pre_cb.y = ec->y;
          }

        if (!ec->placed)
          {
             Eina_List *skiplist = NULL;
             int new_x, new_y, t = 0;
             E_Client *trans_ec = NULL;

             if (zw > ec->w)
               new_x = zx + (rand() % (zw - ec->w));
             else
               new_x = zx;
             if (zh > ec->h)
               new_y = zy + (rand() % (zh - ec->h));
             else
               new_y = zy;

             e_comp_object_frame_geometry_get(ec->frame, NULL, NULL, &t, NULL);

             if (ec->icccm.transient_for)
               trans_ec = e_pixmap_find_client(E_PIXMAP_TYPE_X, ec->icccm.transient_for);
             if (trans_ec)
               {
                  // if transient for a window and not placed, center on
                  // transient parent if found
                  new_x = trans_ec->x + ((trans_ec->w - ec->w) / 2);
                  new_y = trans_ec->y + ((trans_ec->h - ec->h) / 2);
               }
             else if ((e_config->window_placement_policy == E_WINDOW_PLACEMENT_SMART) || (e_config->window_placement_policy == E_WINDOW_PLACEMENT_ANTIGADGET))
               {
                  skiplist = eina_list_append(skiplist, ec);
                  if (ec->transients)
                    skiplist = eina_list_merge(skiplist, eina_list_clone(ec->transients));
                  if (ec->desk)
                    e_place_desk_region_smart(ec->desk, skiplist,
                                              ec->x, ec->y, ec->w, ec->h,
                                              &new_x, &new_y);
                  else
                    e_place_zone_region_smart(ec->zone, skiplist,
                                              ec->x, ec->y, ec->w, ec->h,
                                              &new_x, &new_y);
                  eina_list_free(skiplist);
               }
             else if (e_config->window_placement_policy == E_WINDOW_PLACEMENT_MANUAL)
               {
                  e_place_zone_manual(ec->zone, ec->w, t, &new_x, &new_y);
               }
             else
               {
                  e_place_zone_cursor(ec->zone, ec->x, ec->y, ec->w, ec->h,
                                      t, &new_x, &new_y);
               }
             ec->x = new_x;
             ec->y = new_y;
             ec->changes.pos = 1;
             ec->pre_cb.x = ec->x; ec->pre_cb.y = ec->y;
          }
        else if (!E_INTERSECTS(ec->x, ec->y, ec->w, ec->h, zx, zy, zw, zh))
          {
             /* If an ec is placed out of bound, fix it! */
             ec->x = zx + ((zw - ec->w) / 2);
             ec->y = zy + ((zh - ec->h) / 2);
             ec->changes.pos = 1;
          }

        /* Recreate state */
        if ((ec->e.state.centered) &&
            ((!ec->remember) ||
             ((ec->remember) && (!(ec->remember->apply & E_REMEMBER_APPLY_POS)))))
          {
             ec->x = zx + (zw - ec->w) / 2;
             ec->y = zy + (zh - ec->h) / 2;
             ec->changes.pos = 1;
          }

        /* if the explicit geometry request asks for the app to be
         * in another zone - well move it there */
        {
           E_Zone *zone = NULL;
           int x, y;

           x = MAX(ec->x, 0);
           y = MAX(ec->y, 0);
           if ((!ec->re_manage) && ((ec->x != x) || (ec->y != y)))
             zone = e_comp_zone_xy_get(x, y);

           if (!zone)
             {
                zone = e_comp_zone_xy_get(ec->x + (ec->w / 2), ec->y + (ec->h / 2));
                if (zone)
                  {
                     E_Zone *z2 = e_comp_zone_xy_get(ec->x, ec->y);

                     if (z2 && (z2 != zone))
                       {
                          size_t psz = 0;
                          E_Zone *zf = z2;
                          Eina_List *l;

                          EINA_LIST_FOREACH(e_comp->zones, l, z2)
                            {
                                int w, h;

                                x = ec->x, y = ec->y, w = ec->w, h = ec->h;
                                E_RECTS_CLIP_TO_RECT(x, y, w, h, z2->x, z2->y, z2->w, z2->h);
                                if (w * h == z2->w * z2->h)
                                  {
                                     /* client fully covering zone */
                                     zf = z2;
                                     break;
                                  }
                                if ((unsigned)(w * h) > psz)
                                  {
                                     psz = w * h;
                                     zf = z2;
                                  }
                            }
                          zone = zf;
                       }
                  }
             }
           if (!zone)
             zone = e_comp_zone_xy_get(ec->x, ec->y);
           if (!zone)
             zone = e_comp_zone_xy_get(ec->x + ec->w - 1, ec->y);
           if (!zone)
             zone = e_comp_zone_xy_get(ec->x + ec->w - 1, ec->y + ec->h - 1);
           if (!zone)
             zone = e_comp_zone_xy_get(ec->x, ec->y + ec->h - 1);
           if ((zone) && (zone != ec->zone))
             e_client_zone_set(ec, zone);
        }
     }

   if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_POST_NEW_CLIENT, ec)) return;

   /* effect changes to the window border itself */
   if ((ec->changes.shading))
     {
        /*  show at start of unshade (but don't hide until end of shade) */
        //if (ec->shaded)
          //ecore_x_window_raise(ec->win);
        ec->changes.shading = 0;
        send_event = 0;
        rem_change = 1;
     }
   if (ec->changes.shaded) send_event = 0;
   if ((ec->changes.shaded) && (ec->changes.pos) && (ec->changes.size))
     {
        //if (ec->shaded)
          //ecore_x_window_lower(ec->win);
        //else
          //ecore_x_window_raise(ec->win);
        ec->changes.shaded = 0;
        rem_change = 1;
     }
   else if ((ec->changes.shaded) && (ec->changes.pos))
     {
        //if (ec->shaded)
          //ecore_x_window_lower(ec->win);
        //else
          //ecore_x_window_raise(ec->win);
        ec->changes.size = 1;
        ec->changes.shaded = 0;
        rem_change = 1;
     }
   else if ((ec->changes.shaded) && (ec->changes.size))
     {
        //if (ec->shaded)
          //ecore_x_window_lower(ec->win);
        //else
          //ecore_x_window_raise(ec->win);
        ec->changes.shaded = 0;
        rem_change = 1;
     }
   else if (ec->changes.shaded)
     {
        //if (ec->shaded)
          //ecore_x_window_lower(ec->win);
        //else
          //ecore_x_window_raise(ec->win);
        ec->changes.shaded = 0;
        rem_change = 1;
     }

   if (ec->changes.size)
     {
        ec->changes.size = 0;
        if ((!ec->shaded) && (!ec->shading))
          evas_object_resize(ec->frame, ec->w, ec->h);

        rem_change = 1;
        prop |= E_CLIENT_PROPERTY_SIZE;
     }
   if (ec->changes.pos)
     {
        Eina_Bool placed = ec->placed;

        ec->changes.pos = 0;
        evas_object_move(ec->frame, ec->x, ec->y);
        if (e_config->window_placement_policy == E_WINDOW_PLACEMENT_MANUAL)
          ec->placed = placed;
        rem_change = 1;
        prop |= E_CLIENT_PROPERTY_POS;
     }
   if (ec->changes.need_rescale)
     {
        e_client_rescale(ec);
        ec->changes.need_rescale = 0;
     }

   if (ec->changes.reset_gravity)
     {
        ec->changes.reset_gravity = 0;
        rem_change = 1;
        prop |= E_CLIENT_PROPERTY_GRAVITY;
     }

   if ((ec->changes.visible) && (ec->visible) && (ec->new_client) && (!ec->iconic))
     {
        int x, y;

        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        if ((!ec->placed) && (!ec->re_manage) &&
            (e_config->window_placement_policy == E_WINDOW_PLACEMENT_MANUAL) &&
            (!((ec->icccm.transient_for != 0) ||
               (ec->dialog))) &&
            (!ecmove) && (!ecresize))
          {
             /* Set this window into moving state */

             ec->cur_mouse_action = e_action_find("window_move");
             if (ec->next_mouse_action_ignore)
               ec->cur_mouse_action = NULL;
             if (ec->cur_mouse_action)
               {
                  if ((!ec->cur_mouse_action->func.end_mouse) &&
                      (!ec->cur_mouse_action->func.end))
                    ec->cur_mouse_action = NULL;
                  if (ec->cur_mouse_action)
                    {
                       int t;
                       ec->x = x - (ec->w >> 1);
                       e_comp_object_frame_geometry_get(ec->frame, NULL, NULL, &t, NULL);
                       ec->y = y - (t >> 1);
                       EC_CHANGED(ec);
                       ec->changes.pos = 1;
                    }
               }
          }

        evas_object_show(ec->frame);
        if (ec->cur_mouse_action)
          {
             ec->moveinfo.down.x = ec->x;
             ec->moveinfo.down.y = ec->y;
             ec->moveinfo.down.w = ec->w;
             ec->moveinfo.down.h = ec->h;
             ec->mouse.current.mx = x;
             ec->mouse.current.my = y;
             ec->moveinfo.down.button = 0;
             ec->moveinfo.down.mx = x;
             ec->moveinfo.down.my = y;

             e_object_ref(E_OBJECT(ec->cur_mouse_action));
             ec->cur_mouse_action->func.go(E_OBJECT(ec), NULL);
             if (e_config->border_raise_on_mouse_action)
               evas_object_raise(ec->frame);
             evas_object_focus_set(ec->frame, 1);
          }
        if (evas_object_visible_get(ec->frame))
          ec->changes.visible = 0;
     }
   else if ((ec->changes.visible) && (ec->new_client))
     {
        ec->changes.visible = 0;
        if (!ec->iconic)
          _e_client_event_simple(ec, E_EVENT_CLIENT_HIDE);
     }

   if (ec->changes.icon)
     {
        if (!ec->new_client)
          E_FREE_FUNC(ec->desktop, efreet_desktop_free);
        if (ec->remember && ec->remember->prop.desktop_file)
          {
             Efreet_Desktop *d;
             const char *desktop = ec->remember->prop.desktop_file;

             d = efreet_desktop_get(desktop);
             if (!d)
               d = efreet_util_desktop_name_find(desktop);
             if (d)
               {
                  efreet_desktop_free(ec->desktop);
                  ec->desktop = d;
               }
          }
        if (!ec->desktop)
          {
             if (ec->steam.steam_game_id)
               {
                  Efreet_Desktop *d;
                  Eina_List *desks = efreet_util_desktop_name_glob_list("*");
                  EINA_LIST_FREE(desks, d)
                    {
                       if (!d->exec) continue;
                       if (!strncmp(d->exec, "steam ", 6))
                         {
                            const char *st = strstr(d->exec, "steam://rungameid/");
                            if (st)
                              {
                                 st += strlen("steam://rungameid/");
                                 unsigned int id = atoi(st);
                                 if (id == ec->steam.steam_game_id)
                                   ec->desktop = d;
                              }
                         }
                    }
               }
          }
        if (!ec->desktop)
          {
             E_Exec_Instance *inst;

             inst = e_exec_startup_id_pid_instance_find(ec->netwm.startup_id,
                                                        ec->netwm.pid);
             if (inst && inst->clients)
               {
                  E_Client *ec2 = eina_list_data_get(inst->clients);

                  if (ec2->netwm.pid == ec->netwm.pid)
                    ec->desktop = inst->desktop;
               }
             else if (inst)
               ec->desktop = inst->desktop;
             if (ec->desktop) efreet_desktop_ref(ec->desktop);
          }
        if (!ec->desktop)
          {
             if (ec->internal && (ec->icccm.class && (!strncmp(ec->icccm.class, "e_fwin::", 8))))
               ec->desktop = efreet_util_desktop_exec_find("enlightenment_filemanager");
          }
        if (!ec->desktop)
          {
             if ((ec->icccm.name) || (ec->icccm.class))
               ec->desktop = efreet_util_desktop_wm_class_find(ec->icccm.name,
                                                               ec->icccm.class);
          }
        if (!ec->desktop && ec->icccm.command.argv && (ec->icccm.command.argc > 0))
          {
             ec->desktop = efreet_util_desktop_exec_find(ec->icccm.command.argv[0]);
          }
        if (!ec->desktop)
          {
             // special case hacks for specific apps that just don't do things
             // right so we have to work around them
             if (ec->icccm.class && ec->icccm.name &&
                 (!strcmp(ec->icccm.class, "Steam")) &&
                 (!strcmp(ec->icccm.name, "Steam")))
               {
                  ec->desktop = efreet_util_desktop_file_id_find("steam.desktop");
               }
             /* libreoffice and maybe others match window class
                with .desktop file name */
             else if (ec->icccm.class)
               {
                  char buf[4096] = {0};
                  snprintf(buf, sizeof(buf), "%s.desktop", ec->icccm.class);
                  ec->desktop = efreet_util_desktop_file_id_find(buf);
                  if (!ec->desktop)
                    {
                       char *s;

                       strncpy(buf, ec->icccm.class, sizeof(buf) - 1);
                       s = buf;
                       eina_str_tolower(&s);
                       if (strcmp(s, ec->icccm.class))
                         ec->desktop = efreet_util_desktop_exec_find(s);
                    }
               }
          }
        if (!ec->desktop && ec->icccm.name)
          {
             /* this works for most cases as fallback. useful when app is
                run from a shell  */
             ec->desktop = efreet_util_desktop_exec_find(ec->icccm.name);
          }
        if (!ec->desktop && ec->parent)
          {
             E_Client *ec2 = ec->parent;
             if (ec2->desktop)
               {
                  efreet_desktop_ref(ec2->desktop);
                  ec->desktop = ec2->desktop;
               }
          }

        if (ec->desktop)
          {
             if (!ec->exe_inst)
               e_exec_phony(ec);
             if (!ec->exe_inst->desktop)
               {
                  efreet_desktop_ref(ec->desktop);
                  ec->exe_inst->desktop = ec->desktop;
               }
          }
        ec->changes.icon = !e_comp_object_frame_icon_update(ec->frame);
        prop |= E_CLIENT_PROPERTY_ICON;
     }

   if (ec->new_client)
     e_comp->new_clients--;
   ec->new_client = 0;
   ec->changed = ec->changes.pos || ec->changes.size ||
                 ec->changes.stack || ec->changes.prop || ec->changes.border ||
                 ec->changes.reset_gravity || ec->changes.shading || ec->changes.shaded ||
                 ec->changes.shape || ec->changes.shape_input || ec->changes.icon ||
                 ec->changes.internal_state ||
                 ec->changes.need_maximize || ec->changes.need_unmaximize;
   ec->changes.stack = 0;

   if ((!ec->input_only) && (!ec->iconic) &&
       ((!ec->zone) || e_client_util_desk_visible(ec, e_desk_current_get(ec->zone))) &&
       ((ec->take_focus) || (ec->want_focus)))
     {
        ec->take_focus = 0;
        if ((ec->stack.prev) && (!ec->stack.next) &&
            (ec->stack.prev == e_client_focused_get()))
          {
             e_client_focus_set_with_pointer(ec);
          }
        else if ((e_config->focus_setting == E_FOCUS_NEW_WINDOW) || (ec->want_focus))
          {
             ec->want_focus = 0;
             e_client_focus_set_with_pointer(ec);
          }
        else if (ec->dialog)
          {
             if ((e_config->focus_setting == E_FOCUS_NEW_DIALOG) ||
                 ((e_config->focus_setting == E_FOCUS_NEW_DIALOG_IF_OWNER_FOCUSED) &&
                  (ec->parent == e_client_focused_get())))
               {
                  e_client_focus_set_with_pointer(ec);
               }
          }
        else
          {
             /* focus window by default when it is the only one on desk */
             E_Client *ec2 = NULL;
             Eina_List *l;
             EINA_LIST_FOREACH(focus_stack, l, ec2)
               {
                  if (ec == ec2) continue;
                  if ((!ec2->iconic) && (ec2->visible) &&
                      ((ec->desk == ec2->desk) || ec2->sticky))
                    break;
               }

             if (!ec2)
               {
                  e_client_focus_set_with_pointer(ec);
               }
          }
     }
   else
     ec->take_focus = ec->want_focus = 0;

   if (ec->changes.need_maximize)
     {
        E_Maximize max = ec->maximized;
        ec->maximized = E_MAXIMIZE_NONE;
        e_client_maximize(ec, max);
        ec->changes.need_maximize = 0;
     }
   else if (ec->changes.need_unmaximize)
     {
        e_client_unmaximize(ec, ec->maximized);
        ec->changes.need_unmaximize = 0;
     }

   if (ec->need_fullscreen)
     {
        e_client_fullscreen(ec, e_config->fullscreen_policy);
        ec->need_fullscreen = 0;
     }

   if (rem_change)
     e_remember_update(ec);

   if (send_event && rem_change && prop)
     {
        _e_client_event_property(ec, prop);
     }
   _e_client_hook_call(E_CLIENT_HOOK_EVAL_END, ec);
}

static void
_e_client_frame_update(E_Client *ec)
{
   const char *bordername;

   ec->border.changed = 0;
   if ((!e_comp_object_frame_allowed(ec->frame)) && (!e_comp_object_frame_exists(ec->frame)))
     return;
   if (ec->fullscreen || ec->borderless)
     bordername = "borderless";
   else if (ec->bordername)
     bordername = ec->bordername;
   else if (ec->mwm.borderless)
     bordername = "borderless";
   else if (((ec->icccm.transient_for != 0) || (ec->dialog)) &&
            (ec->icccm.min_w == ec->icccm.max_w) &&
            (ec->icccm.min_h == ec->icccm.max_h))
     bordername = "noresize_dialog";
   else if ((ec->icccm.min_w == ec->icccm.max_w) &&
            (ec->icccm.min_h == ec->icccm.max_h))
     bordername = "noresize";
   else if (ec->shaped)
     bordername = "shaped";
   else if (e_pixmap_is_x(ec->pixmap) &&
            ((!ec->icccm.accepts_focus) &&
            (!ec->icccm.take_focus)))
     bordername = "nofocus";
   else if (ec->urgent)
     bordername = "urgent";
   else if (((ec->icccm.transient_for && (!ec->netwm.type)) || (ec->dialog)) &&
            (e_pixmap_is_x(ec->pixmap)))
     bordername = "dialog";
   else if (ec->netwm.state.modal)
     bordername = "modal";
   else if ((ec->netwm.state.skip_taskbar) ||
            (ec->netwm.state.skip_pager))
     bordername = "skipped";
  /*
   else if ((ec->internal) && (ec->icccm.class) &&
            (!strncmp(ec->icccm.class, "e_fwin", 6)))
     bordername = "internal_fileman";
  */
   else
     bordername = e_config->theme_default_border_style;
   if (!bordername) bordername = "default";

   e_client_border_set(ec, bordername);
}

////////////////////////////////////////////////
EINTERN void
e_client_idler_before(void)
{
   const Eina_List *l;
   E_Client *ec;

   if ((!eina_hash_population(clients_hash[0])) && (!eina_hash_population(clients_hash[1]))) return;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        Eina_Stringshare *title;
        // pass 1 - eval0. fetch properties on new or on change and
        // call hooks to decide what to do - maybe move/resize
        if (ec->ignored || (!ec->changed)) continue;

        if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_PRE_FETCH, ec)) continue;
        /* FETCH is hooked by the compositor to get client hints */
        title = e_client_util_name_get(ec);
        if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_FETCH, ec)) continue;
        if (title != e_client_util_name_get(ec))
          _e_client_event_property(ec, E_CLIENT_PROPERTY_TITLE);
        /* PRE_POST_FETCH calls e_remember apply for new client */
        if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_PRE_POST_FETCH, ec)) continue;
        if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_POST_FETCH, ec)) continue;
        if (!_e_client_hook_call(E_CLIENT_HOOK_EVAL_PRE_FRAME_ASSIGN, ec)) continue;

        if ((ec->border.changed) && (!ec->shaded) && (!e_client_is_stacking(ec)) &&
            ((!ec->override) || ec->internal) &&
            (!(((ec->maximized & E_MAXIMIZE_TYPE) == E_MAXIMIZE_FULLSCREEN))))
          _e_client_frame_update(ec);
        ec->border.changed = 0;
        _e_client_hook_call(E_CLIENT_HOOK_EVAL_POST_FRAME_ASSIGN, ec);
     }

   E_CLIENT_FOREACH(ec)
     {
        if (ec->ignored) continue;
        // pass 2 - show windows needing show
        if ((ec->changes.visible) && (ec->visible) &&
            (!ec->new_client) && (!ec->changes.pos) &&
            (!ec->changes.size))
          {
             evas_object_show(ec->frame);
             ec->changes.visible = !evas_object_visible_get(ec->frame);
          }

        if ((!ec->new_client) && (!e_client_util_ignored_get(ec)) &&
            (!E_INSIDE(ec->x, ec->y, 0, 0, e_comp->w - 5, e_comp->h - 5)) &&
            (!E_INSIDE(ec->x, ec->y, 0 - ec->w + 5, 0 - ec->h + 5, e_comp->w - 5, e_comp->h - 5))
            )
          {
             if (e_config->screen_limits != E_CLIENT_OFFSCREEN_LIMIT_ALLOW_FULL)
               {
                  if (ec->parent)
                    e_comp_object_util_center_on(ec->frame, ec->parent->frame);
                  else
                    _e_client_move_lost_window_to_center(ec);
               }
          }
        // handle window stack
        if (!ec->stack.prev && ec->stack.next)
          {
             if (ec->stack.ignore == 0)
               {
                  Eina_List *ll, *list = e_client_stack_list_prepare(ec);
                  E_Client *child, *bottom, *moving = NULL, *rel;
                  int x, y;

                  bottom = rel = e_client_stack_bottom_get(ec);
                  EINA_LIST_FOREACH(list, ll, child)
                    {
                       if (child->moving)
                         {
                            moving = child;
                            break;
                         }
                    }
                  if (moving)
                    {
                       Evas_Coord ox, oy;

                       evas_object_geometry_get(ec->frame, &ox, &oy, NULL, NULL);
                       rel = moving;
                    }
                  EINA_LIST_FOREACH(list, ll, child)
                    {
                       if (moving)
                         {
                            if (child == moving) continue;
                         }
                       else if (child == bottom) continue;
                       x = rel->x + ((rel->w - child->w) / 2);
                       y = rel->y + ((rel->h - child->h) / 2);
                       if ((x != child->x)  || (y != child->y))
                         {
                            child->x = x;
                            child->y = y;
                            child->pre_cb.x = x;
                            child->pre_cb.y = y;
                            child->changes.pos = 1;
                            child->changed = 1;
                         }
                    }
                  e_client_stack_list_finish(list);
               }
          }
     }

   if (_e_client_layout_cb)
     _e_client_layout_cb();

   // pass 3 - hide windows needing hide and eval (main eval)
   E_CLIENT_FOREACH(ec)
     {
        if (ec->ignored || e_object_is_del(E_OBJECT(ec))) continue;

        if ((ec->changes.visible) && (!ec->visible))
          {
             evas_object_hide(ec->frame);
             ec->changes.visible = 0;
          }

        if (ec->changed)
          _e_client_eval(ec);

        if ((ec->changes.visible) && (ec->visible) && (!ec->changed))
          {
             evas_object_show(ec->frame);
             ec->changes.visible = !evas_object_visible_get(ec->frame);
             if (ec->changes.visible) EC_CHANGED(ec);
             if (!e_client_util_desk_visible(ec, e_desk_current_get(ec->zone)))
               evas_object_hide(ec->frame);
          }
     }
}


EINTERN Eina_Bool
e_client_init(void)
{
   clients_hash[0] = eina_hash_pointer_new(NULL);
   clients_hash[1] = eina_hash_pointer_new(NULL);

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_POINTER_WARP,
                         _e_client_cb_pointer_warp, NULL);
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_UPDATE,
                         _e_client_cb_efreet_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_ICON_CACHE_UPDATE,
                         _e_client_cb_efreet_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CONFIG_ICON_THEME,
                         _e_client_cb_config_icon_theme, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CONFIG_MODE_CHANGED,
                         _e_client_cb_config_mode, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_DESK_WINDOW_PROFILE_CHANGE,
                         _e_client_cb_desk_window_profile_change, NULL);

   E_EVENT_CLIENT_ADD = ecore_event_type_new();
   E_EVENT_CLIENT_REMOVE = ecore_event_type_new();
   E_EVENT_CLIENT_DESK_SET = ecore_event_type_new();
   E_EVENT_CLIENT_ZONE_SET = ecore_event_type_new();
   E_EVENT_CLIENT_RESIZE = ecore_event_type_new();
   E_EVENT_CLIENT_MOVE = ecore_event_type_new();
   E_EVENT_CLIENT_SHOW = ecore_event_type_new();
   E_EVENT_CLIENT_HIDE = ecore_event_type_new();
   E_EVENT_CLIENT_ICONIFY = ecore_event_type_new();
   E_EVENT_CLIENT_UNICONIFY = ecore_event_type_new();
   E_EVENT_CLIENT_STACK = ecore_event_type_new();
   E_EVENT_CLIENT_FOCUS_IN = ecore_event_type_new();
   E_EVENT_CLIENT_FOCUS_OUT = ecore_event_type_new();
   E_EVENT_CLIENT_PROPERTY = ecore_event_type_new();
   E_EVENT_CLIENT_FULLSCREEN = ecore_event_type_new();
   E_EVENT_CLIENT_UNFULLSCREEN = ecore_event_type_new();

   return (!!clients_hash[1]);
}

EINTERN void
e_client_shutdown(void)
{
   E_FREE_FUNC(clients_hash[0], eina_hash_free);
   E_FREE_FUNC(clients_hash[1], eina_hash_free);

   E_FREE_LIST(handlers, ecore_event_handler_del);

   e_int_client_menu_hooks_clear();
   E_FREE_FUNC(warp_timer, ecore_timer_del);
   warp_client = NULL;
}

E_API void
e_client_unignore(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->ignored) return;

   ec->ignored = 0;
   if (!e_client_util_ignored_get(ec))
     {
        if (starting)
          focus_stack = eina_list_prepend(focus_stack, ec);
        else
          focus_stack = eina_list_append(focus_stack, ec);
     }
   _e_client_event_simple(ec, E_EVENT_CLIENT_ADD);
   _e_client_hook_call(E_CLIENT_HOOK_UNIGNORE, ec);
}

E_API E_Client *
e_client_new(E_Pixmap *cp, int first_map, int internal)
{
   E_Client *ec;
   E_Pixmap_Type ptype = e_pixmap_type_get(cp);

   if ((ptype != E_PIXMAP_TYPE_X) && (ptype != E_PIXMAP_TYPE_WL)) return NULL;
   if (eina_hash_find(clients_hash[ptype], &cp)) return NULL;

   ec = E_OBJECT_ALLOC(E_Client, E_CLIENT_TYPE, _e_client_free);
   if (!ec) return NULL;
   e_object_del_func_set(E_OBJECT(ec), E_OBJECT_CLEANUP_FUNC(_e_client_del));

   ec->focus_policy_override = E_FOCUS_LAST;
   ec->w = 1;
   ec->h = 1;
   ec->internal = internal;

   ec->pixmap = cp;
   e_pixmap_client_set(cp, ec);
   ec->resize_mode = E_POINTER_RESIZE_NONE;
   ec->layer = E_LAYER_CLIENT_NORMAL;

   /* printf("##- ON MAP CLIENT 0x%x SIZE %ix%i %i:%i\n",
    *     ec->win, ec->w, ec->h, att->x, att->y); */

   /* FIXME: if first_map is 1 then we should ignore the first hide event
    * or ensure the window is already hidden and events flushed before we
    * create a border for it */
   if (first_map)
     {
        // printf("##- FIRST MAP\n");
        ec->re_manage = 1;
        // needed to be 1 for internal windw and on restart.
        // ec->ignore_first_unmap = 2;
     }
   ec->offer_resistance = 1;
   ec->new_client = 1;
   e_comp->new_clients++;

   if (!_e_client_hook_call(E_CLIENT_HOOK_NEW_CLIENT, ec))
     {
        /* delete the above allocated object */
        //e_object_del(E_OBJECT(ec));
        return NULL;
     }

   ec->icccm.title = NULL;
   ec->icccm.name = NULL;
   ec->icccm.class = NULL;
   ec->icccm.icon_name = NULL;
   ec->icccm.machine = NULL;
   ec->icccm.min_w = 1;
   ec->icccm.min_h = 1;
   ec->icccm.max_w = 32767;
   ec->icccm.max_h = 32767;
   ec->icccm.base_w = 0;
   ec->icccm.base_h = 0;
   ec->icccm.step_w = -1;
   ec->icccm.step_h = -1;
   ec->icccm.min_aspect = 0.0;
   ec->icccm.max_aspect = 0.0;

   ec->netwm.pid = 0;
   ec->netwm.name = NULL;
   ec->netwm.icon_name = NULL;
   ec->netwm.desktop = 0;
   ec->netwm.state.modal = 0;
   ec->netwm.state.sticky = 0;
   ec->netwm.state.shaded = 0;
   ec->netwm.state.hidden = 0;
   ec->netwm.state.maximized_v = 0;
   ec->netwm.state.maximized_h = 0;
   ec->netwm.state.skip_taskbar = 0;
   ec->netwm.state.skip_pager = 0;
   ec->netwm.state.fullscreen = 0;
   ec->netwm.state.stacking = E_STACKING_NONE;
   ec->netwm.action.move = 0;
   ec->netwm.action.resize = 0;
   ec->netwm.action.minimize = 0;
   ec->netwm.action.shade = 0;
   ec->netwm.action.stick = 0;
   ec->netwm.action.maximized_h = 0;
   ec->netwm.action.maximized_v = 0;
   ec->netwm.action.fullscreen = 0;
   ec->netwm.action.change_desktop = 0;
   ec->netwm.action.close = 0;
   ec->netwm.opacity = 255;

   e_comp->clients = eina_list_append(e_comp->clients, ec);
   eina_hash_add(clients_hash[ptype], &ec->pixmap, ec);

   if (!ec->ignored) EC_CHANGED(ec);

   e_comp_object_client_add(ec);

   if (ec->override)
     _e_client_zone_update(ec);
   else if (!ec->desk)
     e_client_desk_set(ec, e_desk_current_get(e_zone_current_get()));
   if (!ec->re_manage)
     ec->placed = ec->changes.pos = 0; //ensure placement is run
   if (ec->frame)
     {
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_SHOW, _e_client_cb_evas_show, ec);
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_HIDE, _e_client_cb_evas_hide, ec);
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_MOVE, _e_client_cb_evas_move, ec);
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_RESIZE, _e_client_cb_evas_resize, ec);
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_RESTACK, _e_client_cb_evas_restack, ec);
        evas_object_smart_callback_add(ec->frame, "shade_done", _e_client_cb_evas_shade_done, ec);
        if (ec->override)
          evas_object_layer_set(ec->frame, E_LAYER_CLIENT_TOP);
        else
          evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
     }
   if (!e_client_util_ignored_get(ec))
     {
        if (starting)
          focus_stack = eina_list_prepend(focus_stack, ec);
        else
          focus_stack = eina_list_append(focus_stack, ec);
     }

   e_hints_client_list_set();
   return ec;
}

E_API Eina_Bool
e_client_desk_window_profile_available_check(E_Client *ec, const char *profile)
{
   int i;

   E_OBJECT_CHECK_RETURN(ec, EINA_FALSE);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(profile, EINA_FALSE);
   EINA_SAFETY_ON_FALSE_RETURN_VAL(ec->e.state.profile.use, EINA_FALSE);
   if (ec->e.state.profile.num == 0) return EINA_TRUE;

   for (i = 0; i < ec->e.state.profile.num; i++)
     {
        if (!e_util_strcmp(ec->e.state.profile.available_list[i],
                           profile))
          return EINA_TRUE;
     }

   return EINA_FALSE;
}

E_API void
e_client_desk_window_profile_wait_desk_set(E_Client *ec, E_Desk *desk)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);

   if (ec->e.state.profile.wait_desk == desk) return;

   if (ec->e.state.profile.wait_desk_delfn)
     {
        if (ec->e.state.profile.wait_desk)
          e_object_delfn_del(E_OBJECT(ec->e.state.profile.wait_desk),
                             ec->e.state.profile.wait_desk_delfn);
        ec->e.state.profile.wait_desk_delfn = NULL;
     }

   if (ec->e.state.profile.wait_desk)
     e_object_unref(E_OBJECT(ec->e.state.profile.wait_desk));
   ec->e.state.profile.wait_desk = NULL;

   if (desk)
     {
        ec->e.state.profile.wait_desk_delfn =
           e_object_delfn_add(E_OBJECT(desk),
                              _e_client_desk_window_profile_wait_desk_delfn,
                              ec);
     }
   ec->e.state.profile.wait_desk = desk;
   if (ec->e.state.profile.wait_desk)
     e_object_ref(E_OBJECT(ec->e.state.profile.wait_desk));
}

E_API void
e_client_desk_set(E_Client *ec, E_Desk *desk)
{
   E_Event_Client_Desk_Set *ev;
   E_Desk *old_desk;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);
   if (ec->desk == desk) return;
   if (ec->zone)
     eina_stringshare_replace(&(ec->restore_zone_id), ec->zone->randr2_id);
   if (ec->e.state.profile.use)
     {
        const char *profile = desk->window_profile;

        if (!profile) profile = elm_config_profile_get();
        if (!profile) profile = "standard";
        if (e_util_strcmp(ec->e.state.profile.name, profile))
          {
             if (e_client_desk_window_profile_available_check(ec, profile))
               {
                  eina_stringshare_replace(&ec->e.state.profile.set, profile);
                  eina_stringshare_replace(&ec->e.state.profile.wait, NULL);
                  ec->e.state.profile.wait_for_done = 0;
                  e_client_desk_window_profile_wait_desk_set(ec, desk);
                  EC_CHANGED(ec);
               }
          }
     }

   if (ec->fullscreen)
     {
        if (ec->desk)
          ec->desk->fullscreen_clients = eina_list_remove(ec->desk->fullscreen_clients, ec);
        if (!eina_list_data_find(desk->fullscreen_clients, ec))
          desk->fullscreen_clients = eina_list_append(desk->fullscreen_clients, ec);
     }
   old_desk = ec->desk;
   ec->desk = desk;
   if (ec->frame)
     {
        e_comp_object_effect_unclip(ec->frame);
        e_comp_object_effect_set(ec->frame, NULL);
     }
   if (desk->visible || ec->sticky)
     {
        // force visibility if its a stack window going onto this desktop
        if (ec->stack.prev || ec->stack.next) ec->hidden = 0;
        if ((!ec->hidden) && (!ec->iconic))
          evas_object_show(ec->frame);
     }
   else
     {
        ec->hidden = 1;
        evas_object_hide(ec->frame);
     }
   e_client_comp_hidden_set(ec, (!desk->visible) && (!ec->sticky));
   e_client_zone_set(ec, desk->zone);

   e_hints_window_desktop_set(ec);

   if (old_desk)
     {
        ev = E_NEW(E_Event_Client_Desk_Set, 1);
        ev->ec = ec;
        UNREFD(ec, 4);
        e_object_ref(E_OBJECT(ec));
        ev->desk = old_desk;
        e_object_ref(E_OBJECT(old_desk));
        ecore_event_add(E_EVENT_CLIENT_DESK_SET, ev, (Ecore_End_Cb)_e_client_event_desk_set_free, NULL);

        if (old_desk->zone == ec->zone)
          {
             if (ec->maximized || ec->fullscreen)
               e_client_rescale(ec);
          }
     }

   if (ec->stack.prev || ec->stack.next)
     {
        if (ec->stack.ignore == 0)
          {
             Eina_List *l, *list = e_client_stack_list_prepare(ec);
             E_Client *child;

             EINA_LIST_FOREACH(list, l, child)
               {
                  if (child == ec) break;
                  e_client_desk_set(child, ec->desk);
                  evas_object_stack_below(child->frame, ec->frame);
               }
             EINA_LIST_REVERSE_FOREACH(list, l, child)
               {
                  if (child == ec) break;
                  e_client_desk_set(child, ec->desk);
                  evas_object_stack_above(child->frame, ec->frame);
               }
             e_client_stack_list_finish(list);
          }
     }
   else
     {
        if (e_config->transient.desktop)
          {
             E_Client *child;
             const Eina_List *l;

             EINA_LIST_FOREACH(ec->transients, l, child)
               e_client_desk_set(child, ec->desk);
             e_client_transients_restack(ec);
          }
     }

   e_remember_update(ec);
   _e_client_hook_call(E_CLIENT_HOOK_DESK_SET, ec);
   evas_object_smart_callback_call(ec->frame, "desk_change", ec);
}

E_API Eina_Bool
e_client_comp_grabbed_get(void)
{
   return comp_grabbed;
}

E_API E_Client *
e_client_action_get(void)
{
   return action_client;
}

E_API E_Client *
e_client_warping_get(void)
{
   return warp_client;
}


E_API Eina_List *
e_clients_immortal_list(void)
{
   const Eina_List *l;
   Eina_List *list = NULL;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (ec->lock_life)
          list = eina_list_append(list, ec);
     }
   return list;
}

//////////////////////////////////////////////////////////

E_API void
e_client_mouse_in(E_Client *ec, int x, int y)
{
   if (comp_grabbed) return;
   if (warp_client && (ec != warp_client)) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->desk && ec->desk->animate_count) return;
   ec->mouse.current.mx = x;
   ec->mouse.current.my = y;
   ec->mouse.in = 1;
   if ((!ec->iconic) && (!e_client_util_ignored_get(ec)))
     e_focus_event_mouse_in(ec);
}

E_API void
e_client_mouse_out(E_Client *ec, int x, int y)
{
   if (comp_grabbed) return;
   if (ec->desk && ec->desk->animate_count) return;
   if (e_pixmap_is_x(ec->pixmap) && E_INSIDE(x, y, ec->x, ec->y, ec->w, ec->h)) return;

   ec->mouse.current.mx = x;
   ec->mouse.current.my = y;
   ec->mouse.in = 0;
   if (ec->fullscreen) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if ((!ec->iconic) && (!e_client_util_ignored_get(ec)))
     e_focus_event_mouse_out(ec);
}

E_API void
e_client_mouse_wheel(E_Client *ec, Evas_Point *output, E_Binding_Event_Wheel *ev)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (action_client) return;
   ec->mouse.current.mx = output->x;
   ec->mouse.current.my = output->y;
   if ((!ec->cur_mouse_action) && (!e_client_util_ignored_get(ec)))
     e_bindings_wheel_event_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(ec), ev);
}

E_API void
e_client_mouse_down(E_Client *ec, int button, Evas_Point *output, E_Binding_Event_Mouse_Button *ev)
{
   Eina_Bool did_act = EINA_FALSE;
   E_Client *pfocus;
   int player;

   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (action_client || ec->iconic || e_client_util_ignored_get(ec)) return;
   if ((button >= 1) && (button <= 3))
     {
        ec->mouse.last_down[button - 1].mx = output->x;
        ec->mouse.last_down[button - 1].my = output->y;
        ec->mouse.last_down[button - 1].x = ec->x;
        ec->mouse.last_down[button - 1].y = ec->y;
        ec->mouse.last_down[button - 1].w = ec->w;
        ec->mouse.last_down[button - 1].h = ec->h;
     }
   else
     {
        ec->moveinfo.down.x = ec->x;
        ec->moveinfo.down.y = ec->y;
        ec->moveinfo.down.w = ec->w;
        ec->moveinfo.down.h = ec->h;
     }
   ec->mouse.current.mx = output->x;
   ec->mouse.current.my = output->y;
   pfocus = e_client_focused_get();
   player = ec->layer;
   if (!ec->cur_mouse_action)
     {
        ec->cur_mouse_action =
          e_bindings_mouse_down_event_handle(E_BINDING_CONTEXT_WINDOW,
                                             E_OBJECT(ec), ev);
        if (ec->next_mouse_action_ignore)
          ec->cur_mouse_action = NULL;
        if (ec->cur_mouse_action)
          {
             did_act = EINA_TRUE;
             e_object_ref(E_OBJECT(ec->cur_mouse_action));
             if (ec->internal)
               {
                  int button_mask, i;
                  Evas *e;

                  e = evas_object_evas_get(ec->internal_elm_win);
                  button_mask = evas_pointer_button_down_mask_get(e);
                  for (i = 0; i < 32; i++)
                    {
                      if ((button_mask & (1 << i)))
                        evas_event_feed_mouse_up(e, i + 1, EVAS_BUTTON_NONE, 0, NULL);
                    }
                  evas_event_feed_mouse_out(e, 0, NULL);
               }
          }
     }
   if ((!did_act) || (((pfocus == e_client_focused_get()) || (ec == e_client_focused_get())) && (ec->layer >= player)))
     e_focus_event_mouse_down(ec);
   if ((button >= 1) && (button <= 3))
     {
        ec->mouse.last_down[button - 1].mx = output->x;
        ec->mouse.last_down[button - 1].my = output->y;
        ec->mouse.last_down[button - 1].x = ec->x;
        ec->mouse.last_down[button - 1].y = ec->y;
        ec->mouse.last_down[button - 1].w = ec->w;
        ec->mouse.last_down[button - 1].h = ec->h;
     }
   else
     {
        ec->moveinfo.down.x = ec->x;
        ec->moveinfo.down.y = ec->y;
        ec->moveinfo.down.w = ec->w;
        ec->moveinfo.down.h = ec->h;
     }
   ec->mouse.current.mx = output->x;
   ec->mouse.current.my = output->y;
}

E_API void
e_client_mouse_up(E_Client *ec, int button, Evas_Point *output, E_Binding_Event_Mouse_Button* ev)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (ec->iconic || e_client_util_ignored_get(ec)) return;
   if ((button >= 1) && (button <= 3))
     {
        ec->mouse.last_up[button - 1].mx = output->x;
        ec->mouse.last_up[button - 1].my = output->y;
        ec->mouse.last_up[button - 1].x = ec->x;
        ec->mouse.last_up[button - 1].y = ec->y;
     }
   ec->mouse.current.mx = output->x;
   ec->mouse.current.my = output->y;
   /* also we don't pass the same params that went in - then again that */
   /* should be ok as we are just ending the action if it has an end */
   if (ec->cur_mouse_action)
     _e_client_mouse_action_end(ec);
   else
     {
        if (!e_bindings_mouse_up_event_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(ec), ev))
          e_focus_event_mouse_up(ec);
     }
   if ((button >= 1) && (button <= 3))
     {
        ec->mouse.last_up[button - 1].mx = output->x;
        ec->mouse.last_up[button - 1].my = output->y;
        ec->mouse.last_up[button - 1].x = ec->x;
        ec->mouse.last_up[button - 1].y = ec->y;
     }

   ec->drag.start = 0;
}

E_API void
e_client_mouse_move(E_Client *ec, Evas_Point *output)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (ec->iconic || e_client_util_ignored_get(ec)) return;
   if ((ec->mouse.current.mx == output->x) && (ec->mouse.current.my == output->y)) return;
   ec->mouse.current.mx = output->x;
   ec->mouse.current.my = output->y;
   if (ec->moving)
     {
        int x, y, new_x, new_y;
        int new_w, new_h;
        Eina_List *skiplist = NULL;

        if (action_handler_key) return;
        if ((ec->moveinfo.down.button >= 1) && (ec->moveinfo.down.button <= 3))
          {
             x = ec->mouse.last_down[ec->moveinfo.down.button - 1].x +
               (ec->mouse.current.mx - ec->moveinfo.down.mx);
             y = ec->mouse.last_down[ec->moveinfo.down.button - 1].y +
               (ec->mouse.current.my - ec->moveinfo.down.my);
          }
        else
          {
             x = ec->moveinfo.down.x +
               (ec->mouse.current.mx - ec->moveinfo.down.mx);
             y = ec->moveinfo.down.y +
               (ec->mouse.current.my - ec->moveinfo.down.my);
          }
        e_comp_object_frame_xy_adjust(ec->frame, x, y, &new_x, &new_y);

        skiplist = eina_list_append(skiplist, ec);
        e_resist_client_position(skiplist,
                                 ec->x, ec->y, ec->w, ec->h,
                                 x, y, ec->w, ec->h,
                                 &new_x, &new_y, &new_w, &new_h);
        eina_list_free(skiplist);

        if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
          _e_client_stay_within_canvas(ec, x, y, &new_x, &new_y);

        ec->shelf_fix.x = 0;
        ec->shelf_fix.y = 0;
        ec->shelf_fix.modified = 0;
        evas_object_move(ec->frame, new_x, new_y);
     }
   else if (e_client_util_resizing_get(ec))
     {
        if (action_handler_key) return;
        _e_client_resize_handle(ec);
     }
   else if (ec->drag.start)
     {
        if ((ec->drag.x == -1) && (ec->drag.y == -1))
          {
             ec->drag.x = output->x;
             ec->drag.y = output->y;
          }
        else if (ec->zone)
          {
             int dx, dy;

             dx = ec->drag.x - output->x;
             dy = ec->drag.y - output->y;
             if (((dx * dx) + (dy * dy)) >
                 (e_config->drag_resist * e_config->drag_resist))
               {
                  /* start drag! */
                  if (
#ifndef HAVE_WAYLAND_ONLY
                    ec->netwm.icons ||
#endif
                    ec->desktop || ec->internal_icon)
                    {
                       Evas_Object *o = NULL;
                       int x, y, w, h;
                       const char *drag_types[] = { "enlightenment/border" };

                       REFD(ec, 1);
                       e_object_ref(E_OBJECT(ec));
                       e_comp_object_frame_icon_geometry_get(ec->frame, &x, &y, &w, &h);

                       client_drag = e_drag_new(output->x, output->y,
                                                drag_types, 1, ec, -1,
                                                NULL,
                                                _e_client_cb_drag_finished);
                       client_drag->button_mask = evas_pointer_button_down_mask_get(e_comp->evas);
                       e_drag_resize(client_drag, w, h);

                       o = e_client_icon_add(ec, client_drag->evas);
                       if (!o)
                         {
                            /* FIXME: fallback icon for drag */
                            o = evas_object_rectangle_add(client_drag->evas);
                            evas_object_color_set(o, 255, 255, 255, 255);
                         }

                       e_drag_object_set(client_drag, o);
                       e_drag_start(client_drag,
                                    output->x + (ec->drag.x - x),
                                    output->y + (ec->drag.y - y));
                    }
                  ec->drag.start = 0;
               }
          }
     }
}
///////////////////////////////////////////////////////

E_API void
e_client_res_change_geometry_save(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (ec->pre_res_change.valid) return;
   ec->pre_res_change.valid = 1;
   ec->pre_res_change.x = ec->x;
   ec->pre_res_change.y = ec->y;
   ec->pre_res_change.w = ec->w;
   ec->pre_res_change.h = ec->h;
   ec->pre_res_change.saved.x = ec->saved.x;
   ec->pre_res_change.saved.y = ec->saved.y;
   ec->pre_res_change.saved.w = ec->saved.w;
   ec->pre_res_change.saved.h = ec->saved.h;
}

E_API void
e_client_res_change_geometry_restore(E_Client *ec)
{
   struct
   {
      unsigned char valid E_BITFIELD;
      int           x, y, w, h;
      struct
      {
         int x, y, w, h;
      } saved;
   } pre_res_change;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->pre_res_change.valid) return;
   if (ec->new_client) return;
   if (!ec->zone) return;

   memcpy(&pre_res_change, &ec->pre_res_change, sizeof(pre_res_change));

   if (ec->fullscreen)
     {
        if ((eina_list_count(e_comp->zones) > 1) ||
            (e_config->fullscreen_policy == E_FULLSCREEN_RESIZE))
          evas_object_geometry_set(ec->frame, ec->zone->x, ec->zone->y, ec->zone->w, ec->zone->h);
        else
          {
             e_client_unfullscreen(ec);
             e_client_fullscreen(ec, e_config->fullscreen_policy);
          }
     }
   else if (ec->maximized != E_MAXIMIZE_NONE)
     {
        int x, y, w, h;

        if (e_client_maximize_geometry_get(ec, ec->maximized, &x, &y, &w, &h))
          {
             Eina_Bool override = ec->maximize_override;
             ec->maximize_override = 1;
             evas_object_geometry_set(ec->frame, x, y, w, h);
             ec->maximize_override = override;
          }
        else
          {
             E_Maximize max = ec->maximized;
             e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
             e_client_maximize(ec, max);
          }
     }
   else
     {
        int x, y, w, h, zx, zy, zw, zh;

        ec->saved.x = ec->pre_res_change.saved.x;
        ec->saved.y = ec->pre_res_change.saved.y;
        ec->saved.w = ec->pre_res_change.saved.w;
        ec->saved.h = ec->pre_res_change.saved.h;

        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);

        if (ec->saved.w > zw)
          ec->saved.w = zw;
        if ((ec->saved.x + ec->saved.w) > (zx + zw))
          ec->saved.x = zx + zw - ec->saved.w;

        if (ec->saved.h > zh)
          ec->saved.h = zh;
        if ((ec->saved.y + ec->saved.h) > (zy + zh))
          ec->saved.y = zy + zh - ec->saved.h;

        x = ec->pre_res_change.x;
        y = ec->pre_res_change.y;
        w = ec->pre_res_change.w;
        h = ec->pre_res_change.h;
        if (w > zw)
          w = zw;
        if (h > zh)
          h = zh;
        if ((x + w) > (zx + zw))
          x = zx + zw - w;
        if ((y + h) > (zy + zh))
          y = zy + zh - h;
        evas_object_move(ec->frame, x, y);
        if (w && h)
          {
             e_client_resize_limit(ec, &w, &h);
             evas_object_resize(ec->frame, w, h);
          }
     }
   memcpy(&ec->pre_res_change, &pre_res_change, sizeof(pre_res_change));
}

E_API void
e_client_rescale(E_Client *ec)
{
   Eina_Bool shaded;
   int shade_dir;

   if (stopping) return;
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (e_comp->updating)
     {
        ec->changes.need_rescale = 1;
        EC_CHANGED(ec);
        return;
     }
   ec->changes.need_rescale = 0;

   shaded = ec->shaded;
   shade_dir = ec->shade_dir;
   if (shaded) e_client_unshade(ec, shade_dir);
   ec->pre_res_change.valid = 0;
   e_client_res_change_geometry_save(ec);
   e_client_res_change_geometry_restore(ec);
   if (shaded) e_client_shade(ec, shade_dir);
}

E_API void
e_client_zone_set(E_Client *ec, E_Zone *zone)
{
   E_Event_Client_Zone_Set *ev;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   if (ec->zone == zone) return;

   /* if the window does not lie in the new zone, move it so that it does */
   if (!E_INTERSECTS(ec->x, ec->y, ec->w, ec->h, zone->x, zone->y, zone->w, zone->h))
     {
        int x, y;

        if (ec->zone)
          {
             /* first guess -- get offset from old zone, and apply to new zone */
             x = zone->x + (ec->x - ec->zone->x);
             y = zone->y + (ec->y - ec->zone->y);
          }
        else
          x = ec->x, y = ec->y;

        /* keep window from hanging off bottom and left */
        if (x + ec->w > zone->x + zone->w) x += (zone->x + zone->w) - (x + ec->w);
        if (y + ec->h > zone->y + zone->h) y += (zone->y + zone->h) - (y + ec->h);

        /* make sure to and left are on screen (if the window is larger than the zone, it will hang off the bottom / right) */
        if (x < zone->x) x = zone->x;
        if (y < zone->y) y = zone->y;

        if (!E_INTERSECTS(x, y, ec->w, ec->h, zone->x, zone->y, zone->w, zone->h))
          {
             /* still not in zone at all, so just move it to closest edge */
             if (x < zone->x) x = zone->x;
             if (x >= zone->x + zone->w) x = zone->x + zone->w - ec->w;
             if (y < zone->y) y = zone->y;
             if (y >= zone->y + zone->h) y = zone->y + zone->h - ec->h;
          }
        evas_object_move(ec->frame, x, y);
     }

   ec->zone = zone;
   eina_stringshare_replace(&(ec->restore_zone_id), zone->randr2_id);

   if ((!ec->desk) || (ec->desk->zone != ec->zone))
     e_client_desk_set(ec, e_desk_current_get(ec->zone));

   ev = E_NEW(E_Event_Client_Zone_Set, 1);
   ev->ec = ec;
   REFD(ec, 5);
   e_object_ref(E_OBJECT(ec));
   ev->zone = zone;
   e_object_ref(E_OBJECT(zone));

   ecore_event_add(E_EVENT_CLIENT_ZONE_SET, ev, (Ecore_End_Cb)_e_client_event_zone_set_free, NULL);

   e_remember_update(ec);
   if (ec->maximized || ec->fullscreen)
     e_client_rescale(ec);
}

E_API void
e_client_geometry_get(E_Client *ec, int *x, int *y, int *w, int *h)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (ec->frame)
     evas_object_geometry_get(ec->frame, x, y, w, h);
   else
     {
        if (x) *x = ec->x;
        if (y) *y = ec->y;
        if (w) *w = ec->w;
        if (h) *h = ec->h;
     }
}

E_API E_Client *
e_client_above_get(const E_Client *ec)
{
   unsigned int x;
   E_Client *ec2;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ec, NULL);
   if (EINA_INLIST_GET(ec)->next) //check current layer
     {
        EINA_INLIST_FOREACH(EINA_INLIST_GET(ec)->next, ec2)
          if (!e_object_is_del(E_OBJECT(ec2)))
            return ec2;
     }
   if (ec->layer == E_LAYER_CLIENT_PRIO) return NULL;
   if (e_comp_canvas_client_layer_map(ec->layer) == 9999) return NULL;

   /* go up the layers until we find one */
   for (x = e_comp_canvas_layer_map(ec->layer) + 1; x <= e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); x++)
     {
        if (!e_comp->layers[x].clients) continue;
        EINA_INLIST_FOREACH(e_comp->layers[x].clients, ec2)
          if (!e_object_is_del(E_OBJECT(ec2)))
            return ec2;
     }
   return NULL;
}

E_API E_Client *
e_client_below_get(const E_Client *ec)
{
   unsigned int x;
   E_Client *ec2;
   Eina_Inlist *l;

   E_OBJECT_CHECK_RETURN(ec, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, NULL);

   EINA_SAFETY_ON_NULL_RETURN_VAL(ec, NULL);
   if (EINA_INLIST_GET(ec)->prev) //check current layer
     {
        for (l = EINA_INLIST_GET(ec)->prev; l; l = l->prev)
          {
             ec2 = EINA_INLIST_CONTAINER_GET(l, E_Client);;
             if (!e_object_is_del(E_OBJECT(ec2)))
               return ec2;
          }
     }
   if (ec->layer == E_LAYER_CLIENT_DESKTOP) return NULL;
   if (e_comp_canvas_client_layer_map(ec->layer) == 9999) return NULL;

   /* go down the layers until we find one */
   x = e_comp_canvas_layer_map(ec->layer);
   if (x > 0) x--;

   for (; x >= e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); x--)
     {
        if (!e_comp->layers[x].clients) continue;
        EINA_INLIST_REVERSE_FOREACH(e_comp->layers[x].clients, ec2)
          if (!e_object_is_del(E_OBJECT(ec2)))
            return ec2;
     }
   return NULL;
}

E_API E_Client *
e_client_bottom_get(void)
{
   unsigned int x;

   for (x = e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); x <= e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); x++)
     {
        E_Client *ec2;

        if (!e_comp->layers[x].clients) continue;
        EINA_INLIST_FOREACH(e_comp->layers[x].clients, ec2)
          if (!e_object_is_del(E_OBJECT(ec2)))
            return ec2;
     }
   return NULL;
}

E_API E_Client *
e_client_top_get(void)
{
   unsigned int x;

   for (x = e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); x >= e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); x--)
     {
        E_Client *ec2;

        if (!e_comp->layers[x].clients) continue;
        EINA_INLIST_REVERSE_FOREACH(e_comp->layers[x].clients, ec2)
          if (!e_object_is_del(E_OBJECT(ec2)))
            return ec2;
     }
   return NULL;
}

E_API unsigned int
e_clients_count(void)
{
   return eina_list_count(e_comp->clients);
}


/**
 * Set a callback which will be called just prior to updating the
 * move coordinates for a border
 */
E_API void
e_client_move_intercept_cb_set(E_Client *ec, E_Client_Move_Intercept_Cb cb)
{
   ec->move_intercept_cb = cb;
}

///////////////////////////////////////

E_API E_Client_Hook *
e_client_hook_add(E_Client_Hook_Point hookpoint, E_Client_Hook_Cb func, const void *data)
{
   E_Client_Hook *ch;

   EINA_SAFETY_ON_TRUE_RETURN_VAL(hookpoint >= E_CLIENT_HOOK_LAST, NULL);
   ch = E_NEW(E_Client_Hook, 1);
   if (!ch) return NULL;
   ch->hookpoint = hookpoint;
   ch->func = func;
   ch->data = (void*)data;
   _e_client_hooks[hookpoint] = eina_inlist_append(_e_client_hooks[hookpoint], EINA_INLIST_GET(ch));
   return ch;
}

E_API void
e_client_hook_del(E_Client_Hook *ch)
{
   ch->delete_me = 1;
   if (_e_client_hooks_walking == 0)
     {
        _e_client_hooks[ch->hookpoint] = eina_inlist_remove(_e_client_hooks[ch->hookpoint], EINA_INLIST_GET(ch));
        free(ch);
     }
   else
     _e_client_hooks_delete++;
}

///////////////////////////////////////

E_API void
e_client_focus_latest_set(E_Client *ec)
{
   if (!ec) CRI("ACK");
   if (focus_track_frozen > 0) return;
   focus_stack = eina_list_remove(focus_stack, ec);
   focus_stack = eina_list_prepend(focus_stack, ec);
}

E_API void
e_client_raise_latest_set(E_Client *ec)
{
   if (!ec) CRI("ACK");
   raise_stack = eina_list_remove(raise_stack, ec);
   raise_stack = eina_list_prepend(raise_stack, ec);
}

E_API Eina_Bool
e_client_focus_track_enabled(void)
{
   return !focus_track_frozen;
}

E_API void
e_client_focus_track_freeze(void)
{
   focus_track_frozen++;
}

E_API void
e_client_focus_track_thaw(void)
{
   if (focus_track_frozen)
     focus_track_frozen--;
}

E_API void
e_client_refocus(void)
{
   E_Client *ec;
   const Eina_List *l;

   EINA_LIST_FOREACH(e_client_focus_stack_get(), l, ec)
     if (ec->desk && ec->desk->visible && (!ec->iconic))
       {
          if (e_comp->input_key_grabs || e_comp->input_mouse_grabs) break;
          evas_object_focus_set(ec->frame, 1);
          break;
       }
}


/*
 * Sets the focus to the given client if necessary
 * There are 3 cases of different focus_policy-configurations:
 *
 * - E_FOCUS_CLICK: just set the focus, the most simple one
 *
 * - E_FOCUS_MOUSE: focus is where the mouse is, so try to
 *   warp the pointer to the window. If this fails (because
 *   the pointer is already in the window), just set the focus.
 *
 * - E_FOCUS_SLOPPY: focus is where the mouse is or on the
 *   last window which was focused, if the mouse is on the
 *   desktop. So, we need to look if there is another window
 *   under the pointer and warp to pointer to the right
 *   one if so (also, we set the focus afterwards). In case
 *   there is no window under pointer, the pointer is on the
 *   desktop and so we just set the focus.
 *
 *
 * This function is to be called when setting the focus was not
 * explicitly triggered by the user (by moving the mouse or
 * clicking for example), but implicitly (by closing a window,
 * the last focused window should get focus).
 *
 */
E_API void
e_client_focus_set_with_pointer(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   /* note: this is here as it seems there are enough apps that do not even
    * expect us to emulate a look of focus but not actually set x input
    * focus as we do - so simply abort any focuse set on such windows */
   if (e_pixmap_is_x(ec->pixmap))
     {
        /* be strict about accepting focus hint */
        if ((!ec->icccm.accepts_focus) &&
            (!ec->icccm.take_focus)) return;
     }
   ec = e_client_stack_active_adjust(ec);
   if (ec->lock_focus_out) return;
   if (ec == focused) return;
   evas_object_focus_set(ec->frame, 1);

   if (e_config->focus_policy == E_FOCUS_CLICK) return;
   if (!ec->visible) return;

   if (e_config->focus_policy == E_FOCUS_SLOPPY)
     {
        E_Client *pec;
        pec = e_client_under_pointer_get(ec->desk, ec);
        /* Do not slide pointer when disabled (probably breaks focus
         * on sloppy/mouse focus but requested by users). */
        if (e_config->pointer_slide && pec && (pec != ec))
          e_client_pointer_warp_to_center(ec);
     }
   else
     {
        if (e_config->pointer_slide)
          e_client_pointer_warp_to_center(ec);
     }
}

static Eina_Bool
_e_client_is_in_parents(E_Client *ec, E_Client *ec_find)
{
   if (ec == ec_find) return EINA_TRUE;
   if (e_object_is_del(E_OBJECT(ec))) return EINA_FALSE;
   if (ec->parent) return _e_client_is_in_parents(ec->parent, ec_find);
   return EINA_FALSE;
}

EINTERN void
e_client_focused_set(E_Client *ec)
{
   E_Client *ec2, *ec_unfocus = focused;
   Eina_List *l, *ll;

   if (ec == focused) return;
   focused = ec;
   if ((ec) && (ec->zone))
     {
        ec->focused = 1;
        e_client_urgent_set(ec, 0);
        if (!e_config->allow_above_fullscreen)
          {
             int x, total = ec->zone->desk_x_count * ec->zone->desk_y_count;

             for (x = 0; x < total; x++)
               {
                  E_Desk *desk = ec->zone->desks[x];
                  // if there's any fullscreen non-parents on this desk, unfullscreen them
                  EINA_LIST_FOREACH_SAFE(desk->fullscreen_clients, l, ll, ec2)
                    {
                       if (ec2 == ec) continue;
                       if (e_object_is_del(E_OBJECT(ec2))) continue;
                       // but only if it's the same desk or one of the clients is sticky
                       if ((desk == ec->desk) || (ec->sticky || ec2->sticky))
                         {
                            if (!_e_client_is_in_parents(ec, ec2))
                              {
                                 if (ec2->fullscreen)
                                   {
                                      e_client_unfullscreen(ec2);
                                      ec2->unfullscreen_forced = 1;
                                   }
                              }
                         }
                    }
               }
          }
        if (ec->unfullscreen_forced)
          {
             e_client_fullscreen(ec, e_config->fullscreen_policy);
          }
     }

   while ((ec_unfocus) && (ec_unfocus->zone))
     {
        Eina_Bool is_popup, is_child;

        ec_unfocus->want_focus = ec_unfocus->focused = 0;
        if (!e_object_is_del(E_OBJECT(ec_unfocus)))
          e_focus_event_focus_out(ec_unfocus);
        if (ec_unfocus->mouse.in && ec && (!e_client_util_is_popup(ec)) &&
            (e_config->focus_policy != E_FOCUS_CLICK) && e_config->pointer_slide)
          e_client_mouse_out(ec_unfocus, ec_unfocus->x - 1, ec_unfocus->y - 1);

        E_FREE_FUNC(ec_unfocus->raise_timer, ecore_timer_del);

        is_popup = EINA_FALSE;
        is_child = EINA_FALSE;
        if ((ec) &&
            ((ec->netwm.type == E_WINDOW_TYPE_MENU) ||
             (ec->netwm.type == E_WINDOW_TYPE_TOOLBAR) ||
             (ec->netwm.type == E_WINDOW_TYPE_DOCK) ||
             (ec->netwm.type == E_WINDOW_TYPE_SPLASH) ||
             (ec->netwm.type == E_WINDOW_TYPE_TOOLTIP) ||
             (ec->netwm.type == E_WINDOW_TYPE_DROPDOWN_MENU) ||
             (ec->netwm.type == E_WINDOW_TYPE_POPUP_MENU) ||
             (ec->netwm.type == E_WINDOW_TYPE_COMBO) ||
             (ec->netwm.type == E_WINDOW_TYPE_DND) ||
             (ec->netwm.type == E_WINDOW_TYPE_NOTIFICATION)))
          is_popup = EINA_TRUE;
        if ((ec) && _e_client_is_in_parents(ec, ec_unfocus))
          is_child = EINA_TRUE;

        /* if there unfocus client is fullscreen and visible */
        if ((!is_popup) && (!is_child) &&
            (!e_config->allow_above_fullscreen) &&
            (ec_unfocus->fullscreen) && (!ec_unfocus->iconic) && (!ec_unfocus->hidden) &&
            (ec_unfocus->zone == e_zone_current_get()) &&
            ((ec_unfocus->desk == e_desk_current_get(ec_unfocus->zone)) || (ec_unfocus->sticky)))
          {
             Eina_Bool have_vis_child = EINA_FALSE;

             /* if any of its children are visible */
             EINA_LIST_FOREACH(ec_unfocus->transients, l, ec2)
               {
                  if ((ec2->zone == ec_unfocus->zone) &&
                      ((ec2->desk == ec_unfocus->desk) ||
                       (ec2->sticky) || (ec_unfocus->sticky)))
                    {
                       have_vis_child = EINA_TRUE;
                       break;
                    }
               }
             // if no children are visible, unfullscreen
             if ((!e_object_is_del(E_OBJECT(ec_unfocus))) && (!have_vis_child))
               {
                  if (ec_unfocus->fullscreen)
                    {
                       e_client_unfullscreen(ec_unfocus);
                       ec_unfocus->unfullscreen_forced = 1;
                    }
               }
          }

        _e_client_hook_call(E_CLIENT_HOOK_FOCUS_UNSET, ec_unfocus);
        /* only send event here if we're not being deleted */
        if ((!e_object_is_del(E_OBJECT(ec_unfocus))) &&
           (e_object_ref_get(E_OBJECT(ec_unfocus)) > 0))
          {
             _e_client_event_simple(ec_unfocus, E_EVENT_CLIENT_FOCUS_OUT);
             e_client_urgent_set(ec_unfocus, ec_unfocus->icccm.urgent);
          }
        break;
     }
   if (!ec) return;

   _e_client_hook_call(E_CLIENT_HOOK_FOCUS_SET, ec);
   e_focus_event_focus_in(ec);

   if (!focus_track_frozen)
     e_client_focus_latest_set(ec);

   e_hints_active_window_set(ec);
   _e_client_event_simple(ec, E_EVENT_CLIENT_FOCUS_IN);
   if (ec->sticky && ec->desk && (!ec->desk->visible))
     e_client_desk_set(ec, e_desk_current_get(ec->zone));
}

E_API void
e_client_activate(E_Client *ec, Eina_Bool just_do_it)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   ec = e_client_stack_active_adjust(ec);
   if ((e_config->focus_setting == E_FOCUS_NEW_WINDOW) ||
       ((ec->parent) &&
        ((e_config->focus_setting == E_FOCUS_NEW_DIALOG) ||
         ((ec->parent->focused) &&
          (e_config->focus_setting == E_FOCUS_NEW_DIALOG_IF_OWNER_FOCUSED)))) ||
       (just_do_it))
     {
        if (ec->iconic)
          {
             if (e_config->clientlist_warp_to_iconified_desktop == 1)
               e_desk_show(ec->desk);

             if (!ec->lock_user_iconify)
               e_client_uniconify(ec);
          }
        if ((!ec->iconic) && ((!ec->sticky) || e_config->focus_revert_allow_sticky))
          {
             int val = e_config->focus_last_focused_per_desktop;

             /* prevent infinite focus loops during refocus */
             if (!ec->lock_focus_out)
               e_config->focus_last_focused_per_desktop = 0;
             e_desk_show(ec->desk);
             e_config->focus_last_focused_per_desktop = val;
          }
        if (!ec->lock_user_stacking)
          evas_object_raise(ec->frame);
        if (ec->shaded || ec->shading)
          e_client_unshade(ec, ec->shade_dir);
        if (!ec->lock_focus_out)
          {
             /* XXX ooffice does send this request for
                config dialogs when the main window gets focus.
                causing the pointer to jump back and forth.  */
             if ((e_config->focus_policy != E_FOCUS_CLICK) && (!ec->new_client) &&
                 (!e_config->disable_all_pointer_warps) &&
                 (!e_util_strcmp(ec->icccm.name, "VCLSalFrame")))
               ecore_evas_pointer_warp(e_comp->ee,
                                       ec->x + (ec->w / 2), ec->y + (ec->h / 2));
             evas_object_focus_set(ec->frame, 1);
          }
     }
}

E_API E_Client *
e_client_focused_get(void)
{
   return focused;
}

E_API Eina_List *
e_client_focus_stack_get(void)
{
   return focus_stack;
}

YOLO E_API void
e_client_focus_stack_set(Eina_List *l)
{
   focus_stack = l;
}

E_API Eina_List *
e_client_raise_stack_get(void)
{
   return raise_stack;
}

E_API Eina_List *
e_client_lost_windows_get(E_Zone *zone)
{
   Eina_List *list = NULL;
   const Eina_List *l;
   E_Client *ec;
   int loss_overlap = 5;

   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);
   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (ec->zone != zone) continue;
        if (e_client_util_ignored_get(ec)) continue;

        if (!E_INTERSECTS(ec->zone->x + loss_overlap,
                          ec->zone->y + loss_overlap,
                          ec->zone->w - (2 * loss_overlap),
                          ec->zone->h - (2 * loss_overlap),
                          ec->x, ec->y, ec->w, ec->h))
          {
             list = eina_list_append(list, ec);
          }
     }
   return list;
}

///////////////////////////////////////

E_API void
e_client_shade(E_Client *ec, E_Direction dir)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (((!ec->shaded) && ec->shading) || (ec->shaded && (!ec->shading)) || (ec->fullscreen) ||
       ((ec->maximized) && (!e_config->allow_manip))) return;
   if (!e_util_strcmp("borderless", ec->bordername)) return;
   if (!e_comp_object_frame_allowed(ec->frame)) return;

   e_hints_window_shaded_set(ec, 1);
   e_hints_window_shade_direction_set(ec, dir);
   ec->take_focus = 0;
   ec->shading = 1;
   ec->shade_dir = dir;

   if (e_config->border_shade_animate && (!ec->new_client))
     {
        ec->changes.shading = 1;
        EC_CHANGED(ec);

        evas_object_smart_callback_call(ec->frame, "shading", (uintptr_t*)dir);
     }
   else
     evas_object_smart_callback_call(ec->frame, "shaded", (uintptr_t*)dir);

   e_remember_update(ec);
}

E_API void
e_client_unshade(E_Client *ec, E_Direction dir)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if ((!ec->shaded) || (ec->shading))
     return;

   e_hints_window_shaded_set(ec, 0);
   e_hints_window_shade_direction_set(ec, dir);
   ec->shading = 1;
   ec->shade_dir = 0;

   if (e_config->border_shade_animate)
     {
        ec->changes.shading = 1;
        EC_CHANGED(ec);

        evas_object_smart_callback_call(ec->frame, "unshading", (uintptr_t*)dir);
     }
   else
     evas_object_smart_callback_call(ec->frame, "unshaded", (uintptr_t*)dir);

   e_remember_update(ec);
}

///////////////////////////////////////

E_API Eina_Bool
e_client_maximize_geometry_get(const E_Client *ec, E_Maximize max, int *mx, int *my, int *mw, int *mh)
{
   int x1, yy1, x2, y2;
   int x = 0, y = 0, w, h, pw, ph;
   int zx, zy, zw, zh;
   int ecx, ecy, ecw, ech;

   if (e_client_util_ignored_get(ec)) return EINA_FALSE;
   zx = zy = zw = zh = 0;

   switch (max & E_MAXIMIZE_TYPE)
     {
      case E_MAXIMIZE_FULLSCREEN:
        w = ec->zone->w;
        h = ec->zone->h;

        e_client_resize_limit(ec, &w, &h);
        /* center x-direction */
        x1 = ec->zone->x + (ec->zone->w - w) / 2;
        /* center y-direction */
        yy1 = ec->zone->y + (ec->zone->h - h) / 2;

        switch (max & E_MAXIMIZE_DIRECTION)
          {
           case E_MAXIMIZE_BOTH:
             x = x1, y = yy1;
             break;

           case E_MAXIMIZE_VERTICAL:
             x = ec->x, y = yy1, w = ec->w;
             break;

           case E_MAXIMIZE_HORIZONTAL:
             x = x1, y = ec->y, h = ec->h;
             break;

           case E_MAXIMIZE_LEFT:
             x = ec->zone->x, y = ec->zone->y, w /= 2;
             break;

           case E_MAXIMIZE_RIGHT:
             x = x1, y = ec->zone->y, w /= 2;
             break;
          }
        if (mx) *mx = x;
        if (my) *my = y;
        if (mw) *mw = w;
        if (mh) *mh = h;
        break;

      case E_MAXIMIZE_SMART:
      case E_MAXIMIZE_EXPAND:
        e_zone_desk_useful_geometry_get(ec->zone, ec->desk, &zx, &zy, &zw, &zh);
        w = zw, h = zh;

        e_comp_object_frame_xy_unadjust(ec->frame, ec->x, ec->y, &ecx, &ecy);
        e_comp_object_frame_wh_unadjust(ec->frame, ec->w, ec->h, &ecw, &ech);

        if (ecw < zw)
          w = ecw;

        if (ech < zh)
          h = ech;

        if (ecx < zx) // window left not useful coordinates
          x1 = zx;
        else if (ecx + ecw > zx + zw) // window right not useful coordinates
          x1 = zx + zw - ecw;
        else // window normal position
          x1 = ecx;

        if (ecy < zy) // window top not useful coordinates
          yy1 = zy;
        else if (ecy + ech > zy + zh) // window bottom not useful coordinates
          yy1 = zy + zh - ech;
        else // window normal position
          yy1 = ecy;

        switch (max & E_MAXIMIZE_DIRECTION)
          {
           case E_MAXIMIZE_BOTH:
             x = zx, y = zy, w = zw, h = zh;
             break;

           case E_MAXIMIZE_VERTICAL:
             x = ec->x, y = zy, w = ec->w, h = zh;
             break;

           case E_MAXIMIZE_HORIZONTAL:
             x = zx, y = ec->y, w = zw, h = ec->h;
             break;

           case E_MAXIMIZE_LEFT:
             x = zx, y = zy, w = zw / 2, h = zh;
             break;

           case E_MAXIMIZE_RIGHT:
             x = zx + zw / 2, y = zy, w = zw / 2, h = zh;
             break;
          }
        break;

      case E_MAXIMIZE_FILL:
        x1 = ec->zone->x;
        yy1 = ec->zone->y;
        x2 = ec->zone->x + ec->zone->w;
        y2 = ec->zone->y + ec->zone->h;

        e_zone_desk_useful_geometry_get(ec->zone, ec->desk, &zx, &zy, &zw, &zh);
        x1 = zx, yy1 = zy, x2 = x1 + zw, y2 = yy1 + zh;

        w = x2 - x1;
        h = y2 - yy1;
        pw = w;
        ph = h;
        e_client_resize_limit(ec, &w, &h);
        /* center x-direction */
        x1 = x1 + (pw - w) / 2;
        /* center y-direction */
        yy1 = yy1 + (ph - h) / 2;

        switch (max & E_MAXIMIZE_DIRECTION)
          {
           case E_MAXIMIZE_BOTH:
             x = x1, y = yy1;
             break;

           case E_MAXIMIZE_VERTICAL:
             x = ec->x, y = yy1, w = ec->w;
             break;

           case E_MAXIMIZE_HORIZONTAL:
             x = x1, y = ec->y, h = ec->h;
             break;

           case E_MAXIMIZE_LEFT:
             x = ec->zone->x, y = ec->zone->y, w /= 2;
             break;

           case E_MAXIMIZE_RIGHT:
             x = x1, y = ec->zone->y, w /= 2;
             break;
          }
        break;
      default:
        return EINA_FALSE;
     }
   if (mx) *mx = x;
   if (my) *my = y;
   if (mw) *mw = w;
   if (mh) *mh = h;
   return EINA_TRUE;
}

E_API void
e_client_maximize(E_Client *ec, E_Maximize max)
{
   Eina_Bool override;
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (!ec->zone) return;
   if (ec->maximized == max) return;
   if (!(max & E_MAXIMIZE_DIRECTION)) max |= E_MAXIMIZE_BOTH;

   if ((ec->shaded) || (ec->shading)) return;

   /* Only allow changes in vertical/ horizontal maximization */
   if (((ec->maximized & E_MAXIMIZE_DIRECTION) == (max & E_MAXIMIZE_DIRECTION)) ||
       ((ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_BOTH)) return;
   if (ec->new_client)
     {
        ec->changes.need_maximize = 1;
        ec->maximized &= ~E_MAXIMIZE_TYPE;
        ec->maximized |= max;
        EC_CHANGED(ec);
        if (ec->re_manage) ec->changes.pos = 0;
        return;
     }
   if ((max & E_MAXIMIZE_TYPE) == E_MAXIMIZE_FULLSCREEN)
     evas_object_smart_callback_call(ec->frame, "fullscreen", NULL);
   else
     evas_object_smart_callback_call(ec->frame, "maximize", NULL);
   evas_object_smart_callback_call(ec->frame, "maximize_pre", &max);
   if (!max) return;
   if (ec->moving)
     {
        if (ec == action_client)
          e_comp_canvas_feed_mouse_up(0); //triggers event grabber cb
     }
   override = ec->maximize_override;
   if (ec->fullscreen)
     e_client_unfullscreen(ec);
   ec->pre_res_change.valid = 0;
   if (!ec->saved.set)
     {
        if (!(ec->maximized & E_MAXIMIZE_HORIZONTAL))
          {
             /* Horizontal hasn't been set */
             if (ec->changes.pos)
               e_comp_object_frame_xy_adjust(ec->frame, ec->x, 0, &ec->saved.x, NULL);
             else
               ec->saved.x = ec->client.x;
             ec->saved.x -= ec->zone->x;
             if (ec->visible)
               ec->saved.w = ec->client.w;
          }
        if (!(ec->maximized & E_MAXIMIZE_VERTICAL))
          {
             /* Vertical hasn't been set */
             if (ec->changes.pos)
               e_comp_object_frame_xy_adjust(ec->frame, 0, ec->y, NULL, &ec->saved.y);
             else
               ec->saved.y = ec->client.y;
             ec->saved.y -= ec->zone->y;
             if (ec->visible)
               ec->saved.h = ec->client.h;
          }
        ec->saved.zone = ec->zone->num;
        ec->saved.frame = e_comp_object_frame_exists(ec->frame) || (!e_comp_object_frame_allowed(ec->frame));
     }

   ec->maximize_override = 1;

   {
      int x, y, w, h;
      e_client_maximize_geometry_get(ec, max, &x, &y, &w, &h);
      if (!_e_client_maximize_run(ec, x, y, w, h))
        ec->maximize_override = override;
   }

   /* Remove previous type */
   ec->maximized &= ~E_MAXIMIZE_TYPE;
   /* Add new maximization. It must be added, so that VERTICAL + HORIZONTAL == BOTH */
   ec->maximized |= max;
   ec->changes.need_unmaximize = 0;

   if ((ec->maximized & E_MAXIMIZE_DIRECTION) > E_MAXIMIZE_BOTH)
     /* left/right maximize */
     e_hints_window_maximized_set(ec, 0,
                                  ((ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_LEFT) ||
                                  ((ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_RIGHT));
   else
     e_hints_window_maximized_set(ec, ec->maximized & E_MAXIMIZE_HORIZONTAL,
                                  ec->maximized & E_MAXIMIZE_VERTICAL);
   e_remember_update(ec);
   evas_object_smart_callback_call(ec->frame, "maximize_done", NULL);
}

E_API Eina_Bool
e_client_unmaximize_geometry_get(const E_Client *ec, E_Maximize max, int *mx, int *my, int *mw, int *mh)
{
   int w, h, x, y;

   if (e_client_util_ignored_get(ec)) return EINA_FALSE;
   if (!(ec->maximized & E_MAXIMIZE_TYPE)) return EINA_FALSE;
   if ((ec->maximized & E_MAXIMIZE_TYPE) == E_MAXIMIZE_FULLSCREEN)
     {
        if (mx) *mx = ec->saved.x + ec->zone->x;
        if (my) *my = ec->saved.y + ec->zone->y;
        if (mw) *mw = ec->saved.w;
        if (mh) *mh = ec->saved.h;
        return EINA_TRUE;
     }

   w = ec->client.w;
   h = ec->client.h;
   x = ec->client.x;
   y = ec->client.y;
   max &= (ec->maximized & E_MAXIMIZE_DIRECTION);

   if (max & E_MAXIMIZE_VERTICAL)
     {
        /* Remove vertical */
        h = ec->saved.h;
        y = ec->saved.y + ec->zone->y;
     }
   if (max & E_MAXIMIZE_HORIZONTAL)
     {
        /* Remove horizontal */
        w = ec->saved.w;
        x = ec->saved.x + ec->zone->x;
     }
   if (mx) *mx = x;
   if (my) *my = y;
   if (mw) *mw = w;
   if (mh) *mh = h;
   return EINA_TRUE;
}

E_API void
e_client_unmaximize(E_Client *ec, E_Maximize max)
{
   E_Maximize unmax = max;
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (!(max & E_MAXIMIZE_DIRECTION))
     {
        CRI("BUG: Unmaximize call without direction!");
        return;
     }
   if (ec->new_client)
     {
        ec->changes.need_unmaximize = 1;
        EC_CHANGED(ec);
        return;
     }

   if ((ec->shaded) || (ec->shading)) return;

   /* Remove directions not used */
   unmax &= (ec->maximized & E_MAXIMIZE_DIRECTION);
   evas_object_smart_callback_call(ec->frame, "unmaximize_pre", &unmax);
   /* Can only remove existing maximization directions */
   if ((!unmax) && (!ec->need_fullscreen)) return;
   if (!unmax) unmax = max & (ec->maximized & E_MAXIMIZE_DIRECTION);
   if (ec->maximized & E_MAXIMIZE_TYPE)
     {
        ec->pre_res_change.valid = 0;
        ec->changes.need_maximize = 0;

        if ((ec->maximized & E_MAXIMIZE_TYPE) == E_MAXIMIZE_FULLSCREEN)
          {
             E_Maximize tmp_max = ec->maximized;

             //un-set maximized state for updating frame.
             ec->maximized = E_MAXIMIZE_NONE;
             _e_client_frame_update(ec);
             // re-set maximized state for unmaximize smart callback.
             ec->maximized = tmp_max;
             evas_object_smart_callback_call(ec->frame, "unfullscreen", NULL);
             // un-set maximized state.
             ec->maximized = E_MAXIMIZE_NONE;
             e_client_util_move_resize_without_frame(ec,
                                                     ec->saved.x + ec->zone->x,
                                                     ec->saved.y + ec->zone->y,
                                                     ec->saved.w, ec->saved.h);
             ec->saved.x = ec->saved.y = ec->saved.w = ec->saved.h = 0;
             ec->saved.set = 0;
             e_hints_window_size_unset(ec);
          }
        else
          {
             int w, h, x, y;
             Eina_Bool horiz = EINA_FALSE, vert = EINA_FALSE;
             Eina_Bool fullscreen = !!eina_list_data_find(ec->desk->fullscreen_clients, ec);

             e_client_unmaximize_geometry_get(ec, unmax, &x, &y, &w, &h);
             if (unmax & E_MAXIMIZE_VERTICAL)
               {
                  /* Remove vertical */
                  vert = EINA_TRUE;
                  if ((unmax & E_MAXIMIZE_VERTICAL) == E_MAXIMIZE_VERTICAL)
                    {
                       if ((ec->maximized & E_MAXIMIZE_LEFT) == E_MAXIMIZE_LEFT)
                         ec->maximized &= ~E_MAXIMIZE_LEFT;
                       if ((ec->maximized & E_MAXIMIZE_RIGHT) == E_MAXIMIZE_RIGHT)
                         ec->maximized &= ~E_MAXIMIZE_RIGHT;
                       ec->maximized &= ~E_MAXIMIZE_VERTICAL;
                    }
                  if ((unmax & E_MAXIMIZE_LEFT) == E_MAXIMIZE_LEFT)
                    ec->maximized &= ~E_MAXIMIZE_LEFT;
                  if ((unmax & E_MAXIMIZE_RIGHT) == E_MAXIMIZE_RIGHT)
                    ec->maximized &= ~E_MAXIMIZE_RIGHT;
               }
             if (unmax & E_MAXIMIZE_HORIZONTAL)
               {
                  /* Remove horizontal */
                  horiz = EINA_TRUE;
                  ec->maximized &= ~E_MAXIMIZE_HORIZONTAL;
               }

             if (!(ec->maximized & E_MAXIMIZE_DIRECTION))
               {
                  ec->maximized = E_MAXIMIZE_NONE;
                  _e_client_frame_update(ec);
                  e_hints_window_size_unset(ec);
               }
             if (e_config->window_maximize_animate && (!ec->maximize_anims_disabled))
               ec->maximize_override = 1;
             if (!fullscreen)
               evas_object_smart_callback_call(ec->frame, "unmaximize", NULL);
             if (ec->saved.frame &&
               (e_comp_object_frame_exists(ec->frame) || (!e_comp_object_frame_allowed(ec->frame))))
               {
                  e_comp_object_frame_xy_adjust(ec->frame, x, y, &x, &y);
                  e_comp_object_frame_wh_adjust(ec->frame, w, h, &w, &h);
               }
             e_client_resize_limit(ec, &w, &h);
             if (fullscreen)
               evas_object_smart_callback_call(ec->frame, "unmaximize", NULL);
             if (!_e_client_maximize_run(ec, x, y, w, h))
               ec->maximize_override = 0;
             if (vert)
               ec->saved.h = ec->saved.y = 0;
             if (horiz)
               ec->saved.w = ec->saved.x = 0;
             if (vert && horiz)
               ec->saved.set = ec->saved.frame = 0;
          }
        e_hints_window_maximized_set(ec, ec->maximized & E_MAXIMIZE_HORIZONTAL,
                                     ec->maximized & E_MAXIMIZE_VERTICAL);
     }
   e_remember_update(ec);
   evas_object_smart_callback_call(ec->frame, "unmaximize_done", NULL);
   ec->changes.need_unmaximize = 0;
}

E_API void
e_client_fullscreen(E_Client *ec, E_Fullscreen policy)
{
   int x = 0, y = 0, w = 0, h = 0;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;

   if ((ec->shaded) || (ec->shading) || ec->fullscreen) return;
   if ((!e_config->allow_above_fullscreen) && (!ec->desk->visible)) return;
   ec->need_fullscreen = 1;
   if (ec->new_client) return;
   if (e_comp->nocomp_ec && (ec->desk == e_comp->nocomp_ec->desk))
     {
        e_object_unref(E_OBJECT(e_comp->nocomp_ec));
        e_object_ref(E_OBJECT(ec));
        e_comp->nocomp_ec = ec;
     }
   if (!eina_list_data_find(ec->desk->fullscreen_clients, ec))
     ec->desk->fullscreen_clients = eina_list_append(ec->desk->fullscreen_clients, ec);
   ec->pre_res_change.valid = 0;

   if (ec->maximized)
     {
        x = ec->saved.x;
        y = ec->saved.y;
        w = ec->saved.w;
        h = ec->saved.h;
     }
   else
     {
        if (ec->changes.pos)
          e_comp_object_frame_xy_adjust(ec->frame, ec->x, ec->y, &ec->saved.x, &ec->saved.y);
        else
          {
             ec->saved.x = ec->client.x;
             ec->saved.y = ec->client.y;
          }
        ec->saved.x -= ec->zone->x;
        ec->saved.y -= ec->zone->y;

        if (ec->visible)
          {
             ec->saved.w = ec->client.w;
             ec->saved.h = ec->client.h;
          }
     }
   E_FREE_FUNC(ec->agent, evas_object_del);
   ec->saved.maximized = ec->maximized;
   ec->saved.zone = ec->zone->num;
   ec->saved.frame = e_comp_object_frame_exists(ec->frame) || (!e_comp_object_frame_allowed(ec->frame));

   if (ec->maximized)
     {
        Eina_Bool maximize_anims_disabled = ec->maximize_anims_disabled;

        ec->maximize_anims_disabled = 1;
        e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
        ec->maximize_anims_disabled = maximize_anims_disabled;
        ec->saved.x = x;
        ec->saved.y = y;
        ec->saved.w = w;
        ec->saved.h = h;
        ec->saved.frame = e_comp_object_frame_exists(ec->frame) || (!e_comp_object_frame_allowed(ec->frame));
     }

   ec->saved.layer = ec->layer;
   ec->saved.set = 1;
   if (!e_config->allow_above_fullscreen)
     evas_object_layer_set(ec->frame, E_LAYER_CLIENT_FULLSCREEN);

   ec->fullscreen = 1;
   ec->fullscreen_time = ecore_time_get();
   ec->unfullscreen_forced = 0;
#ifndef HAVE_WAYLAND_ONLY
   if ((eina_list_count(e_comp->zones) > 1) ||
       (policy == E_FULLSCREEN_RESIZE) || (!ecore_x_randr_query()))
#else
   if ((eina_list_count(e_comp->zones) > 1) ||
       (policy == E_FULLSCREEN_RESIZE))
#endif
     {
        evas_object_geometry_set(ec->frame, ec->zone->x, ec->zone->y, ec->zone->w, ec->zone->h);
     }
   else if (policy == E_FULLSCREEN_ZOOM)
     {
        /* compositor backends! */
        evas_object_smart_callback_call(ec->frame, "fullscreen_zoom", NULL);
     }

   e_hints_window_fullscreen_set(ec, 1);
   e_hints_window_size_unset(ec);
   if (!e_client_util_ignored_get(ec))
     _e_client_frame_update(ec);
   ec->fullscreen_policy = policy;
   evas_object_smart_callback_call(ec->frame, "fullscreen", NULL);

   _e_client_event_simple(ec, E_EVENT_CLIENT_FULLSCREEN);

   e_remember_update(ec);
   ec->need_fullscreen = 0;
}

E_API void
e_client_unfullscreen(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if ((ec->shaded) || (ec->shading)) return;
   if (!ec->fullscreen) return;
   ec->pre_res_change.valid = 0;
   ec->fullscreen = 0;
   ec->unfullscreen_forced = 0;
   ec->need_fullscreen = 0;
   ec->desk->fullscreen_clients = eina_list_remove(ec->desk->fullscreen_clients, ec);

   if (ec->fullscreen_policy == E_FULLSCREEN_ZOOM)
     evas_object_smart_callback_call(ec->frame, "unfullscreen_zoom", NULL);

   if (!e_client_util_ignored_get(ec))
     _e_client_frame_update(ec);
   ec->fullscreen_policy = 0;
   evas_object_smart_callback_call(ec->frame, "unfullscreen", NULL);

   if (ec->saved.maximized)
     {
        Eina_Bool maximize_anims_disabled = ec->maximize_anims_disabled;
        ec->changes.pos = ec->changes.size = 0;
        ec->maximize_anims_disabled = 1;
        e_client_maximize(ec,
          (e_config->maximize_policy & E_MAXIMIZE_TYPE) | ec->saved.maximized);
        ec->maximize_anims_disabled = maximize_anims_disabled;
     }
   else
     {
        if (ec->saved.frame &&
          (e_comp_object_frame_exists(ec->frame) || (!e_comp_object_frame_allowed(ec->frame))))
          e_client_util_move_resize_without_frame(ec, ec->zone->x + ec->saved.x,
                                                  ec->zone->y + ec->saved.y,
                                                  ec->saved.w, ec->saved.h);
        else
           evas_object_geometry_set(ec->frame, ec->zone->x + ec->saved.x,
                                                   ec->zone->y + ec->saved.y,
                                                   ec->saved.w, ec->saved.h);
        ec->saved.w = ec->saved.x = ec->saved.h = ec->saved.y = 0;
        ec->saved.set = ec->saved.frame = 0;
     }

   evas_object_layer_set(ec->frame, ec->saved.layer);

   e_hints_window_fullscreen_set(ec, 0);
   _e_client_event_simple(ec, E_EVENT_CLIENT_UNFULLSCREEN);

   e_remember_update(ec);
   if (!ec->desk->fullscreen_clients)
     e_comp_render_queue();
}

///////////////////////////////////////


E_API void
e_client_iconify(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->shading || ec->iconic) return;
   if (((ec->stack.prev || ec->stack.next)) && (!ec->stack.ignore))
     {
        Eina_List *l, *list = e_client_stack_list_prepare(ec);
        E_Client *child;

        EINA_LIST_FOREACH(list, l, child)
          {
             e_client_iconify(child);
          }
        e_client_stack_list_finish(list);
        E_Desk *desk;

        desk = e_desk_current_get(ec->zone);
        e_desk_last_focused_focus(desk);
        return;
     }
   ec->iconic = 1;
   ec->want_focus = ec->take_focus = 0;
   ec->changes.visible = 0;
   if (ec->fullscreen)
     ec->desk->fullscreen_clients = eina_list_remove(ec->desk->fullscreen_clients, ec);
   e_client_comp_hidden_set(ec, 1);
   if (!ec->stack.ignore)
     {
        if (!ec->new_client)
          {
             e_client_revert_focus(ec);
             evas_object_hide(ec->frame);
          }
        e_client_urgent_set(ec, ec->icccm.urgent);
     }
   else
     {
        if (!ec->new_client)
          evas_object_hide(ec->frame);
        e_client_urgent_set(ec, ec->icccm.urgent);
        if (ec->focused)
          evas_object_focus_set(ec->frame, 0);
     }

   _e_client_event_simple(ec, E_EVENT_CLIENT_ICONIFY);

   if (!ec->stack.prev && !ec->stack.next)
     {
        if (e_config->transient.iconify)
          {
             E_Client *child;
             Eina_List *list = eina_list_clone(ec->transients);

             EINA_LIST_FREE(list, child)
               e_client_iconify(child);
          }
     }
   e_remember_update(ec);
}

E_API void
e_client_uniconify(E_Client *ec)
{
   E_Desk *desk;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->shading || (!ec->iconic)) return;

   if (((ec->stack.prev || ec->stack.next)) && (!ec->stack.ignore))
     {
        Eina_List *l, *list = e_client_stack_list_prepare(ec);
        E_Client *child, *ec_focus = NULL;

        EINA_LIST_FOREACH(list, l, child)
          {
             e_client_uniconify(child);
             if (!l->next) ec_focus = child;
          }
        e_client_stack_list_finish(list);
        evas_object_raise(ec_focus->frame);
        evas_object_focus_set(ec_focus->frame, 1);
        return;
     }
   desk = e_desk_current_get(ec->desk->zone);
   e_client_desk_set(ec, desk);
   if (!ec->stack.ignore)
     evas_object_raise(ec->frame);
   evas_object_show(ec->frame);
   e_client_comp_hidden_set(ec, 0);
   ec->deskshow = ec->iconic = 0;
   if (!ec->stack.ignore)
     evas_object_focus_set(ec->frame, 1);

   _e_client_event_simple(ec, E_EVENT_CLIENT_UNICONIFY);

   if (!ec->stack.prev && !ec->stack.next)
     {
        if (e_config->transient.iconify)
          {
             E_Client *child;
             Eina_List *list = eina_list_clone(ec->transients);

             EINA_LIST_FREE(list, child)
               e_client_uniconify(child);
          }
     }
   e_remember_update(ec);
}

///////////////////////////////////////

E_API void
e_client_urgent_set(E_Client *ec, Eina_Bool urgent)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (urgent && e_screensaver_on_get() && e_config->screensaver_wake_on_urgent)
     {
        int x, y;
        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        ecore_evas_pointer_warp(e_comp->ee, x, y);
        e_comp_canvas_notidle();
     }
   if (!ec->zone) return;

   urgent = !!urgent;
   if (urgent == ec->urgent) return;
   _e_client_event_property(ec, E_CLIENT_PROPERTY_URGENCY);
   if (urgent && (!ec->focused) && (!ec->want_focus))
     {
        e_comp_object_signal_emit(ec->frame, "e,state,urgent", "e");
        ec->urgent = urgent;
     }
   else
     {
        e_comp_object_signal_emit(ec->frame, "e,state,not_urgent", "e");
        ec->urgent = 0;
     }
}

///////////////////////////////////////

E_API void
e_client_stick(E_Client *ec)
{
   E_Desk *desk;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->sticky) return;
   desk = ec->desk;
   ec->desk = NULL;
   if (desk && ec->fullscreen)
     desk->fullscreen_clients = eina_list_remove(desk->fullscreen_clients, ec);
   ec->sticky = 1;
   ec->hidden = 0;
   e_hints_window_sticky_set(ec, 1);
   e_client_desk_set(ec, desk);
   evas_object_smart_callback_call(ec->frame, "stick", NULL);

   if (ec->stack.prev || ec->stack.next)
     {
        if (ec->stack.ignore == 0)
          {
             Eina_List *l, *list = e_client_stack_list_prepare(ec);
             E_Client *child;

             EINA_LIST_FOREACH(list, l, child)
               {
                  if (child == ec) continue;
                  child->sticky = 1;
                  e_hints_window_sticky_set(child, 1);
                  evas_object_show(ec->frame);
               }
             e_client_stack_list_finish(list);
          }
     }
   else
     {
        if (e_config->transient.desktop)
          {
             E_Client *child;
             Eina_List *list = eina_list_clone(ec->transients);

             EINA_LIST_FREE(list, child)
               {
                  child->sticky = 1;
                  e_hints_window_sticky_set(child, 1);
                  evas_object_show(ec->frame);
               }
          }
     }

   _e_client_event_property(ec, E_CLIENT_PROPERTY_STICKY);
   e_remember_update(ec);
}

E_API void
e_client_unstick(E_Client *ec)
{
   E_Desk *desk;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   /* Set the desk before we unstick the client */
   if (!ec->sticky) return;
   desk = e_desk_current_get(ec->zone);
   if (ec->desk && ec->fullscreen)
     ec->desk->fullscreen_clients = eina_list_remove(ec->desk->fullscreen_clients, ec);
   ec->desk = NULL;
   ec->hidden = ec->sticky = 0;
   e_hints_window_sticky_set(ec, 0);
   e_client_desk_set(ec, desk);
   evas_object_smart_callback_call(ec->frame, "unstick", NULL);

   if (ec->stack.prev || ec->stack.next)
     {
        if (ec->stack.ignore == 0)
          {
             Eina_List *l, *list = e_client_stack_list_prepare(ec);
             E_Client *child;

             EINA_LIST_FOREACH(list, l, child)
               {
                  if (child == ec) continue;
                  child->sticky = 1;
                  e_hints_window_sticky_set(child, 0);
               }
             e_client_stack_list_finish(list);
          }
     }
   else
     {
        if (e_config->transient.desktop)
          {
             E_Client *child;
             Eina_List *list = eina_list_clone(ec->transients);

             EINA_LIST_FREE(list, child)
               {
                  child->sticky = 0;
                  e_hints_window_sticky_set(child, 0);
               }
          }
     }

   _e_client_event_property(ec, E_CLIENT_PROPERTY_STICKY);

   e_client_desk_set(ec, e_desk_current_get(ec->zone));
   e_remember_update(ec);
}

E_API void
e_client_pinned_set(E_Client *ec, Eina_Bool set)
{
   E_Layer layer;

   EINA_SAFETY_ON_NULL_RETURN(ec);
   ec->borderless = !!set;
   ec->user_skip_winlist = !!set;
   if (set)
     layer = E_LAYER_CLIENT_BELOW;
   else
     layer = E_LAYER_CLIENT_NORMAL;

   evas_object_layer_set(ec->frame, layer);

   ec->border.changed = 1;
   EC_CHANGED(ec);
}

E_API void
e_client_prop_misc_changed(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   _e_client_event_property(ec, E_CLIENT_PROPERTY_MISC);
}

///////////////////////////////////////

E_API Eina_Bool
e_client_border_set(E_Client *ec, const char *name)
{
   Eina_Stringshare *pborder;

   E_OBJECT_CHECK_RETURN(ec, EINA_FALSE);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, EINA_FALSE);
   if ((!e_comp_object_frame_allowed(ec->frame)) && (!e_comp_object_frame_exists(ec->frame)))
     return EINA_FALSE;
   if (ec->border.changed)
     ERR("CALLING WHEN border.changed SET!");

   if (eina_streq(ec->border.name, name)) return EINA_TRUE;
   if (ec->mwm.borderless && (!eina_streq(name, "borderless")))
     {
        e_util_dialog_show(_("Client Error!"), _("Something has attempted to set a border when it shouldn't! Report this!"));
        ERR("border change attempted for MWM borderless client!");
     }
   if ((!ec->border.name) && eina_streq(name, "borderless")) return EINA_TRUE;
   pborder = ec->border.name;
   ec->border.name = eina_stringshare_add(name);
   if (e_comp_object_frame_theme_set(ec->frame, name))
     {
        eina_stringshare_del(pborder);
        return EINA_TRUE;
     }
   eina_stringshare_del(ec->border.name);
   ec->border.name = pborder;
   return EINA_FALSE;
}

///////////////////////////////////////

E_API void
e_client_comp_hidden_set(E_Client *ec, Eina_Bool hidden)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;

   hidden = !!hidden;
   if (ec->comp_hidden == hidden) return;
   ec->comp_hidden = hidden;
   evas_object_smart_callback_call(ec->frame, "comp_hidden", NULL);
}

///////////////////////////////////////

E_API void
e_client_act_move_keyboard(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (!ec->zone) return;

   if (!_e_client_move_begin(ec))
     return;
   _e_client_action_init(ec);
   if (!_e_client_hook_call(E_CLIENT_HOOK_MOVE_UPDATE, ec)) return;

   if (!action_handler_key)
     action_handler_key = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, _e_client_move_key_down, NULL);

   if (!action_handler_mouse)
     action_handler_mouse = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _e_client_move_mouse_down, NULL);
   _e_client_action_move_timeout_add();
}

E_API void
e_client_act_resize_keyboard(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (!ec->zone) return;

   ec->resize_mode = E_POINTER_RESIZE_BR;
   ec->keyboard_resizing = 1;
   if (!e_client_resize_begin(ec))
     {
        ec->keyboard_resizing = 0;
        return;
     }
   _e_client_action_init(ec);

   if (!action_handler_key)
     action_handler_key = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, _e_client_resize_key_down, NULL);

   if (!action_handler_mouse)
     action_handler_mouse = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, _e_client_resize_mouse_down, NULL);
   _e_client_action_resize_timeout_add();
}

E_API void
e_client_act_move_begin(E_Client *ec, E_Binding_Event_Mouse_Button *ev)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (e_client_util_resizing_get(ec) || (ec->moving)) return;
   if (ev)
     {
        char source[256];

        snprintf(source, sizeof(source) - 1, "mouse,down,%i", ev->button);
        _e_client_moveinfo_gather(ec, source);
     }
   if (!_e_client_move_begin(ec))
     return;

   _e_client_action_init(ec);
   e_pointer_mode_push(ec, E_POINTER_MOVE);
}

E_API void
e_client_act_move_end(E_Client *ec, E_Binding_Event_Mouse_Button *ev EINA_UNUSED)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (!ec->moving) return;
   e_zone_edge_enable();
   _e_client_move_end(ec);
   e_zone_flip_coords_handle(ec->zone, -1, -1);
   _e_client_action_finish();
}

E_API void
e_client_act_resize_begin(E_Client *ec, E_Binding_Event_Mouse_Button *ev)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->lock_user_size || ec->shaded || ec->shading) return;
   if (e_client_util_resizing_get(ec) || (ec->moving)) return;
   if (ev)
     {
        char source[256];

        snprintf(source, sizeof(source) - 1, "mouse,down,%i", ev->button);
        _e_client_moveinfo_gather(ec, source);
     }
   if ((ec->mouse.current.mx > (ec->x + ec->w / 5)) &&
       (ec->mouse.current.mx < (ec->x + ec->w * 4 / 5)))
     {
        if (ec->mouse.current.my < (ec->y + ec->h / 2))
          {
             ec->resize_mode = E_POINTER_RESIZE_T;
          }
        else
          {
             ec->resize_mode = E_POINTER_RESIZE_B;
          }
     }
   else if (ec->mouse.current.mx < (ec->x + ec->w / 2))
     {
        if ((ec->mouse.current.my > (ec->y + ec->h / 5)) &&
            (ec->mouse.current.my < (ec->y + ec->h * 4 / 5)))
          {
             ec->resize_mode = E_POINTER_RESIZE_L;
          }
        else if (ec->mouse.current.my < (ec->y + ec->h / 2))
          {
             ec->resize_mode = E_POINTER_RESIZE_TL;
          }
        else
          {
             ec->resize_mode = E_POINTER_RESIZE_BL;
          }
     }
   else
     {
        if ((ec->mouse.current.my > (ec->y + ec->h / 5)) &&
            (ec->mouse.current.my < (ec->y + ec->h * 4 / 5)))
          {
             ec->resize_mode = E_POINTER_RESIZE_R;
          }
        else if (ec->mouse.current.my < (ec->y + ec->h / 2))
          {
             ec->resize_mode = E_POINTER_RESIZE_TR;
          }
        else
          {
             ec->resize_mode = E_POINTER_RESIZE_BR;
          }
     }
   if (!e_client_resize_begin(ec))
     return;
   _e_client_action_init(ec);
   e_pointer_mode_push(ec, ec->resize_mode);
}

E_API void
e_client_act_resize_end(E_Client *ec, E_Binding_Event_Mouse_Button *ev EINA_UNUSED)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (e_client_util_resizing_get(ec))
     {
        _e_client_resize_end(ec);
        ec->changes.reset_gravity = 1;
        if (!e_object_is_del(E_OBJECT(ec)))
          EC_CHANGED(ec);
     }
   _e_client_action_finish();
}

E_API void
e_client_act_menu_begin(E_Client *ec, E_Binding_Event_Mouse_Button *ev, int key)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->border_menu) return;
   if (ev)
     e_int_client_menu_show(ec, ev->canvas.x, ev->canvas.y, key, ev->timestamp);
   else
     {
        int x, y;

        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        e_int_client_menu_show(ec, x, y, key, 0);
     }
}

E_API void
e_client_act_close_begin(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->lock_close) return;
   if (ec->icccm.delete_request)
     {
        ec->delete_requested = 1;
        evas_object_smart_callback_call(ec->frame, "delete_request", NULL);
     }
   else if (e_config->kill_if_close_not_possible)
     {
        e_client_act_kill_begin(ec);
     }
}

E_API void
e_client_act_kill_begin(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (ec->internal) return;
   if (ec->lock_close) return;
   if (ec->netwm.pid == getpid()) return;
   if ((ec->netwm.pid > 1) && (e_config->kill_process))
     {
        kill(ec->netwm.pid, SIGINT);
        ec->kill_timer = ecore_timer_loop_add(e_config->kill_timer_wait,
                                         _e_client_cb_kill_timer, ec);
     }
   else
     evas_object_smart_callback_call(ec->frame, "kill_request", NULL);
}

////////////////////////////////////////////////


E_API Evas_Object *
e_client_icon_add(E_Client *ec, Evas *evas)
{
   Evas_Object *o;

   E_OBJECT_CHECK_RETURN(ec, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, NULL);
   o = NULL;
   if (ec->internal)
     {
        if (!ec->internal_icon)
          {
             o = e_icon_add(evas);
             e_util_icon_theme_set(o, "enlightenment");
          }
        else
          {
             if (!ec->internal_icon_key)
               {
                  char *ext;

                  ext = strrchr(ec->internal_icon, '.');
                  if ((ext) && ((!strcmp(ext, ".edj"))))
                    {
                       o = edje_object_add(evas);
                       if (!edje_object_file_set(o, ec->internal_icon, "icon"))
                         e_util_icon_theme_set(o, "enlightenment");
                    }
                  else if (ext)
                    {
                       o = e_icon_add(evas);
                       e_icon_file_set(o, ec->internal_icon);
                    }
                  else
                    {
                       o = e_icon_add(evas);
                       if (!e_util_icon_theme_set(o, ec->internal_icon))
                         e_util_icon_theme_set(o, "enlightenment");
                    }
               }
             else
               {
                  o = edje_object_add(evas);
                  edje_object_file_set(o, ec->internal_icon,
                                       ec->internal_icon_key);
               }
          }
        return o;
     }
#ifndef HAVE_WAYLAND_ONLY
   if ((e_config->use_app_icon) && (ec->icon_preference != E_ICON_PREF_USER))
     {
        if (ec->netwm.icons)
          {
             o = e_icon_add(evas);
             e_icon_data_set(o, ec->netwm.icons[0].data,
                             ec->netwm.icons[0].width,
                             ec->netwm.icons[0].height);
             e_icon_alpha_set(o, 1);
             return o;
          }
     }
#endif
   if (!o)
     {
        if ((ec->desktop) && (ec->icon_preference != E_ICON_PREF_NETWM))
          {
             o = e_util_desktop_icon_add(ec->desktop, 128, evas);
             if (o)
               return o;
          }
#ifndef HAVE_WAYLAND_ONLY
        else if (ec->netwm.icons)
          {
             o = e_icon_add(evas);
             e_icon_data_set(o, ec->netwm.icons[0].data,
                             ec->netwm.icons[0].width,
                             ec->netwm.icons[0].height);
             e_icon_alpha_set(o, 1);
             return o;
          }
#endif
     }
   o = e_icon_add(evas);
   e_util_icon_theme_set(o, "unknown");
   return o;
}

////////////////////////////////////////////

E_API void
e_client_ping(E_Client *ec)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!e_config->ping_clients) return;
   ec->ping_ok = 0;
   evas_object_smart_callback_call(ec->frame, "ping", NULL);
   ec->ping = ecore_loop_time_get();
   if (ec->ping_poller) ecore_poller_del(ec->ping_poller);
   ec->ping_poller = ecore_poller_add(ECORE_POLLER_CORE,
                                      e_config->ping_clients_interval,
                                      _e_client_cb_ping_poller, ec);
}

////////////////////////////////////////////

E_API void
e_client_move_cancel(void)
{
   if (!ecmove) return;
   if (ecmove->cur_mouse_action)
     {
        E_Client *ec;

        ec = ecmove;
        e_object_ref(E_OBJECT(ec));
        _e_client_mouse_action_end(ec);
        e_object_unref(E_OBJECT(ec));
     }
   else
     _e_client_move_end(ecmove);
}

E_API void
e_client_resize_cancel(void)
{
   if (!ecresize) return;
   if (ecresize->cur_mouse_action)
     {
        E_Client *ec;

        ec = ecresize;
        e_object_ref(E_OBJECT(ec));
        _e_client_mouse_action_end(ec);
        e_object_unref(E_OBJECT(ec));
     }
   else
     _e_client_resize_end(ecresize);
}

E_API Eina_Bool
e_client_resize_begin(E_Client *ec)
{
   if ((ec->shaded) || (ec->shading) ||
       (ec->fullscreen) || (ec->lock_user_size))
     goto error;
   if (!_e_client_action_input_win_new()) goto error;
   if (!ec->lock_user_stacking)
     {
        if (e_config->border_raise_on_mouse_action)
          evas_object_raise(ec->frame);
     }
   ecresize = ec;
   _e_client_hook_call(E_CLIENT_HOOK_RESIZE_BEGIN, ec);
   if (!e_client_util_resizing_get(ec))
     {
        if (ecresize == ec) ecresize = NULL;
        _e_client_action_input_win_del();
        return EINA_FALSE;
     }
   E_FREE_FUNC(ec->raise_timer, ecore_timer_del);
   _e_client_action_event_grabber_init(ec);
   return EINA_TRUE;
error:
   ec->resize_mode = E_POINTER_RESIZE_NONE;
   return EINA_FALSE;
}


////////////////////////////////////////////

E_API void
e_client_frame_recalc(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (!ec->frame) return;
   evas_object_smart_callback_call(ec->frame, "frame_recalc", NULL);
}

////////////////////////////////////////////

E_API void
e_client_signal_move_begin(E_Client *ec, const char *sig, const char *src EINA_UNUSED)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;

   if (e_client_util_resizing_get(ec) || (ec->moving)) return;
   _e_client_moveinfo_gather(ec, sig);
   if (!_e_client_move_begin(ec)) return;
   _e_client_action_init(ec);
   e_pointer_mode_push(ec, E_POINTER_MOVE);
}

E_API void
e_client_signal_move_end(E_Client *ec, const char *sig EINA_UNUSED, const char *src EINA_UNUSED)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->zone) return;
   if (!ec->moving) return;
   _e_client_move_end(ec);
   e_zone_edge_enable();
   e_zone_flip_coords_handle(ec->zone, -1, -1);
   _e_client_action_finish();
}

E_API void
e_client_signal_resize_begin(E_Client *ec, const char *dir, const char *sig, const char *src EINA_UNUSED)
{
   int resize_mode = E_POINTER_RESIZE_BR;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   if (e_client_util_resizing_get(ec) || (ec->moving)) return;
   if (!strcmp(dir, "tl"))
     {
        resize_mode = E_POINTER_RESIZE_TL;
     }
   else if (!strcmp(dir, "t"))
     {
        resize_mode = E_POINTER_RESIZE_T;
     }
   else if (!strcmp(dir, "tr"))
     {
        resize_mode = E_POINTER_RESIZE_TR;
     }
   else if (!strcmp(dir, "r"))
     {
        resize_mode = E_POINTER_RESIZE_R;
     }
   else if (!strcmp(dir, "br"))
     {
        resize_mode = E_POINTER_RESIZE_BR;
     }
   else if (!strcmp(dir, "b"))
     {
        resize_mode = E_POINTER_RESIZE_B;
     }
   else if (!strcmp(dir, "bl"))
     {
        resize_mode = E_POINTER_RESIZE_BL;
     }
   else if (!strcmp(dir, "l"))
     {
        resize_mode = E_POINTER_RESIZE_L;
     }
   ec->resize_mode = resize_mode;
   _e_client_moveinfo_gather(ec, sig);
   if (!e_client_resize_begin(ec))
     return;
   _e_client_action_init(ec);
   e_pointer_mode_push(ec, ec->resize_mode);
}

E_API void
e_client_signal_resize_end(E_Client *ec, const char *dir EINA_UNUSED, const char *sig EINA_UNUSED, const char *src EINA_UNUSED)
{
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!e_client_util_resizing_get(ec)) return;
   _e_client_resize_handle(ec);
   _e_client_resize_end(ec);
   ec->changes.reset_gravity = 1;
   EC_CHANGED(ec);
   _e_client_action_finish();
}

////////////////////////////////////////////

E_API void
e_client_resize_limit(const E_Client *ec, int *w, int *h)
{
   int l = 0, r = 0, t = 0, b = 0;
   int dw = 0, dh = 0;
   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);

   e_comp_object_frame_geometry_get(ec->frame, &l, &r, &t, &b);
   if ((ec->frame) && e_comp_object_frame_allowed(ec->frame))
     {
        e_comp_object_frame_wh_unadjust(ec->frame, ec->w, ec->h, &dw, &dh);
        e_comp_object_frame_wh_unadjust(ec->frame, *w, *h, w, h);
     }
   else
     {
        *w -= l + r;
        *h -= t + b;
        dw = ec->w;
        dh = ec->h;
     }
   dw = abs(*w - dw);
   dh = abs(*h - dh);
   if (*h < 1) *h = 1;
   if (*w < 1) *w = 1;

   if ((ec->icccm.max_w > 0) && (*w > ec->icccm.max_w)) *w = ec->icccm.max_w;
   else if (*w < ec->icccm.min_w)
     *w = ec->icccm.min_w;
   if ((ec->icccm.max_h > 0) && (*h > ec->icccm.max_h)) *h = ec->icccm.max_h;
   else if (*h < ec->icccm.min_h)
     *h = ec->icccm.min_h;

   if (ec->icccm.step_w > 0)
     {
        int bw = ec->icccm.base_w ?: ec->icccm.min_w;

        bw = bw + (((*w - bw) / ec->icccm.step_w) * ec->icccm.step_w);
        if ((bw > ec->icccm.min_w) && ((ec->icccm.max_w < 1) || (bw < ec->icccm.max_w)))
          *w = bw;
     }
   if (ec->icccm.step_h > 0)
     {
        int bh = ec->icccm.base_h ?: ec->icccm.min_h;

        bh = bh + (((*h - bh) / ec->icccm.step_h) * ec->icccm.step_h);
        if ((bh > ec->icccm.min_h) && ((ec->icccm.max_h < 1) || (bh < ec->icccm.max_h)))
          *h = bh;
     }

   if (EINA_DBL_NONZERO(ec->icccm.min_aspect) ||
       EINA_DBL_NONZERO(ec->icccm.max_aspect))
     {
        double a = (double)*w / *h;
        Evas_Aspect_Control aspect;
        int aw, ah;
        double val;

        if (a < ec->icccm.min_aspect)
          {
             if (dw)
               aspect = EVAS_ASPECT_CONTROL_HORIZONTAL;
             else if (dh)
               aspect = EVAS_ASPECT_CONTROL_VERTICAL;
             else
               aspect = EVAS_ASPECT_CONTROL_BOTH;
             switch (aspect)
               {
                case EVAS_ASPECT_CONTROL_HORIZONTAL:
                  val = ((*h - (*w / ec->icccm.min_aspect)) * ec->icccm.step_h) / ec->icccm.step_h;
                  if (val > 0)
                    ah = ceil(val);
                  else
                    ah = floor(val);
                  if (*h - ah > ec->icccm.min_h)
                    {
                       *h -= ah;
                       break;
                    }
                  EINA_FALLTHROUGH;
                  /* No break */
                default:
                  val = (((*h * ec->icccm.min_aspect) - *w) * ec->icccm.step_w) / ec->icccm.step_w;
                  if (val > 0)
                    aw = ceil(val);
                  else
                    aw = floor(val);
                  if (*w + aw < ec->icccm.max_w)
                    *w += aw;
                  break;
               }
          }
        a = (double)*w / *h;
        if (a < ec->icccm.min_aspect) abort();
        if (a > ec->icccm.max_aspect)
          {
             if (dw)
               aspect = EVAS_ASPECT_CONTROL_HORIZONTAL;
             else if (dh)
               aspect = EVAS_ASPECT_CONTROL_VERTICAL;
             else
               aspect = EVAS_ASPECT_CONTROL_BOTH;
             switch (aspect)
               {
                case EVAS_ASPECT_CONTROL_HORIZONTAL:
                  val = (((*w / ec->icccm.max_aspect) - *h) * ec->icccm.step_h) / ec->icccm.step_h;
                  if (val > 0)
                    ah = ceil(val);
                  else
                    ah = floor(val);
                  if (*h + ah > ec->icccm.max_h)
                    {
                       *h += ah;
                       break;
                    }
                  EINA_FALLTHROUGH;
                  /* No break */
                default:
                  val = ((*w - (*h * ec->icccm.max_aspect)) * ec->icccm.step_w) / ec->icccm.step_w;
                  if (val > 0)
                    aw = ceil(val);
                  else
                    aw = floor(val);
                  if (*w - aw > ec->icccm.min_w)
                    *w -= aw;
                  break;
               }
          }
     }

   if (*h < 1) *h = 1;
   if (*w < 1) *w = 1;

   if ((ec->frame) && e_comp_object_frame_allowed(ec->frame))
     {
        if (ec->frame)
          e_comp_object_frame_wh_adjust(ec->frame, *w, *h, w, h);
     }
   else
     {
        *w += l + r;
        *h += t + b;
     }
}

////////////////////////////////////////////



E_API E_Client *
e_client_under_pointer_get(E_Desk *desk, E_Client *exclude)
{
   int x, y;

   /* We need to ensure that we can get the comp window for the
    * zone of either the given desk or the desk of the excluded
    * window, so return if neither is given */
   if (desk)
     ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   else if (exclude)
     ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   else
     return NULL;

   if (!desk)
     {
        desk = exclude->desk;
        if (!desk)
          {
             if (exclude->zone)
               desk = e_desk_current_get(exclude->zone);
             else
               desk = e_desk_current_get(e_zone_current_get());
          }
     }

   return _e_client_under_pointer_helper(desk, exclude, x, y);
}

////////////////////////////////////////////

E_API int
e_client_pointer_warp_to_center_now(E_Client *ec)
{
   if (e_config->disable_all_pointer_warps) return 0;
   if (warp_client == ec)
     {
        ecore_evas_pointer_warp(e_comp->ee, warp_to_x, warp_to_y);
        warp_to = 0;
        _e_client_pointer_warp_to_center_timer(NULL);
     }
   else
     {
        if (e_client_pointer_warp_to_center(ec))
          e_client_pointer_warp_to_center_now(ec);
     }
   return 1;
}

E_API int
e_client_pointer_warp_to_center(E_Client *ec)
{
   int x, y;
   E_Client *cec = NULL;

   ec = e_client_stack_active_adjust(ec);
   if (ec->override) return 0;
   if (!ec->zone) return 0;
   if (e_config->disable_all_pointer_warps) return 0;
   /* Only warp the pointer if it is not already in the area of
    * the given border */
   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   if ((x >= ec->x) && (x <= (ec->x + ec->w)) &&
       (y >= ec->y) && (y <= (ec->y + ec->h)))
     {
        cec = _e_client_under_pointer_helper(ec->desk, ec, x, y);
        if (cec) cec = e_client_stack_active_adjust(cec);
        if (cec == ec) return 0;
     }

   warp_to_x = ec->x + (ec->w / 2);
   if (warp_to_x < (ec->zone->x + 1))
     warp_to_x = ec->zone->x + ((ec->x + ec->w - ec->zone->x) / 2);
   else if (warp_to_x > (ec->zone->x + ec->zone->w))
     warp_to_x = (ec->zone->x + ec->zone->w + ec->x) / 2;

   warp_to_y = ec->y + (ec->h / 2);
   if (warp_to_y < (ec->zone->y + 1))
     warp_to_y = ec->zone->y + ((ec->y + ec->h - ec->zone->y) / 2);
   else if (warp_to_y > (ec->zone->y + ec->zone->h))
     warp_to_y = (ec->zone->y + ec->zone->h + ec->y) / 2;

   /* TODO: handle case where another border is over the exact center,
    * find a place where the requested border is not overlapped?
    *
   if (!cec) cec = _e_client_under_pointer_helper(ec->desk, ec, x, y);
   if (cec != ec)
     {
     }
   */

   warp_to = 1;
   warp_client = ec;
   ecore_evas_pointer_xy_get(e_comp->ee, &warp_x[0], &warp_y[0]);
   if (warp_timer) ecore_timer_del(warp_timer);
   warp_timer = ecore_timer_loop_add(0.01, _e_client_pointer_warp_to_center_timer, ec);
   return 1;
}

////////////////////////////////////////////

E_API void
e_client_redirected_set(E_Client *ec, Eina_Bool set)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (ec->input_only) return;
   set = !!set;
   if (ec->redirected == set) return;
   if (set)
     {
        e_client_frame_recalc(ec);
        if (!_e_client_hook_call(E_CLIENT_HOOK_REDIRECT, ec)) return;
     }
   else
     {
        if (!_e_client_hook_call(E_CLIENT_HOOK_UNREDIRECT, ec)) return;
     }
   e_comp_object_redirected_set(ec->frame, set);
   ec->redirected = !!set;
}

////////////////////////////////////////////

E_API void
e_client_next_mouse_action_ignore(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   ec->next_mouse_action_ignore = EINA_TRUE;
}

////////////////////////////////////////////

E_API Eina_Bool
e_client_is_stacking(const E_Client *ec)
{
   return e_comp->layers[e_comp_canvas_layer_map(ec->layer)].obj == ec->frame;
}

E_API Eina_Bool
e_client_has_xwindow(const E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   return EINA_FALSE;
#else
# ifdef HAVE_WAYLAND
   if (!e_pixmap_is_x(ec->pixmap))
     return !!e_comp_wl_client_xwayland_pixmap(ec);
# endif
   return e_pixmap_is_x(ec->pixmap);
#endif
}

////////////////////////////////////////////

E_API void
e_client_layout_cb_set(E_Client_Layout_Cb cb)
{
   if (_e_client_layout_cb && cb)
     CRI("ATTEMPTING TO OVERWRITE EXISTING CLIENT LAYOUT HOOK!!!");
   _e_client_layout_cb = cb;
}

////////////////////////////////////////////

E_API void
e_client_parent_set(E_Client *ec, E_Client *parent)
{
//   E_Client *prev;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (parent)
     {
        E_OBJECT_CHECK(parent);
        E_OBJECT_TYPE_CHECK(parent, E_CLIENT_TYPE);
     }

   if (ec == parent)
     {
        ERR("refusing to set client as its own parent");
        return;
     }

   if (parent && (parent->parent == ec))
     {
        ERR("refusing to set client as its parent's parent");
        return;
     }

   if (ec->parent == parent) return;

   /* If we already have a parent, remove it */
   if (ec->parent)
     {
        ec->parent->transients = eina_list_remove(ec->parent->transients, ec);
        if (ec->parent->modal == ec) ec->parent->modal = NULL;
        ec->parent = NULL;
     }
   if (parent)
     {
//        prev = eina_list_last_data_get(parent->transients);
        parent->transients = eina_list_append(parent->transients, ec);
        ec->parent = parent;
     }
   if (ec->parent)
     {
        evas_object_layer_set(ec->frame, ec->parent->layer);

/* complex stacking right above one dialog after the other hurts the
 * simple assumptions that the window should be on top by clients so
 * be dumber and disable this
        if (prev)
          evas_object_stack_above(ec->frame, prev->frame);
        else
          evas_object_stack_above(ec->frame, parent->frame);
 */
        evas_object_raise(ec->frame);

        if (e_client_util_ignored_get(ec)) return;
        if (e_pixmap_usable_get(ec->pixmap) && (!ec->lock_user_location))
          e_comp_object_util_center_on(ec->frame, parent->frame);

        if ((e_config->focus_setting == E_FOCUS_NEW_DIALOG) ||
            (ec->parent->focused && (e_config->focus_setting == E_FOCUS_NEW_DIALOG_IF_OWNER_FOCUSED)))
          ec->take_focus = 1;
     }
}

static void
_e_client_stack_top(E_Client *ec_base EINA_UNUSED,
                    E_Client *ec_stack, E_Client *ec_above)
{
/* complex stacking right above one dialog after the other hurts the
 * simple assumptions that the window should be on top by clients so
 * be dumber and disable this
   if (!ec_above)
     {
        evas_object_raise(ec_stack->frame);
        evas_object_stack_above(ec_base->frame, ec_stack->frame);
     }
   else
     {
        if (ec_above->transients)
          {
             e_client_transients_restack(ec_above);
             E_Client *ec_last = eina_list_last_data_get(ec_above->transients);
             if (ec_last)
               evas_object_stack_above(ec_stack->frame, ec_last->frame);
             else
               evas_object_stack_above(ec_stack->frame, ec_above->frame);
          }
        else
          evas_object_stack_above(ec_stack->frame, ec_above->frame);
     }
 */
   if (!ec_above) evas_object_raise(ec_stack->frame);
   else
     {
        if (ec_above->transients) e_client_transients_restack(ec_above);
        evas_object_raise(ec_stack->frame);
     }
}

E_API void
e_client_transients_restack(E_Client *ec)
{
   E_Client *child, *below = NULL;
   Eina_List *list;

   E_OBJECT_CHECK(ec);
   E_OBJECT_TYPE_CHECK(ec, E_CLIENT_TYPE);
   if (!ec->transients) return;

   list = eina_list_clone(ec->transients);
   EINA_LIST_FREE(list, child)
     {
        // Don't stack iconic transients. If the user wants these shown,
        // that's another option.
        if (child->iconic) continue;
        _e_client_stack_top(ec, child, below);
        below = child;
     }
}
#include "e.h"

E_API int E_EVENT_CLIENT_VOLUME = -1;
E_API int E_EVENT_CLIENT_MUTE = -1;
E_API int E_EVENT_CLIENT_UNMUTE = -1;
E_API int E_EVENT_CLIENT_VOLUME_SINK_ADD = -1;
E_API int E_EVENT_CLIENT_VOLUME_SINK_DEL = -1;
E_API int E_EVENT_CLIENT_VOLUME_SINK_CHANGED = -1;

static void _e_client_volume_event_simple_free(void *d, E_Event_Client *ev);
static void _e_client_volume_event_simple(E_Client *ec, int type);
static void _e_client_volume_object_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_client_volume_object_volume_changed(void *data, Evas_Object *obj, void *event_info);
static void _e_client_volume_object_volume_drag_stop(void *data, Evas_Object *obj, void *event_info);
static Eina_Bool _e_client_volume_object_changed(void *data, int type, void *event);
static void _e_client_volume_object_del_cb(void *data, Evas *evas, Evas_Object *obj, void *event_info);

static void
_e_client_volume_event_simple_free(void *d EINA_UNUSED, E_Event_Client *ev)
{
   UNREFD(ev->ec, 3);
   e_object_unref(E_OBJECT(ev->ec));
   free(ev);
}

static void
_e_client_volume_event_simple(E_Client *ec, int type)
{
   E_Event_Client *ev;

   ev = E_NEW(E_Event_Client, 1);
   ev->ec = ec;
   REFD(ec, 3);
   e_object_ref(E_OBJECT(ec));
   ecore_event_add(type, ev,
                   (Ecore_End_Cb)_e_client_volume_event_simple_free, NULL);
}

static void
_e_client_volume_sink_event_simple_free(void *d EINA_UNUSED, E_Event_Client_Volume_Sink *ev)
{
   UNREFD(ev->ec, 3);
   e_object_unref(E_OBJECT(ev->ec));
   free(ev);
}

static void
_e_client_volume_sink_event_simple(E_Client *ec, E_Client_Volume_Sink *sink, int type)
{
   E_Event_Client_Volume_Sink *ev;

   ev = E_NEW(E_Event_Client_Volume_Sink, 1);
   ev->ec = ec;
   ev->sink = sink;
   REFD(ec, 3);
   e_object_ref(E_OBJECT(ec));
   ecore_event_add(type, ev,
                   (Ecore_End_Cb)_e_client_volume_sink_event_simple_free, NULL);
}

static void
_e_client_volume_object_mouse_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec;

   ec = data;

   e_client_volume_mute_set(ec, !(ec->mute));
}

static void
_e_client_volume_object_volume_changed(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Client *ec;

   ec = data;

   e_client_volume_set(ec, elm_slider_value_get(obj));
}

static void
_e_client_volume_object_volume_drag_stop(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Client *ec;

   ec = data;

   e_client_volume_set(ec, elm_slider_value_get(obj));
}

static Eina_Bool
_e_client_volume_object_changed(void *data, int type EINA_UNUSED, void *event)
{
   E_Event_Client *ev;
   Evas_Object *o;
   Evas_Object *bx;
   Edje_Message_Int_Set *msg;

   ev = event;
   bx = data;

   o = edje_object_part_swallow_get(bx, "e.swallow.volume_icon");
   if (o)
     {
        msg = alloca(sizeof(Edje_Message_Int_Set) + (2 * sizeof(int)));
        msg->count = 3;
        msg->val[0] = ev->ec->mute;
        msg->val[1] = ev->ec->volume;
        msg->val[2] = msg->val[1];
        edje_object_message_send(o, EDJE_MESSAGE_INT_SET, 0, msg);
        edje_object_signal_emit(o,  "e,action,volume,change", "e");
     }
   o = edje_object_part_swallow_get(bx, "e.swallow.volume");
   if (o)
     {
        elm_slider_value_set(o, ev->ec->volume);
     }

   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_client_volume_object_del_cb(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Eina_List *handlers;
   Evas_Object *o;

   handlers = data;

   o = edje_object_part_swallow_get(obj, "e.swallow.volume_icon");
   if (o) evas_object_del(o);
   o = edje_object_part_swallow_get(obj, "e.swallow.volume");
   if (o) evas_object_del(o);

   E_FREE_LIST(handlers, ecore_event_handler_del);
}



EINTERN int
e_client_volume_init(void)
{
   E_EVENT_CLIENT_VOLUME = ecore_event_type_new();
   E_EVENT_CLIENT_MUTE = ecore_event_type_new();
   E_EVENT_CLIENT_UNMUTE = ecore_event_type_new();
   E_EVENT_CLIENT_VOLUME_SINK_ADD = ecore_event_type_new();
   E_EVENT_CLIENT_VOLUME_SINK_DEL = ecore_event_type_new();
   E_EVENT_CLIENT_VOLUME_SINK_CHANGED = ecore_event_type_new();

   return EINA_TRUE;
}

EINTERN void
e_client_volume_shutdown(void)
{
}

E_API E_Client_Volume_Sink *
e_client_volume_sink_new(E_Client_Volume_Sink_Get func_get, E_Client_Volume_Sink_Set func_set, E_Client_Volume_Sink_Min_Get func_min_get, E_Client_Volume_Sink_Max_Get func_max_get, E_Client_Volume_Sink_Name_Get func_name_get, void *data)
{
   E_Client_Volume_Sink *sink;

   sink = E_NEW(E_Client_Volume_Sink, 1);
   sink->func_set = func_set;
   sink->func_get = func_get;
   sink->func_min_get = func_min_get;
   sink->func_max_get = func_max_get;
   sink->func_name_get = func_name_get;
   sink->data = data;

   return sink;
}

E_API void
e_client_volume_sink_del(E_Client_Volume_Sink *sink)
{
   E_Client *ec;

   EINA_LIST_FREE(sink->clients, ec)
     {
        ec->sinks = eina_list_remove(ec->sinks, sink);
        e_comp_object_frame_volume_update(ec->frame);
        _e_client_volume_sink_event_simple(ec, sink,
                                           E_EVENT_CLIENT_VOLUME_SINK_DEL);
        e_object_unref(E_OBJECT(ec));
     }
   free(sink);
}

E_API void
e_client_volume_sink_set(E_Client_Volume_Sink *sink, int volume, Eina_Bool mute)
{
   EINA_SAFETY_ON_NULL_RETURN(sink);
   if (sink->func_set)
     sink->func_set(volume, mute, sink->data);
}

E_API void
e_client_volume_sink_get(const E_Client_Volume_Sink *sink, int *volume, Eina_Bool *mute)
{
   EINA_SAFETY_ON_NULL_RETURN(sink);
   if (sink->func_get)
     sink->func_get(volume, mute, sink->data);
}

E_API int
e_client_volume_sink_min_get(const E_Client_Volume_Sink *sink)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(sink, 0);
   if (sink->func_min_get)
     return sink->func_min_get(sink->data);

   return 0;
}

E_API int
e_client_volume_sink_max_get(const E_Client_Volume_Sink *sink)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(sink, 0);
   if (sink->func_max_get)
     return sink->func_max_get(sink->data);
   return 0;
}

E_API const char *
e_client_volume_sink_name_get(const E_Client_Volume_Sink *sink)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(sink, 0);
   if (sink->func_name_get)
     return sink->func_name_get(sink->data);
   return NULL;
}

static void
_e_client_volume_update(E_Client *ec)
{
   E_Client_Volume_Sink *sink;
   Eina_List *l;
   int volume_min, volume_max, volume, count;
   Eina_Bool mute;

   mute = EINA_TRUE;
   volume = 0;
   volume_min = 9999999;
   volume_max = 0;
   count = 0;
   EINA_LIST_FOREACH(ec->sinks, l, sink)
     {
        int volume_min2, volume_max2, volume2;
        Eina_Bool mute2;

        volume_min2 = e_client_volume_sink_min_get(sink);
        volume_max2 = e_client_volume_sink_max_get(sink);
        if (volume_min2 < volume_min) volume_min = volume_min2;
        if (volume_max2 > volume_max) volume_max = volume_max2;
        e_client_volume_sink_get(sink, &volume2, &mute2);
        if (!mute2)
          {
             mute = EINA_FALSE;
             volume += volume2;
             count++;
          }
     }
   if (ec->sinks)
     {
        ec->volume_min = volume_min;
        ec->volume_max = volume_max;
        if (count == 0)
          {
             if (ec->volume < volume_min)
               ec->volume = volume_min;
             if (ec->volume > volume_max)
               ec->volume = volume_max;
          }
        else
          ec->volume = volume / count;
        ec->mute = mute;
        ec->volume_control_enabled = EINA_TRUE;
     }
   else
     {
        ec->volume_min = 0;
        ec->volume_max = 0;
        ec->volume = 0;
        ec->mute = EINA_FALSE;
        ec->volume_control_enabled = EINA_FALSE;
     }
   if (ec->volume_control_enabled)
     {
        e_comp_object_frame_volume_update(ec->frame);
        e_client_volume_display_set(ec, ec->volume, ec->mute);
     }
}

E_API void
e_client_volume_sink_append(E_Client *ec, E_Client_Volume_Sink *sink)
{
   int volume;
   Eina_Bool mute;

   EINA_SAFETY_ON_NULL_RETURN(ec);

   ec->sinks = eina_list_append(ec->sinks, sink);
   sink->clients = eina_list_append(sink->clients, ec);
   e_object_ref(E_OBJECT(ec));
   if (!ec->volume_control_enabled)
     {
        ec->volume_min = e_client_volume_sink_min_get(sink);
        ec->volume_max = e_client_volume_sink_max_get(sink);
        e_client_volume_sink_get(sink, &volume, &mute);
        ec->volume = volume;
        ec->mute = !!mute;
        ec->volume_control_enabled = EINA_TRUE;
     }
   _e_client_volume_update(ec);
   _e_client_volume_sink_event_simple(ec, sink,
                                      E_EVENT_CLIENT_VOLUME_SINK_ADD);
}

E_API void
e_client_volume_sink_remove(E_Client *ec, E_Client_Volume_Sink *sink)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   ec->sinks = eina_list_remove(ec->sinks, sink);
   sink->clients = eina_list_remove(sink->clients, ec);
   _e_client_volume_update(ec);
   _e_client_volume_sink_event_simple(ec, sink,
                                      E_EVENT_CLIENT_VOLUME_SINK_DEL);
   e_object_unref(E_OBJECT(ec));
}

E_API void
e_client_volume_sink_update(E_Client_Volume_Sink *sink)
{
   Eina_List *l;
   E_Client *ec;

   EINA_SAFETY_ON_NULL_RETURN(sink);

   EINA_LIST_FOREACH(sink->clients, l, ec)
     {
        _e_client_volume_update(ec);
        _e_client_volume_sink_event_simple(ec, sink,
                                           E_EVENT_CLIENT_VOLUME_SINK_CHANGED);
     }
}

E_API void
e_client_volume_display_set(E_Client *ec, int volume, Eina_Bool mute)
{
   ec->volume = volume;
   ec->mute = !!mute;
   if (mute)
     _e_client_volume_event_simple(ec, E_EVENT_CLIENT_MUTE);
   else
     _e_client_volume_event_simple(ec, E_EVENT_CLIENT_UNMUTE);
   _e_client_volume_event_simple(ec, E_EVENT_CLIENT_VOLUME);
}

E_API void
e_client_volume_set(E_Client *ec, int volume)
{
   Eina_List *l;
   E_Client_Volume_Sink *sink;

   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (volume < ec->volume_min) volume = ec->volume_min;
   if (volume > ec->volume_max) volume = ec->volume_max;
   if (volume == ec->volume) return;
   ec->volume = volume;

   EINA_LIST_FOREACH(ec->sinks, l, sink)
     {
        e_client_volume_sink_set(sink, ec->volume, ec->mute);
     }

   _e_client_volume_update(ec);
   _e_client_volume_event_simple(ec, E_EVENT_CLIENT_VOLUME);
}

E_API void
e_client_volume_mute_set(E_Client *ec, Eina_Bool mute)
{
   Eina_List *l;
   E_Client_Volume_Sink *sink;

   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (!!mute == ec->mute) return;
   ec->mute = !!mute;

   EINA_LIST_FOREACH(ec->sinks, l, sink)
     {
        if (sink->func_set)
          sink->func_set(ec->volume, ec->mute, sink->data);
     }
   if (mute)
     _e_client_volume_event_simple(ec, E_EVENT_CLIENT_MUTE);
   else
     _e_client_volume_event_simple(ec, E_EVENT_CLIENT_UNMUTE);
}

E_API Evas_Object *
e_client_volume_object_add(E_Client *ec, Evas *evas)
{
   Evas_Object *bx, *o;
   Edje_Message_Int_Set *msg;
   Eina_List *handlers = NULL;

   bx = edje_object_add(evas);
   e_theme_edje_object_set(bx,
                           "base/theme/border/volume",
                           "e/widgets/border/volume");

   if (edje_object_part_exists(bx, "e.swallow.volume_icon"))
     {
        o = edje_object_add(evas);
        e_theme_edje_object_set(o,
                                "base/theme/modules/mixer",
                                "e/modules/mixer/main");

        evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                       _e_client_volume_object_mouse_down_cb,
                                       ec);
        edje_object_part_swallow(bx, "e.swallow.volume_icon", o);
        msg = alloca(sizeof(Edje_Message_Int_Set) + (2 * sizeof(int)));
        msg->count = 3;
        msg->val[0] = ec->mute;
        msg->val[1] = ec->volume;
        msg->val[2] = msg->val[1];
        edje_object_message_send(o, EDJE_MESSAGE_INT_SET, 0, msg);
        edje_object_signal_emit(o,  "e,action,volume,change", "e");
        evas_object_show(o);
     }
   if (edje_object_part_exists(bx, "e.swallow.volume"))
     {
        o = elm_slider_add(e_comp->elm);
        elm_object_focus_allow_set(o, EINA_FALSE);
        elm_slider_min_max_set(o, ec->volume_min,
                               ec->volume_max);
        elm_slider_span_size_set(o, ec->volume_max
                                 * elm_config_scale_get());
        elm_slider_unit_format_set(o, "%.0f");
        elm_slider_indicator_format_set(o, "%.0f");
        evas_object_smart_callback_add(o, "changed",
                                       _e_client_volume_object_volume_changed,
                                       ec);
        evas_object_smart_callback_add(o, "slider,drag,stop",
                                       _e_client_volume_object_volume_drag_stop,
                                       ec);
        elm_slider_value_set(o, ec->volume);
        edje_object_part_swallow(bx, "e.swallow.volume", o);
        evas_object_show(o);
     }

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_VOLUME,
                         _e_client_volume_object_changed, bx);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_MUTE,
                         _e_client_volume_object_changed, bx);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_UNMUTE,
                         _e_client_volume_object_changed, bx);

   evas_object_event_callback_add(bx, EVAS_CALLBACK_DEL,
                            _e_client_volume_object_del_cb, handlers);
   return bx;
}

E_API void
e_client_volume_object_emit(E_Client *ec, const char *sig, const char *src)
{
   Evas_Object *o;

   EINA_SAFETY_ON_NULL_RETURN(ec);
   o = e_comp_object_frame_volume_get(ec->frame);
   if (o)
     {
        edje_object_signal_emit(o, sig, src);
        o = edje_object_part_swallow_get(o, "e.swallow.volume_icon");
        if (o) edje_object_signal_emit(o,  sig, src);
     }
}
#include "e.h"

void
e_color_update_rgb(E_Color *ec)
{
   if (!ec) return;
   evas_color_rgb_to_hsv(ec->r, ec->g, ec->b, &(ec->h), &(ec->s), &(ec->v));
}

void
e_color_update_hsv(E_Color *ec)
{
   if (!ec) return;
   if (!EINA_FLT_NONZERO(ec->v))
     ec->r = ec->g = ec->b = 0;
   else
     evas_color_hsv_to_rgb(ec->h, ec->s, ec->v, &(ec->r), &(ec->g), &(ec->b));
}

void
e_color_copy(const E_Color *from, E_Color *to)
{
   if (!from || !to) return;
   to->r = from->r;
   to->g = from->g;
   to->b = from->b;
   to->h = from->h;
   to->s = from->s;
   to->v = from->v;
   to->a = from->a;
}

#include "e.h"

static void _e_color_dialog_button1_click(void *data, E_Dialog *edia);
static void _e_color_dialog_button2_click(void *data, E_Dialog *edia);
static void _e_color_dialog_free(E_Color_Dialog *dia);
static void _e_color_dialog_dia_del(void *obj);
static void _e_color_dialog_cb_csel_change(void *data, Evas_Object *obj, void *ev);

/**
 * Create a color selector dialog.
 *
 * @param c compositor to display on
 * @param color color to initialize to (or NULL for black).
 * @param alpha_enabled if set, uses alpha and let user edit it.
 */
E_Color_Dialog *
e_color_dialog_new(const E_Color *color, Eina_Bool alpha_enabled)
{
   E_Color_Dialog *dia;
   Evas_Object *o, *bx, *re, *fr;

   dia = E_OBJECT_ALLOC(E_Color_Dialog, E_COLOR_DIALOG_TYPE, _e_color_dialog_free);
   if (!dia) return NULL;
   dia->dia = e_dialog_new(NULL, "E", "_color_dialog");
   e_dialog_title_set(dia->dia, _("Color Selector"));

   dia->color = calloc(1, sizeof(E_Color));
   dia->initial = calloc(1, sizeof(E_Color));

   if (color)
     e_color_copy(color, dia->color);

   if ((!color) || (!alpha_enabled))
     dia->color->a = 255;

   e_color_copy(dia->color, dia->initial);

   bx = elm_box_add(dia->dia->win);
   E_EXPAND(bx);
   E_FILL(bx);

   o = elm_colorselector_add(bx);
   elm_colorselector_mode_set(o, ELM_COLORSELECTOR_COMPONENTS);
   elm_colorselector_color_set(o, dia->color->r, dia->color->g, dia->color->b, dia->color->a);
   E_EXPAND(o);
   E_FILL(o);
   evas_object_show(o);
   elm_box_pack_end(bx, o);

   fr = elm_frame_add(bx);
   E_WEIGHT(fr, EVAS_HINT_EXPAND, 0);
   E_FILL(fr);
   elm_object_text_set(fr, _("Color Preview"));
   evas_object_show(fr);
   elm_box_pack_end(bx, fr);

   re = evas_object_rectangle_add(evas_object_evas_get(dia->dia->win));
   evas_object_data_set(o, "rect", re);
   evas_object_size_hint_min_set(re, 1, ELM_SCALE_SIZE(100));
   evas_object_show(re);
   elm_object_content_set(fr, re);

   evas_object_smart_callback_add(o, "changed", _e_color_dialog_cb_csel_change, dia);
   e_dialog_content_set(dia->dia, bx, 250, -1);

   /* buttons at the bottom */
   e_dialog_button_add(dia->dia, _("Select"), NULL, _e_color_dialog_button1_click, dia);
   e_dialog_button_add(dia->dia, _("Cancel"), NULL, _e_color_dialog_button2_click, dia);
   elm_win_center(dia->dia->win, 1, 1);

   dia->dia->data = dia;
   e_object_del_attach_func_set(E_OBJECT(dia->dia), _e_color_dialog_dia_del);

   return dia;
}

void
e_color_dialog_show(E_Color_Dialog *dia)
{
   e_dialog_show(dia->dia);
   e_dialog_border_icon_set(dia->dia, "enlightenment/colors");
   evas_object_size_hint_min_set(dia->dia->win, 250, -1);
}

void
e_color_dialog_title_set(E_Color_Dialog *dia, const char *title)
{
   e_dialog_title_set(dia->dia, title);
}

void
e_color_dialog_select_callback_set(E_Color_Dialog *dia, void (*func)(E_Color_Dialog *dia, E_Color *color, void *data), void *data)
{
   dia->select_func = func;
   dia->select_data = data;
}

void
e_color_dialog_cancel_callback_set(E_Color_Dialog *dia, void (*func)(E_Color_Dialog *dia, E_Color *color, void *data), void *data)
{
   dia->cancel_func = func;
   dia->cancel_data = data;
}

E_API void
e_color_dialog_change_callback_set(E_Color_Dialog *dia, void (*func)(E_Color_Dialog *dia, E_Color *color, void *data), void *data)
{
   dia->change_func = func;
   dia->change_data = data;
}

static void
_e_color_dialog_cb_csel_change(void *data, Evas_Object *obj, void *ev EINA_UNUSED)
{
   E_Color_Dialog *dia = data;
   Evas_Object *re;
   int r, g, b;

   elm_colorselector_color_get(obj, &dia->color->r, &dia->color->g, &dia->color->b, &dia->color->a);
   r = dia->color->r;
   g = dia->color->g;
   b = dia->color->b;
   re = evas_object_data_get(obj, "rect");
   evas_color_argb_premul(dia->color->a, &r, &g, &b);
   evas_object_color_set(re, r, g, b, dia->color->a);
   if (dia->change_func)
     dia->change_func(dia, dia->color, dia->change_data);
}

static void
_e_color_dialog_button1_click(void *data, E_Dialog *edia EINA_UNUSED)
{
   E_Color_Dialog *dia;

   dia = data;
   if (dia->select_func)
     dia->select_func(dia, dia->color, dia->select_data);
   _e_color_dialog_free(dia);
}

static void
_e_color_dialog_button2_click(void *data, E_Dialog *edia EINA_UNUSED)
{
   E_Color_Dialog *dia;

   dia = data;
   if (dia->cancel_func)
     dia->cancel_func(dia, dia->initial, dia->cancel_data);
   _e_color_dialog_free(data);
}

static void
_e_color_dialog_free(E_Color_Dialog *dia)
{
   if (dia->dia)
     {
        e_object_del_attach_func_set(E_OBJECT(dia->dia), NULL);
        e_object_del(E_OBJECT(dia->dia));
        dia->dia = NULL;
     }
   E_FREE(dia->color);
   E_FREE(dia);
}

static void
_e_color_dialog_dia_del(void *obj)
{
   E_Dialog *dia = obj;
   E_Color_Dialog *cdia = dia->data;
   if (cdia->cancel_func && cdia->initial)
     cdia->cancel_func(cdia, cdia->initial, cdia->cancel_data);

   cdia->dia = NULL;
   e_object_del(E_OBJECT(cdia));
}

#include "e.h"
#ifdef HAVE_WAYLAND
# include "e_comp_wl.h"
#endif

#define OVER_FLOW 1
//#define BORDER_ZOOMAPS
//////////////////////////////////////////////////////////////////////////
//
// TODO (no specific order):
//   1. abstract evas object and compwin so we can duplicate the object N times
//      in N canvases - for winlist, everything, pager etc. too
//   2. implement "unmapped composite cache" -> N pixels worth of unmapped
//      windows to be fully composited. only the most active/recent.
//   3. for unmapped windows - when window goes out of unmapped comp cache
//      make a miniature copy (1/4 width+height?) and set property on window
//      with pixmap id
//
//////////////////////////////////////////////////////////////////////////

static Eina_List *handlers = NULL;
static Eina_List *hooks = NULL;
E_API E_Comp *e_comp = NULL;
E_API E_Comp_X_Data *e_comp_x = NULL;
E_API E_Comp_Wl_Data *e_comp_wl = NULL;
static Eina_Hash *ignores = NULL;
static Eina_List *actions = NULL;

static E_Comp_Config *conf = NULL;
static E_Config_DD *conf_edd = NULL;
static E_Config_DD *conf_match_edd = NULL;

static Ecore_Timer *action_timeout = NULL;
static Eina_Bool gl_avail = EINA_FALSE;
static Eina_Bool shape_debug = EINA_FALSE;

static int _e_comp_log_dom = -1;

E_API int E_EVENT_COMPOSITOR_UPDATE = -1;
E_API int E_EVENT_COMPOSITOR_DISABLE = -1;
E_API int E_EVENT_COMPOSITOR_ENABLE = -1;
E_API int E_EVENT_COMPOSITOR_XWAYLAND_INIT = -1;

//////////////////////////////////////////////////////////////////////////
#undef DBG
#undef INF
#undef WRN
#undef ERR
#undef CRI

#define SHAPE_DBG(...) do { if (shape_debug) EINA_LOG_DOM_DBG(_e_comp_log_dom, __VA_ARGS__); } while (0)
#define SHAPE_INF(...) do { if (shape_debug) EINA_LOG_DOM_INFO(_e_comp_log_dom, __VA_ARGS__); } while (0)
#define SHAPE_WRN(...) do { if (shape_debug) EINA_LOG_DOM_WARN(_e_comp_log_dom, __VA_ARGS__); } while (0)
#define SHAPE_ERR(...) do { if (shape_debug) EINA_LOG_DOM_ERR(_e_comp_log_dom, __VA_ARGS__); } while (0)
#define SHAPE_CRI(...) do { if (shape_debug) EINA_LOG_DOM_CRIT(_e_comp_log_dom, __VA_ARGS__); } while (0)

#define DBG(...)            EINA_LOG_DOM_DBG(_e_comp_log_dom, __VA_ARGS__)
#define INF(...)            EINA_LOG_DOM_INFO(_e_comp_log_dom, __VA_ARGS__)
#define WRN(...)            EINA_LOG_DOM_WARN(_e_comp_log_dom, __VA_ARGS__)
#define ERR(...)            EINA_LOG_DOM_ERR(_e_comp_log_dom, __VA_ARGS__)
#define CRI(...)            EINA_LOG_DOM_CRIT(_e_comp_log_dom, __VA_ARGS__)

static Eina_Bool
_e_comp_visible_object_clip_is(Evas_Object *obj)
{
   Evas_Object *clip;
   int a;

   clip = evas_object_clip_get(obj);
   if (!evas_object_visible_get(clip)) return EINA_FALSE;
   evas_object_color_get(clip, NULL, NULL, NULL, &a);
   if (a <= 0) return EINA_FALSE;
   if (evas_object_clip_get(clip))
     return _e_comp_visible_object_clip_is(clip);
   return EINA_TRUE;
}

static Eina_Bool
_e_comp_visible_object_is(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   const char *type = evas_object_type_get(obj);
   Evas_Coord xx, yy, ww, hh;

   if ((!type) || (!e_util_strcmp(type, "e_comp_object"))) return EINA_FALSE;
   if (evas_object_data_get(obj, "comp_skip")) return EINA_FALSE;
   evas_object_geometry_get(obj, &xx, &yy, &ww, &hh);
   if (E_INTERSECTS(x, y, w, h, xx, yy, ww, hh))
     {
        if ((evas_object_visible_get(obj))
            && (!evas_object_clipees_get(obj))
           )
          {
             int a;

             evas_object_color_get(obj, NULL, NULL, NULL, &a);
             if (a > 0)
               {
                  if ((!strcmp(type, "rectangle")) ||
                      (!strcmp(type, "image")) ||
                      (!strcmp(type, "text")) ||
                      (!strcmp(type, "textblock")) ||
                      (!strcmp(type, "textgrid")) ||
                      (!strcmp(type, "polygon")) ||
                      (!strcmp(type, "line")))
                    {
                       if (evas_object_clip_get(obj))
                         return _e_comp_visible_object_clip_is(obj);
                       return EINA_TRUE;
                    }
                  else
                    {
                       Eina_List *children;

                       if ((children = evas_object_smart_members_get(obj)))
                         {
                            Eina_List *l;
                            Evas_Object *o;

                            EINA_LIST_FOREACH(children, l, o)
                              {
                                 if (_e_comp_visible_object_is(o, x, y, w, h))
                                   {
                                      if (evas_object_clip_get(o))
                                        {
                                           eina_list_free(children);
                                           return _e_comp_visible_object_clip_is(o);
                                        }
                                      eina_list_free(children);
                                      return !!evas_object_data_get(o, "comp_skip");
                                   }
                              }
                            eina_list_free(children);
                         }
                    }
               }
          }
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_visible_object_is_above(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Evas_Object *above;

   for (above = evas_object_above_get(obj); above;
        above = evas_object_above_get(above))
     {
        if (_e_comp_visible_object_is(above, x, y, w, h)) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static E_Client *
_e_comp_fullscreen_check(void)
{
   E_Client *ec;

//   printf("------------------------\n");
   E_CLIENT_REVERSE_FOREACH(ec)
     {
        Evas_Object *o = ec->frame;

//        printf("FSCK %p [%i %i %ix%i] [%s]\n",
//               ec, ec->x, ec->y, ec->w, ec->h,
//               e_client_util_name_get(ec));
        if (ec->ignored || ec->input_only || (!evas_object_visible_get(ec->frame)))
          continue;
        if (!e_comp_util_client_is_fullscreen(ec))
          {
             if (evas_object_data_get(ec->frame, "comp_skip")) continue;
             return NULL;
          }
        while (o)
          {
             if (_e_comp_visible_object_is_above
                 (o, 0, 0, e_comp->w, e_comp->h)) return NULL;
             o = evas_object_smart_parent_get(o);
          }
        return ec;
     }
   return NULL;
}

static void
_e_comp_fps_update(void)
{
   if (conf->fps_show)
     {
        if (e_comp->canvas->fps_bg) return;

        e_comp->canvas->fps_bg = evas_object_rectangle_add(e_comp->evas);
        evas_object_color_set(e_comp->canvas->fps_bg, 0, 0, 0, 192);
        evas_object_layer_set(e_comp->canvas->fps_bg, E_LAYER_MAX);
        evas_object_name_set(e_comp->canvas->fps_bg, "e_comp->canvas->fps_bg");
        evas_object_lower(e_comp->canvas->fps_bg);
        evas_object_pass_events_set(e_comp->canvas->fps_bg, EINA_TRUE);
        evas_object_show(e_comp->canvas->fps_bg);

        e_comp->canvas->fps_fg = evas_object_text_add(e_comp->evas);
        evas_object_text_font_set(e_comp->canvas->fps_fg, "Sans", 10);
        evas_object_text_text_set(e_comp->canvas->fps_fg, "???");
        evas_object_color_set(e_comp->canvas->fps_fg, 255, 255, 255, 255);
        evas_object_layer_set(e_comp->canvas->fps_fg, E_LAYER_MAX);
        evas_object_name_set(e_comp->canvas->fps_fg, "e_comp->canvas->fps_fg");
        evas_object_stack_above(e_comp->canvas->fps_fg, e_comp->canvas->fps_bg);
        evas_object_pass_events_set(e_comp->canvas->fps_fg, EINA_TRUE);
        evas_object_show(e_comp->canvas->fps_fg);

        e_comp->canvas->fps_gr = evas_object_image_filled_add(e_comp->evas);
        evas_object_image_smooth_scale_set(e_comp->canvas->fps_gr, EINA_FALSE);
        evas_object_image_alpha_set(e_comp->canvas->fps_gr, EINA_TRUE);
        evas_object_image_size_set(e_comp->canvas->fps_gr, 1, 1);
        evas_object_color_set(e_comp->canvas->fps_gr, 255, 255, 255, 255);
        evas_object_layer_set(e_comp->canvas->fps_gr, E_LAYER_MAX);
        evas_object_name_set(e_comp->canvas->fps_gr, "e_comp->canvas->fps_gr");
        evas_object_stack_above(e_comp->canvas->fps_gr, e_comp->canvas->fps_fg);
        evas_object_pass_events_set(e_comp->canvas->fps_gr, EINA_TRUE);
        evas_object_show(e_comp->canvas->fps_gr);
     }
   else
     {
        E_FREE_FUNC(e_comp->canvas->fps_fg, evas_object_del);
        E_FREE_FUNC(e_comp->canvas->fps_bg, evas_object_del);
        E_FREE_FUNC(e_comp->canvas->fps_gr, evas_object_del);
     }
}

static void
_e_comp_cb_nocomp_begin(void)
{
   E_Client *ec, *ecf;

   if (e_comp->nocomp) return;

   E_FREE_FUNC(e_comp->nocomp_delay_timer, ecore_timer_del);

   ecf = _e_comp_fullscreen_check();
   if (!ecf) return;
   e_object_ref(E_OBJECT(ecf));
   e_comp->nocomp_ec = ecf;
   E_CLIENT_FOREACH(ec)
     if (ec != ecf) e_client_redirected_set(ec, 0);

   INF("NOCOMP %p: frame %p", ecf, ecf->frame);
   e_comp->nocomp = 1;

   {
      Eina_Bool fs;

      fs = e_comp->nocomp_ec->fullscreen;
      if (!fs)
        e_comp->nocomp_ec->saved.layer = e_comp->nocomp_ec->layer;
      e_comp->nocomp_ec->fullscreen = 0;
      e_comp->nocomp_ec->layer = E_LAYER_CLIENT_PRIO;
      evas_object_layer_set(e_comp->nocomp_ec->frame, E_LAYER_CLIENT_PRIO);
      e_comp->nocomp_ec->fullscreen = fs;
   }
   e_client_redirected_set(ecf, 0);

   //ecore_evas_manual_render_set(e_comp->ee, EINA_TRUE);
   ecore_evas_hide(e_comp->ee);
   edje_file_cache_flush();
   edje_collection_cache_flush();
   evas_image_cache_flush(e_comp->evas);
   evas_font_cache_flush(e_comp->evas);
   evas_render_dump(e_comp->evas);

   DBG("JOB2...");
   e_comp_render_queue();
   e_comp_shape_queue_block(1);
   ecore_event_add(E_EVENT_COMPOSITOR_DISABLE, NULL, NULL, NULL);
   printf("COMP: suspend\n");
}

static void
_e_comp_cb_nocomp_end(void)
{
   E_Client *ec;

   if (!e_comp->nocomp) return;

   printf("COMP: resume\n");
   INF("COMP RESUME!");
   //ecore_evas_manual_render_set(e_comp->ee, EINA_FALSE);
   ecore_evas_show(e_comp->ee);
   E_CLIENT_FOREACH(ec)
     {
        e_client_redirected_set(ec, 1);
        if (ec->visible && (!ec->input_only))
          e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
     }
#ifndef HAVE_WAYLAND_ONLY
   e_comp_x_nocomp_end();
#endif
   e_comp_render_queue();
   e_comp_shape_queue_block(0);
   ecore_event_add(E_EVENT_COMPOSITOR_ENABLE, NULL, NULL, NULL);
}

static Eina_Bool
_e_comp_cb_nocomp_begin_timeout(void *data EINA_UNUSED)
{
   e_comp->nocomp_delay_timer = NULL;
   if (e_comp->nocomp_override == 0)
     {
        if (_e_comp_fullscreen_check()) e_comp->nocomp_want = 1;
        _e_comp_cb_nocomp_begin();
     }
   return EINA_FALSE;
}


static void
_e_comp_client_update(E_Client *ec)
{
   int pw, ph;

   DBG("UPDATE [%p] pm = %p", ec, ec->pixmap);
   if (e_object_is_del(E_OBJECT(ec))) return;

   e_pixmap_size_get(ec->pixmap, &pw, &ph);

   if (e_pixmap_dirty_get(ec->pixmap) && (!e_comp->nocomp))
     {
        int w, h;

        if (e_pixmap_refresh(ec->pixmap) &&
            e_pixmap_size_get(ec->pixmap, &w, &h) &&
            e_pixmap_size_changed(ec->pixmap, pw, ph))
          {
             e_pixmap_image_clear(ec->pixmap, 0);
             e_comp_object_render_update_del(ec->frame); //clear update
             if (ec->changes.visible && (!evas_object_visible_get(ec->frame)))
               evas_object_show(ec->frame);
          }
        else if (!e_pixmap_size_get(ec->pixmap, NULL, NULL))
          {
             WRN("FAIL %p", ec);
             e_comp_object_redirected_set(ec->frame, 0);
             if (e_pixmap_failures_get(ec->pixmap) > 3)
               e_comp_object_render_update_add(ec->frame);
          }
     }
   if (!(e_comp->saver && ecore_evas_manual_render_get(e_comp->ee)) &&
       e_pixmap_size_get(ec->pixmap, &pw, &ph))
     {
        //INF("PX DIRTY: PX(%dx%d) CLI(%dx%d)", pw, ph, ec->client.w, ec->client.h);
        e_pixmap_image_refresh(ec->pixmap);
        e_comp_object_dirty(ec->frame);
        if (e_pixmap_is_x(ec->pixmap) && (!ec->override))
          evas_object_resize(ec->frame, ec->w, ec->h);
     }
}

static void
_e_comp_nocomp_end(void)
{
   e_comp->nocomp_want = 0;
   E_FREE_FUNC(e_comp->nocomp_delay_timer, ecore_timer_del);
   _e_comp_cb_nocomp_end();
   if (e_comp->nocomp_ec)
     {
        E_Layer layer = MAX(e_comp->nocomp_ec->saved.layer, E_LAYER_CLIENT_NORMAL);
        Eina_Bool fs;

        fs = e_comp->nocomp_ec->fullscreen;
        e_comp->nocomp_ec->fullscreen = 0;
        if (fs)
          {
             if (!e_config->allow_above_fullscreen)
               layer = E_LAYER_CLIENT_FULLSCREEN;
          }
        evas_object_layer_set(e_comp->nocomp_ec->frame, layer);
        e_comp->nocomp_ec->fullscreen = fs;
     }
   E_FREE_FUNC(e_comp->nocomp_ec, e_object_unref);
}

static double
_e_comp_frame_event_fps_calc(int info)
{
   int i, count = 0, iind, start, end, end2;
   Eina_Bool first = EINA_FALSE;
   double t0 = 0.0, dt;

   start = e_comp->frame_event_now - 1 + E_COMP_FRAME_EVENT_COUNT;
   end = e_comp->frame_event_now + E_COMP_FRAME_EVENT_COUNT ;
   end2 = e_comp->frame_event_now + E_COMP_FRAME_EVENT_COUNT + 1;
   for (i = start;;)
     {
        iind = i % E_COMP_FRAME_EVENT_COUNT;
        i--;
        if (iind == (end % E_COMP_FRAME_EVENT_COUNT)) break;
        if (e_comp->frame_events[iind].info[0] != info) continue;
        count++;
        if (!first)
          {
             t0 =  e_comp->frame_events[iind].t;
             first = EINA_TRUE;
          }
        else
          {
             dt = t0 - e_comp->frame_events[iind].t;
             if ((dt > 0.5) || (iind == (end2 % E_COMP_FRAME_EVENT_COUNT)))
               {
                  if (dt > 0.0) return ((double)(count - 1)) / dt;
               }
          }
     }
   return 0.0;
}

E_API void
e_comp_frame_event_add(int info[4], double t)
{
   e_comp->frame_events[e_comp->frame_event_now].info[0] = info[0];
   e_comp->frame_events[e_comp->frame_event_now].info[1] = info[1];
   e_comp->frame_events[e_comp->frame_event_now].info[2] = info[2];
   e_comp->frame_events[e_comp->frame_event_now].info[3] = info[3];
   e_comp->frame_events[e_comp->frame_event_now].t = t;
   e_comp->frame_event_now++;
   if (e_comp->frame_event_now >= E_COMP_FRAME_EVENT_COUNT)
     e_comp->frame_event_now = 0;
}

E_API void
e_comp_client_frame_add(Evas_Object *obj EINA_UNUSED)
{
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_CLIENT_DAMAGE, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
}

static inline void
_e_comp_fps_draw_point(unsigned int *pix, int stride, int w, int y, int col, int x)
{
   unsigned int *pixrow = pix + (y * (stride / 4));
   // out of bounds abort draw
   if (x <  0) return;
   if (x >= w) return;
   pixrow[x] = col;
}

/* for future
static inline void
_e_comp_fps_draw_span(unsigned int *pix, int stride, int w, int y, int col, int x1, int x2)
{
   unsigned int *pixrow = pix + (y * (stride / 4));
   int x;

   if (x2 < x1) // swap so x2 >= x1
     {
        x = x2;
        x2 = x1;
        x1 = x;
     }
   // out of bounds abort draw
   if (x2 < 0) return;
   if (x1 >= w) return;
   // clip to span area
   if (x1 < 0) x1 = 0;
   if (x2 >= w) x2 = w - 1;
   for (x = x1; x <= x2; x++)
     pixrow[x] = col;
}
*/

E_API void
e_comp_fps_update(void)
{
   char buf[128];
   double fps, comp_fps;
   Evas_Coord x = 0, y = 0, w = 0, h = 0;
   Evas_Coord gx = 0, gy = 0, gw = 0, gh = 0;
   Evas_Coord bx = 0, by = 0, bw = 0, bh = 0;
   E_Zone *z;

   e_comp->frameskip++;
   if (e_comp->frameskip >= 20)
     {
        unsigned int *pix;
        int i, pixw, pixh, pixsz, pixstride, px, pixscale, info0, start, end, iind;
        double t;

        t = ecore_time_get();
        e_comp->frameskip = 0;

        pixh = 12;
        pixsz = 2;

        fps = _e_comp_frame_event_fps_calc(E_COMP_FRAME_EVENT_HANDLE_DAMAGE);
        comp_fps = _e_comp_frame_event_fps_calc(E_COMP_FRAME_EVENT_RENDER_END);

        snprintf(buf, sizeof(buf), "FPS: (in) %1.1f (out) %1.1f", fps, comp_fps);
        evas_object_text_text_set(e_comp->canvas->fps_fg, buf);

        evas_object_geometry_get(e_comp->canvas->fps_fg, NULL, NULL, &w, &h);
        w += 16;
        h += 16;
        z = e_zone_current_get();
        pixscale = 1000;
        if (z)
          {
             if (z->w >= 2048) pixw = 2000;
             else if (z->w >= 1600) pixw = 1500;
             else if (z->w >= 1024) pixw = 1000;
             else if (z->w >= 512) pixw = 500;
             else if (z->w >= 256) pixw = 250;
             else pixw = 125;

             switch (conf->fps_corner)
               {
                case 3: // bottom-right
                  x = z->x + z->w - w;
                  y = z->y + z->h - h;

                  gw = pixw;
                  gh = pixh * pixsz;
                  gx = x + w - gw;
                  gy = y - gh - 8;

                  bw = (gw > bw) ? gw : bw;
                  bh = h + gh + 16;
                  bx = (x > gx) ? gx: x;
                  by = gy - 8;
                  break;
                case 2: // bottom-left
                  x = z->x;
                  y = z->y + z->h - h;

                  gw = pixw;
                  gh = pixh * pixsz;
                  gx = x;
                  gy = y - gh - 8;

                  bw = (gw > bw) ? gw : bw;
                  bh = h + gh + 16;
                  bx = x;
                  by = gy - 8;
                  break;
                case 1: // top-right
                  x = z->x + z->w - w;
                  y = z->y;

                  gw = pixw;
                  gh = pixh * pixsz;
                  gx = x + w - gw;
                  gy = y + h;

                  bw = (gw > bw) ? gw : bw;
                  bh = h + gh + 16;
                  bx = (x > gx) ? gx: x;
                  by = y;
                  break;
                case 0: // top-left
                default:
                  x = z->x;
                  y = z->y;

                  gw = pixw;
                  gh = pixh * pixsz;
                  gx = x;
                  gy = y + h;

                  bw = (gw > bw) ? gw : bw;
                  bh = h + gh + 16;
                  bx = x;
                  by = y;
                  break;
               }
          }
        else pixw = 500;

        evas_object_image_size_set(e_comp->canvas->fps_gr, pixw, pixh);
        evas_object_image_alpha_set(e_comp->canvas->fps_gr, EINA_TRUE);
        pixstride = evas_object_image_stride_get(e_comp->canvas->fps_gr);
        pix = evas_object_image_data_get(e_comp->canvas->fps_gr, EINA_TRUE);
        if (pix)
          {
             memset(pix, 0, pixstride * pixh);

             // go backwards from newest to oldest
             start = e_comp->frame_event_now + E_COMP_FRAME_EVENT_COUNT - 1;
             end = e_comp->frame_event_now + E_COMP_FRAME_EVENT_COUNT ;
             for (i = start;;)
               {
                  iind = i % E_COMP_FRAME_EVENT_COUNT;
                  i--;
                  if (iind == (end % E_COMP_FRAME_EVENT_COUNT)) break;

                  info0 = e_comp->frame_events[iind].info[0];
                  px = (t - e_comp->frame_events[iind].t) * pixscale;
                  px = pixw - px - 1;
                  if (px < 0) break;
                  if (info0 == E_COMP_FRAME_EVENT_RENDER2_END)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 0, 0xffffffff, px);
                  else if (info0 == E_COMP_FRAME_EVENT_RENDER2_BEGIN)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 1, 0xffffee88, px);
                  else if (info0 == E_COMP_FRAME_EVENT_RENDER_END)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 2, 0xffff9944, px);
                  else if (info0 == E_COMP_FRAME_EVENT_RENDER_BEGIN)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 3, 0xffff4433, px);
                  else if (info0 == E_COMP_FRAME_EVENT_IDLE_ENTER)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 4, 0xffff88ff, px);
                  else if (info0 == E_COMP_FRAME_EVENT_HANDLE_DAMAGE)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 5, 0xff44ff22, px);
                  else if (info0 == E_COMP_FRAME_EVENT_CLIENT_DAMAGE)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 6, 0xff4466ff, px);
                  else if (info0 == E_COMP_FRAME_EVENT_IDLE_EXIT)
                    _e_comp_fps_draw_point(pix, pixstride, pixw, 7, 0xff994499, px);
               }
             for (t = 0.0; t < 10.0; t += (1.0 / 60.0))
               {
                  px = t * pixscale;
                  px = pixw - px - 1;
                  if (px < 0) break;
                  _e_comp_fps_draw_point(pix, pixstride, pixw, 10, 0xffffffff, px);
                  _e_comp_fps_draw_point(pix, pixstride, pixw, 11, 0xffffffff, px - 1);
                  _e_comp_fps_draw_point(pix, pixstride, pixw, 11, 0xffffffff, px);
                  _e_comp_fps_draw_point(pix, pixstride, pixw, 11, 0xffffffff, px + 1);
               }
             evas_object_image_data_set(e_comp->canvas->fps_gr, pix);
             evas_object_image_data_update_add(e_comp->canvas->fps_gr,
                                               0, 0, pixw, pixh);
          }

        evas_object_color_set(e_comp->canvas->fps_bg, 0, 0, 0, 192);
        evas_object_geometry_set(e_comp->canvas->fps_bg, bx, by, bw, bh);
        evas_object_geometry_set(e_comp->canvas->fps_gr, gx, gy, gw, gh);
        evas_object_move(e_comp->canvas->fps_fg, x + 8, y + 8);
     }
}

static Eina_Bool
_e_comp_cb_update(void)
{
   E_Client *ec;
   Eina_List *l;
   //   static int doframeinfo = -1;

   if (!e_comp) return EINA_FALSE;
   DBG("UPDATE ALL");
   if (e_comp->nocomp) goto nocomp;
//   if (conf->grab && (!e_comp->grabbed))
//     {
//        if (e_comp->grab_cb) e_comp->grab_cb();
//        e_comp->grabbed = 1;
//     }
   e_comp->updating = 1;
   l = e_comp->updates;
   e_comp->updates = NULL;
   EINA_LIST_FREE(l, ec)
     {
        /* clear update flag */
        e_comp_object_render_update_del(ec->frame);
        _e_comp_client_update(ec);
     }
   e_comp->updating = 0;
   _e_comp_fps_update();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_HANDLE_DAMAGE, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_loop_time_get());
        e_comp_fps_update();
     }
nocomp:
   ec = _e_comp_fullscreen_check();
   if (ec)
     {
        if (conf->nocomp_fs)
          {
             if (e_comp->nocomp && e_comp->nocomp_ec)
               {
                  E_Client *nec = NULL;
                  for (ec = e_client_top_get(), nec = e_client_below_get(ec);
                       (ec && nec) && (ec != nec); ec = nec, nec = e_client_below_get(ec))
                    {
                       if (ec == e_comp->nocomp_ec) break;
                       if (evas_object_layer_get(ec->frame) < evas_object_layer_get(e_comp->nocomp_ec->frame)) break;
                       if (e_client_is_stacking(ec)) continue;
                       if (!ec->visible) continue;
                       if (evas_object_data_get(ec->frame, "comp_skip")) continue;
                       if (e_object_is_del(E_OBJECT(ec)) || (!e_client_util_desk_visible(ec, e_desk_current_get(ec->zone)))) continue;
                       if (ec->override || (e_config->allow_above_fullscreen))
                         {
                            _e_comp_nocomp_end();
                            break;
                         }
                       else
                         evas_object_stack_below(ec->frame, e_comp->nocomp_ec->frame);
                    }
               }
             else if ((!e_comp->nocomp) && (!e_comp->nocomp_override))
               {
                  if (!e_comp->nocomp_delay_timer)
                    e_comp->nocomp_delay_timer = ecore_timer_loop_add(1.0, _e_comp_cb_nocomp_begin_timeout, NULL);
               }
          }
     }
   else
     _e_comp_nocomp_end();

   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_cb_job(void *data EINA_UNUSED)
{
   DBG("UPDATE ALL JOB...");
   if (e_comp->update_job) e_comp->update_job = NULL;
   _e_comp_cb_update();
}

//////////////////////////////////////////////////////////////////////////


static void
_e_comp_shape_debug_rect(Eina_Rectangle *rect, E_Color *color)
{
   Evas_Object *o;

#define COLOR_INCREMENT 30
   o = evas_object_rectangle_add(e_comp->evas);
   if (color->r < 256 - COLOR_INCREMENT)
     evas_object_color_set(o, (color->r += COLOR_INCREMENT), 0, 0, 255);
   else if (color->g < 256 - COLOR_INCREMENT)
     evas_object_color_set(o, 0, (color->g += COLOR_INCREMENT), 0, 255);
   else
     evas_object_color_set(o, 0, 0, (color->b += COLOR_INCREMENT), 255);
   evas_object_pass_events_set(o, 1);
   evas_object_layer_set(o, E_LAYER_MENU - 1);
   evas_object_move(o, rect->x, rect->y);
   evas_object_resize(o, rect->w, rect->h);
   e_comp->debug_rects = eina_list_append(e_comp->debug_rects, o);
   evas_object_show(o);
}

static Eina_Bool
_e_comp_shapes_update_object_checker_function_thingy(Evas_Object *o)
{
   Eina_List *l;
   E_Zone *zone;

   if (o == e_comp->canvas->resize_object) return EINA_TRUE;
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if ((o == zone->over) || (o == zone->base)) return EINA_TRUE;
        if ((o == zone->bg_object) || (o == zone->bg_event_object) ||
            (o == zone->bg_clip_object) || (o == zone->prev_bg_object) ||
            (o == zone->transition_object))
          return EINA_TRUE;
     }
   return EINA_FALSE;
}

static Eina_Bool
_tiler_obj_visible(Evas_Object *o)
{
   Evas_Object *o2;

   if (!evas_object_visible_get(o)) return EINA_FALSE;
   o2 = evas_object_clip_get(o);
   if (!o2) return EINA_TRUE;
   return _tiler_obj_visible(o2);
}

static void
_tiler_add_input_sub(Evas_Object *par, Eina_Tiler *tb)
{
   Eina_List *objs = evas_object_smart_members_get(par);
   Evas_Coord x, y, w, h;
   Evas_Object *o;
   const char *type;
   void *sd;

   EINA_LIST_FREE(objs, o)
     {
        if (evas_object_clipees_has(o)) continue;
        if ((_tiler_obj_visible(o)) &&
            (!evas_object_pass_events_get(o)))
          {
             type = evas_object_type_get(o);

             if (type)
               {
                  if (!strcmp(type, "rectangle"))
                    {
                       if (!evas_object_clipees_has(o))
                         {
                            evas_object_geometry_get(o, &x, &y, &w, &h);
                            eina_tiler_rect_add(tb, &(Eina_Rectangle){x, y, w, h});
                         }
                    }
                  else if ((!strcmp(type, "image")) ||
                           (!strcmp(type, "text")) ||
                           (!strcmp(type, "textblock"))
                           )
                    {
                       const char *name = evas_object_name_get(o);

                       evas_object_geometry_get(o, &x, &y, &w, &h);
                       if ((name) && (!strcmp(name, "cw->obj")))
                         eina_tiler_rect_del(tb, &(Eina_Rectangle){x, y, w, h});
                       else
                         eina_tiler_rect_add(tb, &(Eina_Rectangle){x, y, w, h});
                    }
                  else if ((!strcmp(type, "edje")) ||
                           (!strcmp(type, "e_zoomap")))
                    {
                       sd = evas_object_smart_data_get(o);
                       if (sd) _tiler_add_input_sub(o, tb);
                    }
               }
          }
     }
}

static void
_e_comp_shapes_update_comp_client_shape_comp_helper(E_Client *ec, Eina_Tiler *tb, Eina_List **rl)
{
   int x, y, w, h;

   /* ignore deleted shapes */
   if (e_object_is_del(E_OBJECT(ec)))
     {
        SHAPE_INF("IGNORING DELETED: %p", ec);
        return;
     }
   if ((!ec->visible) || (ec->hidden) || (!evas_object_visible_get(ec->frame)) || evas_object_pass_events_get(ec->frame))
     {
        SHAPE_DBG("SKIPPING SHAPE FOR %p", ec);
        return;
     }

   SHAPE_INF("COMP EC: %p", ec);

   if (ec->shaped || ec->shaped_input)
     {
        int num, tot;
        int l, r, t, b;
        Eina_Rectangle *rect, *rects;

        /* add the frame */
        e_comp_object_frame_geometry_get(ec->frame, &l, &r, &t, &b);
        e_comp_object_frame_extends_get(ec->frame, &x, &y, &w, &h);
        if ((l + x) || (r + (w - ec->w + x)) || (t - y) || (b + (h - ec->h + y)))
          {
             if (t - y)
               {
                  eina_tiler_rect_add(tb, &(Eina_Rectangle){ec->x + x, ec->y + y, w, t - y});
                  SHAPE_INF("ADD: %d,%d@%dx%d", ec->x + x, ec->y + y, w, t - y);
               }
             if (l - x)
               {
                  eina_tiler_rect_add(tb, &(Eina_Rectangle){ec->x + x, ec->y + y, l - x, h});
                  SHAPE_INF("ADD: %d,%d@%dx%d", ec->x + x, ec->y + y, l - x, h);
               }
             if (r + (w - ec->w + x))
               {
                  eina_tiler_rect_add(tb, &(Eina_Rectangle){ec->x + l + ec->client.w + x, ec->y + y, r + (w - ec->w + x), h});
                  SHAPE_INF("ADD: %d,%d@%dx%d", ec->x + l + ec->client.w + x, ec->y + y, r + (w - ec->w + x), h);
               }
             if (b + (h - ec->h + y))
               {
                  eina_tiler_rect_add(tb, &(Eina_Rectangle){ec->x + x, ec->y + t + ec->client.h + y, w, b + (h - ec->h + y)});
                  SHAPE_INF("ADD: %d,%d@%dx%d", ec->x + x, ec->y + t + ec->client.h + y, w, b + (h - ec->h + y));
               }
          }
        rects = ec->shape_rects ?: ec->shape_input_rects;
        tot = ec->shape_rects_num ?: ec->shape_input_rects_num;
        for (num = 0, rect = rects; num < tot; num++, rect++)
          {
             x = rect->x, y = rect->y, w = rect->w, h = rect->h;
             x += ec->client.x, y += ec->client.y;
             E_RECTS_CLIP_TO_RECT(x, y, w, h, 0, 0, e_comp->w, e_comp->h);
             if ((w < 1) || (h < 1)) continue;
   //#ifdef SHAPE_DEBUG not sure we can shape check these?
             //r = E_NEW(Eina_Rectangle, 1);
             //EINA_RECTANGLE_SET(r, x, y, w, h);
             //rl = eina_list_append(rl, r);
   //#endif
             eina_tiler_rect_del(tb, &(Eina_Rectangle){x, y, w, h});
             SHAPE_INF("DEL: %d,%d@%dx%d", x, y, w, h);
          }
        _tiler_add_input_sub(ec->frame, tb);
        return;
     }

   if (shape_debug)
     {
        Eina_Rectangle *r;

        r = E_NEW(Eina_Rectangle, 1);
        EINA_RECTANGLE_SET(r, ec->client.x, ec->client.y, ec->client.w, ec->client.h);
        *rl = eina_list_append(*rl, r);
     }

   if (!e_client_util_borderless(ec))
     {
        e_comp_object_frame_extends_get(ec->frame, &x, &y, &w, &h);
        /* add the frame */
        eina_tiler_rect_add(tb, &(Eina_Rectangle){ec->x + x, ec->y + y, w, h});
        SHAPE_INF("ADD: %d,%d@%dx%d", ec->x + x, ec->y + y, w, h);
     }

   if ((!ec->shaded) && (!ec->shading))
     {
        /* delete the client if not shaded */
        eina_tiler_rect_del(tb, &(Eina_Rectangle){ec->client.x, ec->client.y, ec->client.w, ec->client.h});
        SHAPE_INF("DEL: %d,%d@%dx%d", ec->client.x, ec->client.y, ec->client.w, ec->client.h);
     }

   _tiler_add_input_sub(ec->frame, tb);
}

static void
_e_comp_shapes_update_object_shape_comp_helper(Evas_Object *o, Eina_Tiler *tb)
{
   int x, y, w, h;

   /* ignore hidden and pass-event objects */
   if ((!evas_object_visible_get(o)) || evas_object_pass_events_get(o)) return;
   if (evas_object_repeat_events_get(o) && (!evas_object_data_get(o, "comp_repeat"))) return;
   /* ignore canvas objects */
   if (_e_comp_shapes_update_object_checker_function_thingy(o)) return;
   SHAPE_INF("OBJ: %p:%s", o, evas_object_name_get(o) ?: evas_object_type_get(o));
   evas_object_geometry_get(o, &x, &y, &w, &h);
   if ((!w) && (!h) && elm_object_widget_check(o))
     {
        Evas_Object *content = elm_object_content_get(o);
        if (content)
          evas_object_geometry_get(content, &x, &y, &w, &h);
     }
   eina_tiler_rect_add(tb, &(Eina_Rectangle){x, y, w, h});
   SHAPE_INF("ADD: %d,%d@%dx%d", x, y, w, h);
}

static void
_e_comp_shapes_update_job(void *d EINA_UNUSED)
{
   Eina_Tiler *tb;
   E_Client *ec;
   Evas_Object *o = NULL;
   Eina_Rectangle *tr;
   Eina_Iterator *ti;
   Eina_Rectangle *exr;
   unsigned int i, tile_count;
   Ecore_Window win;
   Eina_Rectangle *r;
   Eina_List *rl = NULL;
   E_Color color = {0};
   const char *type;

   SHAPE_INF("---------------------");

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     win = e_comp->win;
   else
     win = e_comp->cm_selection;
   E_FREE_LIST(e_comp->debug_rects, evas_object_del);
   tb = eina_tiler_new(e_comp->w, e_comp->h);
   eina_tiler_tile_size_set(tb, 1, 1);
   /* background */
   eina_tiler_rect_add(tb, &(Eina_Rectangle){0, 0, e_comp->w, e_comp->h});

   ec = e_client_bottom_get();
   if (ec) o = ec->frame;
   for (; o; o = evas_object_above_get(o))
     {
        int layer;

        layer = evas_object_layer_get(o);
        if (e_comp_canvas_client_layer_map(layer) == 9999) //not a client layer
          {
             _e_comp_shapes_update_object_shape_comp_helper(o, tb);
             continue;
          }
        ec = NULL;
        type = evas_object_type_get(o);
        if ((type) && (!strcmp(type, "e_comp_object")))
          ec = e_comp_object_client_get(o);
        if (ec && (!ec->no_shape_cut))
          _e_comp_shapes_update_comp_client_shape_comp_helper(ec, tb
                                                              ,&rl
                                                             );

        else
          _e_comp_shapes_update_object_shape_comp_helper(o, tb);
     }

   ti = eina_tiler_iterator_new(tb);
   tile_count = 128;
   exr = malloc(sizeof(Eina_Rectangle) * tile_count);
   i = 0;
   EINA_ITERATOR_FOREACH(ti, tr)
     {
        exr[i++] = *((Eina_Rectangle *)tr);
        if (i == tile_count - 1)
          exr = realloc(exr, sizeof(Eina_Rectangle) * (tile_count *= 2));
        if (shape_debug)
          {
             Eina_List *l;

             _e_comp_shape_debug_rect(&exr[i - 1], &color);
             SHAPE_INF("%d,%d @ %dx%d", exr[i - 1].x, exr[i - 1].y, exr[i - 1].w, exr[i - 1].h);
             EINA_LIST_FOREACH(rl, l, r)
               {
                  if (E_INTERSECTS(r->x, r->y, r->w, r->h, tr->x, tr->y, tr->w, tr->h))
                    SHAPE_ERR("POSSIBLE RECT FAIL!!!!");
               }
          }
     }

#ifndef HAVE_WAYLAND_ONLY
   ecore_x_window_shape_input_rectangles_set(win, (Ecore_X_Rectangle*)exr, i);
#endif

   if (shape_debug)
     {
        E_FREE_LIST(rl, free);
        printf("\n");
     }
   free(exr);
   eina_iterator_free(ti);
   eina_tiler_free(tb);
   e_comp->shape_job = NULL;
}

//////////////////////////////////////////////////////////////////////////


static Eina_Bool
_e_comp_key_down(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Event_Key *ev)
{
   if ((!strcasecmp(ev->key, "f")) &&
       (ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT) &&
       (ev->modifiers & ECORE_EVENT_MODIFIER_CTRL) &&
       (ev->modifiers & ECORE_EVENT_MODIFIER_ALT))
     {
        e_comp_canvas_fps_toggle();
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_signal_user(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_Event_Signal_User *ev)
{
   siginfo_t sig;

   sig = ev->data;
   /* anything sent via 'kill' will set this code to SI_USER */
   if (sig.si_code != SI_USER) return ECORE_CALLBACK_PASS_ON;

   if (ev->number == 1)
     {
        // e uses this to pop up config panel
     }
   else if (ev->number == 2)
     {
        e_comp_canvas_fps_toggle();
     }
   return ECORE_CALLBACK_PASS_ON;
}

//////////////////////////////////////////////////////////////////////////

static void
_e_comp_free(E_Comp *c)
{
   E_FREE_LIST(c->zones, e_object_del);

   e_comp_canvas_clear();

   e_randr2_shutdown();
#ifdef HAVE_WAYLAND
   if (c->comp_type == E_PIXMAP_TYPE_WL)
     e_comp_wl_shutdown();
#endif
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_x())
     e_comp_x_shutdown();
#endif

   ecore_evas_free(c->ee);

   if (c->update_job) ecore_job_del(c->update_job);
   if (c->nocomp_delay_timer) ecore_timer_del(c->nocomp_delay_timer);
   if (c->nocomp_override_timer) ecore_timer_del(c->nocomp_override_timer);
   ecore_job_del(c->shape_job);
   free(c->canvas);
   free(c);
}

//////////////////////////////////////////////////////////////////////////

static Eina_Bool
_e_comp_object_add(void *d EINA_UNUSED, int t EINA_UNUSED, E_Event_Comp_Object *ev)
{
   if ((!e_comp->nocomp) || (!e_comp->nocomp_ec)) return ECORE_CALLBACK_RENEW;
   if (evas_object_layer_get(ev->comp_object) > MAX(e_comp->nocomp_ec->saved.layer, E_LAYER_CLIENT_NORMAL))
     _e_comp_nocomp_end();
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_override_expire(void *data EINA_UNUSED)
{
   e_comp->nocomp_override_timer = NULL;
   e_comp->nocomp_override--;

   if (e_comp->nocomp_override <= 0)
     {
        e_comp->nocomp_override = 0;
        if (e_comp->nocomp_want) _e_comp_cb_nocomp_begin();
     }
   return EINA_FALSE;
}

//////////////////////////////////////////////////////////////////////////

static Eina_Bool
_e_comp_screensaver_on(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Zone *zone;

   if (e_comp->saver) return ECORE_CALLBACK_RENEW;
   e_comp_override_add();
   e_comp->saver = EINA_TRUE;
   // XXX: this is not quite right - need to wait for signals from theme
   // before freezing render animator
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        e_zone_fade_handle(zone, 1, 3.0);
        edje_object_signal_emit(zone->base, "e,state,screensaver,on", "e");
        edje_object_signal_emit(zone->over, "e,state,screensaver,on", "e");
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_screensaver_off(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Zone *zone;
   E_Client *ec;

   if (!e_comp->saver) return ECORE_CALLBACK_RENEW;
   e_comp_override_del();
   e_comp->saver = EINA_FALSE;
   if (!e_comp->nocomp)
     ecore_evas_manual_render_set(e_comp->ee, EINA_FALSE);
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        edje_object_signal_emit(zone->base, "e,state,screensaver,off", "e");
        edje_object_signal_emit(zone->over, "e,state,screensaver,off", "e");
        e_zone_fade_handle(zone, 0, 0.5);
     }
   E_CLIENT_FOREACH(ec)
     if (e_comp_object_damage_exists(ec->frame))
       e_comp_object_render_update_add(ec->frame);

   return ECORE_CALLBACK_PASS_ON;
}

static Evas_Object *
_e_comp_act_opacity_obj_finder(E_Object *obj)
{
   E_Client *ec;

   switch (obj->type)
     {
      case E_CLIENT_TYPE:
        return ((E_Client *)(void *)obj)->frame;
      case E_ZONE_TYPE:
      case E_COMP_TYPE:
      case E_MENU_TYPE:
        ec = e_client_focused_get();
        return ec ? ec->frame : NULL;
     }
   if (e_obj_is_win(obj))
     return e_win_client_get((void *)obj)->frame;
   ec = e_client_focused_get();
   return ec ? ec->frame : NULL;
}

static void
_e_comp_act_opacity_change_go(E_Object *obj, const char *params)
{
   int opacity, cur;
   Evas_Object *o;

   if ((!params) || (!params[0])) return;
   o = _e_comp_act_opacity_obj_finder(obj);
   if (!o) return;
   opacity = atoi(params);
   opacity = E_CLAMP(opacity, -255, 255);
   evas_object_color_get(o, NULL, NULL, NULL, &cur);
   opacity += cur;
   opacity = E_CLAMP(opacity, 0, 255);
   evas_object_color_set(o, opacity, opacity, opacity, opacity);
}

static void
_e_comp_act_opacity_set_go(E_Object * obj EINA_UNUSED, const char *params)
{
   int opacity;
   Evas_Object *o;

   if ((!params) || (!params[0])) return;
   o = _e_comp_act_opacity_obj_finder(obj);
   if (!o) return;
   opacity = atoi(params);
   opacity = E_CLAMP(opacity, 0, 255);
   evas_object_color_set(o, opacity, opacity, opacity, opacity);
}

static void
_e_comp_act_redirect_toggle_go(E_Object * obj EINA_UNUSED, const char *params EINA_UNUSED)
{
   E_Client *ec;

   ec = e_client_focused_get();
   if ((!ec) || (!e_pixmap_is_x(ec->pixmap)) || (ec == e_comp->nocomp_ec)) return;
   e_comp_client_redirect_toggle(ec);
}

//////////////////////////////////////////////////////////////////////////

static void
_e_comp_resize(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int w, h;

   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   if ((w == e_comp->w) && (h == e_comp->h)) return;
   e_randr2_screens_setup(w, h);
   e_comp_canvas_update();
}

static void
_e_comp_delete_request()
{
   e_sys_action_do(E_SYS_LOGOUT, NULL);
}

EINTERN Eina_Bool
e_comp_init(void)
{
   _e_comp_log_dom = eina_log_domain_register("e_comp", EINA_COLOR_YELLOW);

   shape_debug = !!getenv("E_SHAPE_DEBUG");

   E_EVENT_COMPOSITOR_UPDATE = ecore_event_type_new();
   E_EVENT_COMP_OBJECT_ADD = ecore_event_type_new();
   E_EVENT_COMPOSITOR_DISABLE = ecore_event_type_new();
   E_EVENT_COMPOSITOR_ENABLE = ecore_event_type_new();
   E_EVENT_COMPOSITOR_XWAYLAND_INIT = ecore_event_type_new();

   ignores = eina_hash_pointer_new(NULL);

   e_comp_cfdata_edd_init(&conf_edd, &conf_match_edd);
   conf = e_config_domain_load("e_comp", conf_edd);
   if (conf)
     {
        conf->max_unmapped_pixels = 32 * 1024;
        conf->keep_unmapped = 1;
     }
   else
     conf = e_comp_cfdata_config_new();

   // comp config versioning - add this in. over time add epochs etc. if
   // necessary, but for now a simple version number will do
   if (conf->version < E_COMP_VERSION)
     {
        switch (conf->version)
          {
           case 0:
             // going from version 0 we should disable grab for smoothness
             conf->grab = 0;
             /* fallthrough */
           default:
             break;
          }
        e_config_save_queue();
        conf->version = E_COMP_VERSION;
     }

   if (conf->no_dither)
     e_util_env_set("EVAS_GL_RENDER_DISABLE_DITHER", "1");
   else
     e_util_env_set("EVAS_GL_RENDER_DISABLE_DITHER", NULL);

   {
      E_Action *act;

      act = e_action_add("opacity_change");
      act->func.go = _e_comp_act_opacity_change_go;
      e_action_predef_name_set(N_("Compositor"),
                               N_("Change current window opacity"), "opacity_change",
                               NULL, "syntax: +/- the amount to change opacity by (>0 for more opaque)", 1);
      actions = eina_list_append(actions, act);
      act = e_action_add("opacity_set");
      act->func.go = _e_comp_act_opacity_set_go;
      e_action_predef_name_set(N_("Compositor"),
                               N_("Set current window opacity"), "opacity_set",
                               "255", "syntax: number between 0-255 to set for transparent-opaque", 1);
      actions = eina_list_append(actions, act);
      act = e_action_add("redirect_toggle");
      act->func.go = _e_comp_act_redirect_toggle_go;
      e_action_predef_name_set(N_("Compositor"),
                               N_("Toggle focused client's redirect state"), "redirect_toggle",
                               NULL, NULL, 0);
      actions = eina_list_append(actions, act);
   }

   e_comp_new();
   e_comp->comp_type = E_PIXMAP_TYPE_NONE;

   {
      const char *gl;

      gl = getenv("E_COMP_ENGINE");
      if (gl)
        {
           int val;

           val = strtol(gl, NULL, 10);
           if ((val == E_COMP_ENGINE_SW) || (val == E_COMP_ENGINE_GL))
             e_comp_config_get()->engine = val;
           else if (!strcmp(gl, "gl"))
             e_comp_config_get()->engine = E_COMP_ENGINE_GL;
           else if (!strcmp(gl, "sw"))
             e_comp_config_get()->engine = E_COMP_ENGINE_SW;
           else
             {
                fprintf(stderr,
                        "ERROR: E_COMP_ENGINE value '%s' invalid.\n"
                        "Please use 'gl' or 'sw'.\n"
                        "Valid values below that request a type of acceleration:\n"
                        "  'gl' : OpenGL based acceleration.\n"
                        "  'sw' : CPU based Software rendering.\n",
                        gl);
                exit(101);
             }
        }
   }
   {
      const char *eng;

      eng = getenv("E_WL_FORCE");
      if (eng)
        {
           if ((!strcmp(eng, "buffer")) ||
               (!strcmp(eng, "drm")) ||
               (!strcmp(eng, "wl")) ||
               (!strcmp(eng, "x11")))
             {
                char buf[128];

                snprintf(buf, sizeof(buf), "wl_%s", eng);
                e_xkb_init(E_PIXMAP_TYPE_WL);
                if (e_module_enable(e_module_new(buf)))
                  {
                     e_comp->comp_type = E_PIXMAP_TYPE_WL;
                     goto out;
                  }
             }
           else
             {
                fprintf(stderr,
                        "ERROR: E_WL_FORCE value '%s' invalid.\n"
                        "Please use 'buffer', 'drm', 'wl' or 'x11'\n"
                        "Valid values below that request a type of rendering output target:\n"
                        "  'buffer' : Invisible memory buffer. (Debugging/testing)\n"
                        "  'drm'    : DRM/KMS framebuffer. (You want this as a normal full-screen stand-alone Wayland compositor)\n"
                        "  'wl'     : Window as a Wayland application. (Compositor in a Window for debugging/development/testing)\n"
                        "  'x11'    : Window as a X11 application. (Compositor in a Window for debugging/development/testing)\n",
                        eng);
                exit(101);
             }
        }
   }

#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_x_init())
     {
        e_comp->comp_type = E_PIXMAP_TYPE_X;
        e_xkb_init(E_PIXMAP_TYPE_X);
     }
   else
#endif
     {
        const char **test, *eng[] =
        {
#ifdef USE_MODULE_WL_WL
           "wl_wl",
#endif
#ifdef USE_MODULE_WL_X11
           "wl_x11",
#endif
#ifdef USE_MODULE_WL_DRM
           "wl_drm",
#endif
/* probably add other engines here; fb should be last? */
#ifdef USE_MODULE_WL_FB
           "wl_fb",
#endif
           NULL
        };

        e_xkb_init(E_PIXMAP_TYPE_WL);
        e_util_env_set("HYBRIS_EGLPLATFORM", "wayland");
        for (test = eng; *test; test++)
          {
             if (e_module_enable(e_module_new(*test)))
               {
                  e_comp->comp_type = E_PIXMAP_TYPE_WL;
                  goto out;
               }
          }
        return EINA_FALSE;
     }
//#ifdef HAVE_WAYLAND_CLIENTS
   //e_comp_wl_init();
//#endif
   if (e_comp->comp_type == E_PIXMAP_TYPE_NONE) return EINA_FALSE;
out:
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        e_comp_canvas_fake_layers_init();
        e_screensaver_update();
     }
   e_comp->elm = elm_win_fake_add(e_comp->ee);
   evas_object_event_callback_add(e_comp->elm, EVAS_CALLBACK_RESIZE, _e_comp_resize, NULL);
   elm_win_fullscreen_set(e_comp->elm, 1);
   elm_win_autodel_set(e_comp->elm, 1);
   if (!e_comp->screen)
     evas_object_smart_callback_add(e_comp->elm, "delete,request", _e_comp_delete_request, NULL);
   ecore_evas_focus_set(e_comp->ee, 0);
   ecore_evas_focus_set(e_comp->ee, 1);
   evas_object_show(e_comp->elm);
   e_util_env_set("HYBRIS_EGLPLATFORM", NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_ON, _e_comp_screensaver_on, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_OFF, _e_comp_screensaver_off, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_KEY_DOWN, _e_comp_key_down, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_SIGNAL_USER, _e_comp_signal_user, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_COMP_OBJECT_ADD, _e_comp_object_add, NULL);

   return EINA_TRUE;
}


static Eina_Bool
_style_demo(void *data)
{
   Eina_List *style_shadows, *l;
   int demo_state;
   const E_Comp_Demo_Style_Item *it;

   demo_state = (long)evas_object_data_get(data, "style_demo_state");
   demo_state = (demo_state + 1) % 4;
   evas_object_data_set(data, "style_demo_state", (void *)(long)demo_state);

   style_shadows = evas_object_data_get(data, "style_shadows");
   EINA_LIST_FOREACH(style_shadows, l, it)
     {
        Evas_Object *ob = it->preview;
        Evas_Object *of = it->frame;

        switch (demo_state)
          {
           case 0:
             edje_object_signal_emit(ob, "e,state,visible", "e");
             edje_object_signal_emit(ob, "e,state,focused", "e");
             edje_object_part_text_set(of, "e.text.label", _("Visible"));
             break;

           case 1:
             edje_object_signal_emit(ob, "e,state,unfocused", "e");
             edje_object_part_text_set(of, "e.text.label", _("Focus-Out"));
             break;

           case 2:
             edje_object_signal_emit(ob, "e,state,focused", "e");
             edje_object_part_text_set(of, "e.text.label", _("Focus-In"));
             break;

           case 3:
             edje_object_signal_emit(ob, "e,state,hidden", "e");
             edje_object_part_text_set(of, "e.text.label", _("Hidden"));
             break;

           default:
             break;
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_style_selector_del(void *data       EINA_UNUSED,
                    Evas *e,
                    Evas_Object *o,
                    void *event_info EINA_UNUSED)
{
   Eina_List *style_shadows, *style_list;
   Ecore_Timer *timer;
   Evas_Object *orec0;

   orec0 = evas_object_name_find(e, "style_shadows");
   style_list = evas_object_data_get(orec0, "list");

   style_shadows = evas_object_data_get(o, "style_shadows");
   if (style_shadows)
     {
        E_Comp_Demo_Style_Item *ds_it;

        EINA_LIST_FREE(style_shadows, ds_it)
          {
             style_list = eina_list_remove(style_list, ds_it);

             evas_object_del(ds_it->frame);
             evas_object_del(ds_it->livethumb);
             free(ds_it);
          }
        evas_object_data_set(o, "style_shadows", NULL);
     }

   timer = evas_object_data_get(o, "style_timer");
   if (timer)
     {
        ecore_timer_del(timer);
        evas_object_data_set(o, "style_timer", NULL);
     }

   evas_object_data_set(orec0, "list", style_list);
}

EINTERN Evas_Object *
e_comp_style_selector_create(Evas *evas, const char **source)
{
   Evas_Object *oi, *ob, *oo, *obd, *orec, *oly, *orec0;
   Eina_List *styles, *l, *style_shadows = NULL, *style_list;
   char *style;
   const char *str;
   int n, sel;
   Evas_Coord wmw, wmh;
   Ecore_Timer *timer;

   orec0 = evas_object_name_find(evas, "style_shadows");
   style_list = evas_object_data_get(orec0, "list");
   oi = e_widget_ilist_add(evas, 80, 80, source);
   evas_object_event_callback_add(oi, EVAS_CALLBACK_DEL,
                                  _style_selector_del, oi);
   sel = 0;
   styles = e_theme_comp_frame_list();
   n = 0;
   EINA_LIST_FOREACH(styles, l, style)
     {
        E_Comp_Demo_Style_Item *ds_it;
        char buf[4096];

        ds_it = malloc(sizeof(E_Comp_Demo_Style_Item));

        ob = e_livethumb_add(evas);
        ds_it->livethumb = ob;
        e_livethumb_vsize_set(ob, 240, 240);

        oly = e_layout_add(e_livethumb_evas_get(ob));
        ds_it->layout = ob;
        e_layout_virtual_size_set(oly, 240, 240);
        e_livethumb_thumb_set(ob, oly);
        evas_object_show(oly);

        oo = edje_object_add(e_livethumb_evas_get(ob));
        ds_it->preview = oo;
        snprintf(buf, sizeof(buf), "e/comp/frame/%s", style);
        e_theme_edje_object_set(oo, "base/theme/borders", buf);
        e_layout_pack(oly, oo);
        e_layout_child_move(oo, 39, 39);
        e_layout_child_resize(oo, 162, 162);
        edje_object_signal_emit(oo, "e,state,visible", "e");
        edje_object_signal_emit(oo, "e,state,focused", "e");
        evas_object_show(oo);

        ds_it->frame = edje_object_add(evas);
        e_theme_edje_object_set
          (ds_it->frame, "base/theme/comp", "e/comp/preview");
        edje_object_part_swallow(ds_it->frame, "e.swallow.preview", ob);
        evas_object_show(ds_it->frame);
        style_shadows = eina_list_append(style_shadows, ds_it);

        obd = edje_object_add(e_livethumb_evas_get(ob));
        ds_it->border = obd;
        e_theme_edje_object_set(obd, "base/theme/borders",
                                "e/widgets/border/default/border");
        edje_object_part_text_set(obd, "e.text.title", _("Title"));
        edje_object_signal_emit(obd, "e,state,shadow,on", "e");
        edje_object_part_swallow(oo, "e.swallow.content", obd);
        evas_object_show(obd);

        orec = evas_object_rectangle_add(e_livethumb_evas_get(ob));
        ds_it->client = orec;
        evas_object_color_set(orec, 0, 0, 0, 128);
        edje_object_part_swallow(obd, "e.swallow.client", orec);
        evas_object_show(orec);

        e_widget_ilist_append(oi, ds_it->frame, style, NULL, NULL, style);
        evas_object_show(ob);
        if (*source)
          {
             if (!strcmp(*source, style)) sel = n;
          }
        n++;

        style_list = eina_list_append(style_list, ds_it);
     }
   evas_object_data_set(orec0, "list", style_list);
   evas_object_data_set(oi, "style_shadows", style_shadows);
   timer = ecore_timer_loop_add(3.0, _style_demo, oi);
   evas_object_data_set(oi, "style_timer", timer);
   evas_object_data_set(oi, "style_demo_state", (void *)1);
   e_widget_size_min_get(oi, &wmw, &wmh);
   e_widget_size_min_set(oi, 160, 100);
   e_widget_ilist_selected_set(oi, sel);
   e_widget_ilist_go(oi);

   EINA_LIST_FREE(styles, str)
     eina_stringshare_del(str);

   return oi;
}

E_API E_Comp *
e_comp_new(void)
{
   if (e_comp)
     CRI("CANNOT REPLACE EXISTING COMPOSITOR");
   e_comp = E_OBJECT_ALLOC(E_Comp, E_COMP_TYPE, _e_comp_free);
   if (!e_comp) return NULL;
   e_comp->canvas = E_NEW(E_Comp_Canvas, 1);
   return e_comp;
}

E_API int
e_comp_internal_save(void)
{
   return e_config_domain_save("e_comp", conf_edd, conf);
}

EINTERN int
e_comp_shutdown(void)
{
   Eina_List *l, *ll;
   E_Client *ec;

   E_FREE_FUNC(action_timeout, ecore_timer_del);
   EINA_LIST_FOREACH_SAFE(e_comp->clients, l, ll, ec)
     {
        DELD(ec, 99999);
        e_object_del(E_OBJECT(ec));
     }

   e_object_del(E_OBJECT(e_comp));
   e_comp = NULL;
   E_FREE_LIST(handlers, ecore_event_handler_del);
   E_FREE_LIST(actions, e_object_del);
   E_FREE_LIST(hooks, e_client_hook_del);

   gl_avail = EINA_FALSE;
   e_comp_cfdata_config_free(conf);
   E_CONFIG_DD_FREE(conf_match_edd);
   E_CONFIG_DD_FREE(conf_edd);
   conf = NULL;
   conf_match_edd = NULL;
   conf_edd = NULL;

   E_FREE_FUNC(ignores, eina_hash_free);

   return 1;
}

E_API void
e_comp_render_queue(void)
{
   if (e_comp->update_job)
     {
        DBG("UPDATE JOB DEL...");
        E_FREE_FUNC(e_comp->update_job, ecore_job_del);
     }
   DBG("UPDATE JOB ADD...");
   e_comp->update_job = ecore_job_add(_e_comp_cb_job, e_comp);
}

E_API void
e_comp_client_post_update_add(E_Client *ec)
{
   if (ec->on_post_updates) return;
   ec->on_post_updates = EINA_TRUE;
   e_comp->post_updates = eina_list_append(e_comp->post_updates, ec);
   REFD(ec, 111);
   e_object_ref(E_OBJECT(ec));
}

E_API void
e_comp_shape_queue(void)
{
   if ((e_comp->comp_type != E_PIXMAP_TYPE_X) && (!e_comp_util_has_x())) return;
   if (!e_comp->shape_job)
     e_comp->shape_job = ecore_job_add(_e_comp_shapes_update_job, NULL);
}

E_API void
e_comp_shape_queue_block(Eina_Bool block)
{
   e_comp->shape_queue_blocked = !!block;
   if (block)
     E_FREE_FUNC(e_comp->shape_job, ecore_job_del);
   else
     e_comp_shape_queue();
}

E_API E_Comp_Config *
e_comp_config_get(void)
{
   return conf;
}

E_API void
e_comp_shadows_reset(void)
{
   E_Client *ec;

   _e_comp_fps_update();
   E_LIST_FOREACH(e_comp->zones, e_comp_canvas_zone_update);
   E_CLIENT_FOREACH(ec)
     e_comp_object_frame_theme_set(ec->frame, E_COMP_OBJECT_FRAME_RESHADOW);
}

E_API Ecore_Window
e_comp_top_window_at_xy_get(Evas_Coord x, Evas_Coord y)
{
   E_Client *ec;
   Evas_Object *o;

   EINA_SAFETY_ON_NULL_RETURN_VAL(e_comp, 0);
   o = evas_object_top_at_xy_get(e_comp->evas, x, y, 0, 0);
   if (!o) return e_comp->ee_win;
   ec = evas_object_data_get(o, "E_Client");
   if (ec) return e_client_util_pwin_get(ec);
   return e_comp->ee_win;
}

E_API void
e_comp_util_wins_print(void)
{
   Evas_Object *o;

   o = evas_object_top_get(e_comp->evas);
   while (o)
     {
        E_Client *ec;
        int x, y, w, h;

        ec = evas_object_data_get(o, "E_Client");
        evas_object_geometry_get(o, &x, &y, &w, &h);
        fprintf(stderr, "LAYER %d  ", evas_object_layer_get(o));
        if (ec)
          fprintf(stderr, "EC%s%s:  %p - '%s:%s' || %d,%d @ %dx%d\n",
                  ec->override ? "O" : "", ec->focused ? "*" : "", ec,
                  e_client_util_name_get(ec) ?: ec->icccm.name, ec->icccm.class, x, y, w, h);
        else
          fprintf(stderr, "OBJ: %p - %s || %d,%d @ %dx%d\n", o, evas_object_name_get(o), x, y, w, h);
        o = evas_object_below_get(o);
     }
   fputc('\n', stderr);
}

E_API void
e_comp_ignore_win_add(E_Pixmap_Type type, Ecore_Window win)
{
   E_Client *ec;

   eina_hash_add(ignores, &win, (void*)1);
   ec = e_pixmap_find_client(type, win);
   if (!ec) return;
   ec->ignored = 1;
   if (ec->visible) evas_object_hide(ec->frame);
}

E_API void
e_comp_ignore_win_del(E_Pixmap_Type type, Ecore_Window win)
{
   E_Client *ec;

   eina_hash_del_by_key(ignores, &win);
   ec = e_pixmap_find_client(type, win);
   if ((!ec) || (e_object_is_del(E_OBJECT(ec)))) return;
   ec->ignored = 0;
   if (ec->visible) evas_object_show(ec->frame);
}

E_API Eina_Bool
e_comp_ignore_win_find(Ecore_Window win)
{
   return !!eina_hash_find(ignores, &win);
}

E_API void
e_comp_override_del(void)
{
   e_comp->nocomp_override--;
   if (e_comp->nocomp_override <= 0)
     {
        e_comp->nocomp_override = 0;
        if (e_comp->nocomp_want) _e_comp_cb_nocomp_begin();
     }
}

E_API void
e_comp_override_add(void)
{
   e_comp->nocomp_override++;
   if ((e_comp->nocomp_override > 0) && (e_comp->nocomp)) _e_comp_nocomp_end();
}

E_API E_Comp *
e_comp_find_by_window(Ecore_Window win)
{
   if ((e_comp->win == win) || (e_comp->ee_win == win) || (e_comp->root == win)) return e_comp;
   return NULL;
}

E_API void
e_comp_override_timed_pop(void)
{
   if (e_comp->nocomp_override <= 0) return;
   if (e_comp->nocomp_override_timer)
     e_comp->nocomp_override--;
   else
     e_comp->nocomp_override_timer = ecore_timer_loop_add(1.0, _e_comp_override_expire, NULL);
}

E_API unsigned int
e_comp_e_object_layer_get(const E_Object *obj)
{
   E_Gadcon *gc = NULL;

   if (!obj) return 0;

   switch (obj->type)
     {
      case E_GADCON_CLIENT_TYPE:
        gc = ((E_Gadcon_Client *)(void *)(obj))->gadcon;
        EINA_SAFETY_ON_NULL_RETURN_VAL(gc, 0);
        EINA_FALLTHROUGH;
        /* no break */

      case E_GADCON_TYPE:
        if (!gc) gc = (E_Gadcon *)obj;
        if (gc->shelf) return gc->shelf->layer;
        if (!gc->toolbar) return E_LAYER_DESKTOP;
        return e_win_client_get(gc->toolbar->fwin)->layer;

      case E_ZONE_TYPE:
        return E_LAYER_DESKTOP;

      case E_CLIENT_TYPE:
        return ((E_Client *)(void *)(obj))->layer;

      /* FIXME: add more types as needed */
      default:
        break;
     }
   if (e_obj_is_win(obj))
     return e_win_client_get((void*)obj)->layer;
   return 0;
}

E_API Eina_Bool
e_comp_grab_input(Eina_Bool mouse, Eina_Bool kbd)
{
   Eina_Bool ret = EINA_FALSE;
   Ecore_Window mwin = 0, kwin = 0;

   mouse = !!mouse;
   kbd = !!kbd;
   if (mouse || e_comp->input_mouse_grabs)
     mwin = e_comp->ee_win;
   if (kbd || e_comp->input_mouse_grabs)
     kwin = e_comp->ee_win;
   e_comp_override_add();
   if ((e_comp->input_mouse_grabs && e_comp->input_key_grabs) ||
       e_grabinput_get(mwin, 0, kwin))
     {
        E_Client *ec = e_client_focused_get();

        if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
          {
             if (ec)
               evas_object_focus_set(ec->frame, 0);

#ifdef HAVE_WAYLAND
             e_comp_wl_extension_pointer_unconstrain(NULL);
#endif
          }

        ret = EINA_TRUE;
        e_comp->input_mouse_grabs += mouse;
        e_comp->input_key_grabs += kbd;

        if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
          {
             if (ec && (!e_object_is_del(E_OBJECT(ec))))
               evas_object_focus_set(ec->frame, 1);
          }
     }
   return ret;
}

E_API void
e_comp_ungrab_input(Eina_Bool mouse, Eina_Bool kbd)
{
   Ecore_Window mwin = 0, kwin = 0;

   mouse = !!mouse;
   kbd = !!kbd;
   if (e_comp->input_mouse_grabs)
     e_comp->input_mouse_grabs -= mouse;
   if (e_comp->input_key_grabs)
     e_comp->input_key_grabs -= kbd;
   if (mouse && (!e_comp->input_mouse_grabs))
     mwin = e_comp->ee_win;
   if (kbd && (!e_comp->input_key_grabs))
     kwin = e_comp->ee_win;
   e_comp_override_timed_pop();
   if ((!mwin) && (!kwin)) return;
   e_grabinput_release(mwin, kwin);
   evas_event_feed_mouse_out(e_comp->evas, 0, NULL);
   evas_event_feed_mouse_in(e_comp->evas, 0, NULL);
   if (e_client_focused_get())
     {
        E_Client *ec = e_client_focused_get();

        if ((!e_object_is_del(E_OBJECT(ec))) && (e_comp->comp_type == E_PIXMAP_TYPE_WL))
          {
             Eina_Bool mouse_in = ec->mouse.in;
             int x, y;

             x = ec->mouse.current.mx;
             y = ec->mouse.current.my;
             evas_object_focus_set(ec->frame, 0);
             evas_object_focus_set(ec->frame, 1);
             if (mouse_in)
               e_client_mouse_in(ec, x, y);
          }
        return;
     }
   if (e_config->focus_policy != E_FOCUS_MOUSE)
     e_client_refocus();
}

E_API Eina_Bool
e_comp_util_kbd_grabbed(void)
{
   return e_menu_is_active() || e_client_action_get() || e_grabinput_key_win_get();
}

E_API Eina_Bool
e_comp_util_mouse_grabbed(void)
{
   return e_menu_is_active() || e_client_action_get() || e_grabinput_mouse_win_get();
}

E_API void
e_comp_gl_set(Eina_Bool set)
{
   gl_avail = !!set;
}

E_API Eina_Bool
e_comp_gl_get(void)
{
   return gl_avail;
}

E_API void
e_comp_button_bindings_ungrab_all(void)
{
   if (e_comp->bindings_ungrab_cb)
     e_comp->bindings_ungrab_cb();
}

E_API void
e_comp_button_bindings_grab_all(void)
{
   if (e_comp->bindings_grab_cb)
     e_comp->bindings_grab_cb();
}

E_API void
e_comp_client_redirect_toggle(E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(ec);
   if (!conf->enable_advanced_features) return;
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_X) return;
   ec->unredirected_single = !ec->unredirected_single;
   e_client_redirected_set(ec, !ec->redirected);
   ec->no_shape_cut = !ec->redirected;
   e_comp_shape_queue();
}

E_API Eina_Bool
e_comp_util_object_is_above_nocomp(Evas_Object *obj)
{
   Evas_Object *o;
   int cl, ol;

   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE);
   if (!evas_object_visible_get(obj)) return EINA_FALSE;
   if (!e_comp->nocomp_ec) return EINA_FALSE;
   cl = evas_object_layer_get(e_comp->nocomp_ec->frame);
   ol = evas_object_layer_get(obj);
   if (cl > ol) return EINA_FALSE;
   o = evas_object_above_get(e_comp->nocomp_ec->frame);
   if ((cl == ol) && (evas_object_layer_get(o) == cl))
     {
        do {
           if (o == obj)
             return EINA_TRUE;
           o = evas_object_above_get(o);
        } while (o && (evas_object_layer_get(o) == cl));
     }
   else
     return EINA_TRUE;
   return EINA_FALSE;
}

E_API void
e_comp_clients_rescale(void)
{
   int i;

   if (stopping) return;
   for (i = 0; i < 11; i++)
     {
        Eina_List *tmp = NULL;
        E_Client *ec;

        if (!e_comp->layers[i].clients) continue;
        /* Make temporary list as e_client_res_change_geometry_restore
         * rearranges the order. */
        EINA_INLIST_FOREACH(e_comp->layers[i].clients, ec)
          {
             if ((!e_client_util_ignored_get(ec)) && (!e_object_is_del(E_OBJECT(ec))))
               tmp = eina_list_append(tmp, ec);
          }

        EINA_LIST_FREE(tmp, ec)
          {
             ec->pre_res_change.valid = 0;
             if (ec->maximized || ec->fullscreen)
               e_client_rescale(ec);
          }
     }
}
#include "e.h"

static Eina_List *handlers;
static Ecore_Timer *timer_post_screensaver_lock = NULL;
static Ecore_Timer *timer_post_screensaver_on = NULL;
static Ecore_Timer *timer_pointer_freeze = NULL;

static void
_e_comp_canvas_cb_del()
{
   E_FREE_LIST(handlers, ecore_event_handler_del);
   E_FREE_FUNC(timer_post_screensaver_lock, ecore_timer_del);
   E_FREE_FUNC(timer_post_screensaver_on, ecore_timer_del);
   E_FREE_FUNC(timer_pointer_freeze, ecore_timer_del);
}

static void
_e_comp_canvas_event_compositor_resize_free(void *data EINA_UNUSED, void *event EINA_UNUSED)
{
   e_object_unref(E_OBJECT(e_comp));
}

///////////////////////////////////

static void
_e_comp_canvas_cb_first_frame(void *data EINA_UNUSED, Evas *e, void *event_info EINA_UNUSED)
{
   double now = ecore_time_get();

   switch (e_first_frame[0])
     {
      case 'A': abort();
      case 'E':
      case 'D': exit(101);
      case 'T': fprintf(stderr, "Startup time: '%f' - '%f' = '%f'\n", now, e_first_frame_start_time, now - e_first_frame_start_time);
         break;
     }

   evas_event_callback_del_full(e, EVAS_CALLBACK_RENDER_POST, _e_comp_canvas_cb_first_frame, NULL);
}

static Ecore_Job *_e_comp_update_job = NULL;

static void
_e_comp_canvas_fps_update_job(void *data EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   _e_comp_update_job = NULL;
   if (conf->fps_show) e_comp_fps_update();
}

static Ecore_Job *_e_comp_post_render_job = NULL;

static void
_e_comp_canvas_render_post_job(void *data EINA_UNUSED)
{
   E_Client *ec;

   _e_comp_post_render_job = NULL;
   EINA_LIST_FREE(e_comp->post_updates, ec)
     {
        //INF("POST %p", ec);
        ec->on_post_updates = EINA_FALSE;
        if (!e_object_is_del(E_OBJECT(ec)))
          e_pixmap_image_clear(ec->pixmap, 1);
        evas_object_smart_callback_call(ec->frame, "post_render", NULL);
        UNREFD(ec, 111);
        e_object_unref(E_OBJECT(ec));
     }
}

static void
_e_comp_canvas_render_track_pre(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_RENDER_BEGIN, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
}

static void
_e_comp_canvas_render_track_post(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_RENDER_END, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
}

static void
_e_comp_canvas_render_track_flush_pre(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_RENDER2_BEGIN, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
}

static void
_e_comp_canvas_render_track_flush_post(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_RENDER2_END, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
}

static void
_e_comp_canvas_render_post(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();

   // NOTE: don't modify objects here as we may lose updates - defer...
   if (conf->fps_show)
     {
        if (_e_comp_update_job) ecore_job_del(_e_comp_update_job);
        ecore_job_add(_e_comp_canvas_fps_update_job, NULL);
     }

   e_comp->rendering = EINA_FALSE;

   if (_e_comp_post_render_job) ecore_job_del(_e_comp_post_render_job);
   _e_comp_post_render_job = ecore_job_add(_e_comp_canvas_render_post_job, NULL);

   if (conf->grab && e_comp->grabbed)
     {
        if (e_comp->grab_cb) e_comp->grab_cb();
        e_comp->grabbed = 0;
     }
}

///////////////////////////////////

static void
_e_comp_canvas_cb_mouse_in(void *d EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec;

   e_comp_canvas_notidle();
   if (e_client_action_get() || e_grabinput_mouse_win_get()) return;
   ec = e_client_focused_get();
   if (ec && (!ec->border_menu)) e_focus_event_mouse_out(ec);
}

static void
_e_comp_canvas_cb_mouse_down(void *d EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   e_comp_canvas_notidle();
   if (e_client_action_get() || e_grabinput_mouse_win_get()) return;
   e_bindings_mouse_down_evas_event_handle(E_BINDING_CONTEXT_COMPOSITOR, E_OBJECT(e_comp), event_info);
}

static void
_e_comp_canvas_cb_mouse_up(void *d EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   e_comp_canvas_notidle();
   if (e_client_action_get() || e_grabinput_mouse_win_get()) return;
   e_bindings_mouse_up_evas_event_handle(E_BINDING_CONTEXT_COMPOSITOR, E_OBJECT(e_comp), event_info);
}

static void
_e_comp_canvas_cb_mouse_wheel(void *d EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   e_comp_canvas_notidle();
   if (e_client_action_get() || e_grabinput_mouse_win_get()) return;
   e_bindings_wheel_evas_event_handle(E_BINDING_CONTEXT_COMPOSITOR, E_OBJECT(e_comp), event_info);
}

static Eina_Bool
_key_down(int ctx, Ecore_Event_Key *ev)
{
   e_comp_canvas_notidle();
   if (e_desklock_state_get() && (ctx == E_BINDING_CONTEXT_MANAGER))
     {
        E_Desklock_Interface *iface = e_desklock_interface_current_get();
        if (iface && iface->key_down)
          {
#ifdef HAVE_WAYLAND
             if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
               e_comp_wl_key_down(ev, NULL);
#endif
             return iface->key_down(ev);
          }
     }
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root))
     {
        E_Client *ec;

        ec = e_client_focused_get();
        if (!ec) ec = e_client_action_get();
        /* *block actions when no client is focused (probably something else did a grab here so we'll play nice)
         * *block actions when menu is up
         * *block actions when event (grab) window isn't comp window
         * *other cases?
         */
        if ((!ec) || (ev->event_window != e_comp->ee_win)) return ECORE_CALLBACK_RENEW;
     }
   return ((!e_comp->screen) ||
           (!e_comp->screen->key_down) || (!e_comp->screen->key_down(ev))) &&
     !e_bindings_key_down_event_handle(ctx, E_OBJECT(e_comp), ev)
#ifdef HAVE_WAYLAND
       && !e_comp_wl_key_down(ev, NULL)
#endif
         ;
}

static Eina_Bool
_e_comp_canvas_cb_key_down(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_Event_Key *ev)
{
   return _key_down(E_BINDING_CONTEXT_COMPOSITOR, ev);
}

static Eina_Bool
_e_comp_cb_key_down(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_Event_Key *ev)
{
   return _key_down(E_BINDING_CONTEXT_MANAGER, ev);
}

static Eina_Bool
_key_up(int ctx, Ecore_Event_Key *ev)
{
   e_comp_canvas_notidle();
   if (e_desklock_state_get() && (ctx == E_BINDING_CONTEXT_MANAGER))
     {
        E_Desklock_Interface *iface = e_desklock_interface_current_get();
        if (iface && iface->key_up)
          {
#ifdef HAVE_WAYLAND
             if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
               e_comp_wl_key_up(ev, NULL);
#endif
             return iface->key_up(ev);
          }
     }
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root)) return ECORE_CALLBACK_PASS_ON;
   return ((!e_comp->screen) ||
           (!e_comp->screen->key_up) || (!e_comp->screen->key_up(ev))) &&
     !e_bindings_key_up_event_handle(ctx, E_OBJECT(e_comp), ev)
#ifdef HAVE_WAYLAND
       && !e_comp_wl_key_up(ev, NULL)
#endif
         ;
}

static Eina_Bool
_e_comp_canvas_cb_key_up(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_Event_Key *ev)
{
   return _key_up(E_BINDING_CONTEXT_COMPOSITOR, ev);
}

static Eina_Bool
_e_comp_cb_key_up(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_Event_Key *ev)
{
   return _key_up(E_BINDING_CONTEXT_MANAGER, ev);
}

static Eina_Bool
_e_comp_cb_mouse_up(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root))
     return ECORE_CALLBACK_PASS_ON;
   if (e_bindings_mouse_down_ecore_event_handle(E_BINDING_CONTEXT_MANAGER, E_OBJECT(e_comp), ev))
     return ECORE_CALLBACK_DONE;

#ifdef HAVE_WAYLAND
   return e_comp_wl_grab_client_mouse_button(ev);
#else
   return ECORE_CALLBACK_RENEW;
#endif
}

static Eina_Bool
_e_comp_cb_mouse_down(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root))
     return ECORE_CALLBACK_PASS_ON;
   if (e_bindings_mouse_down_ecore_event_handle(E_BINDING_CONTEXT_MANAGER, E_OBJECT(e_comp), ev))
     return ECORE_CALLBACK_DONE;
#ifdef HAVE_WAYLAND
   return e_comp_wl_grab_client_mouse_button(ev);
#else
   return ECORE_CALLBACK_RENEW;
#endif
}

static Eina_Bool
_e_comp_cb_mouse_wheel(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Wheel *ev)
{
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (ev->event_window != e_comp->root))
     return ECORE_CALLBACK_PASS_ON;
   return !e_bindings_wheel_ecore_event_handle(E_BINDING_CONTEXT_MANAGER, E_OBJECT(e_comp), ev);
}

#ifdef HAVE_WAYLAND
static Eina_Bool
_e_comp_cb_mouse_move(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Move *ev)
{
   static int x = -9999, y = -9999;

   if (e_comp->comp_type != E_PIXMAP_TYPE_WL) return ECORE_CALLBACK_RENEW;
   if (x == -9999)
     x = ev->x, y = ev->y;
   if ((x == ev->x) && (y == ev->y)) return ECORE_CALLBACK_RENEW;
   if (e_client_focused_get() && (!e_comp_util_mouse_grabbed()))
     {
        if ((!e_comp->screen) || (!e_comp->screen->relative_motion))
          {
             e_comp_wl_extension_relative_motion_event(ecore_time_unix_get() * 1000ULL,
               ev->x - x, ev->y - y, 0, 0);
          }
        x = ev->x, y = ev->y;
        if (e_comp_wl_extension_pointer_constraints_update(e_client_focused_get(), ev->x, ev->y))
          return ECORE_CALLBACK_CANCEL;
     }
   x = ev->x, y = ev->y;
   return e_comp_wl_grab_client_mouse_move(ev);
}
#endif

////////////////////////////////////

static Eina_Bool
_e_comp_cb_zone_change(void *d EINA_UNUSED, int type, void *event)
{
   if (type == E_EVENT_ZONE_ADD)
     {
        E_Event_Zone_Add *ev = event;
        E_Client *ec;

        E_CLIENT_FOREACH(ec)
          {
             if (ec->restore_zone_id)
               {
                  E_Zone *restore_zone =
                    e_zone_for_id_get(ec->restore_zone_id);
                  if ((restore_zone) && (ev->zone) &&
                      (restore_zone == ev->zone))
                    {
                       int dx, dy;
                       E_Desk *desk;

                       dx = dy = -1;
                       if (ec->desk)
                         {
                            dx = ec->desk->x;
                            dy = ec->desk->y;
                         }
                       e_client_zone_set(ec, restore_zone);
                       if ((dx >= 0) && (dy >= 0))
                         {
                            desk = e_desk_at_xy_get(restore_zone, dx, dy);
                            if (desk)
                              {
                                 e_client_desk_set(ec, desk);
                                 if (desk == e_desk_current_get(restore_zone))
                                   {
                                      if ((ec->visible) && (!ec->hidden) && (!ec->iconic))
                                        evas_object_show(ec->frame);
                                   }
                              }
                         }
                       e_client_res_change_geometry_restore(ec);
                    }
               }
          }
     }
   e_comp_canvas_update();
   return ECORE_CALLBACK_PASS_ON;
}

////////////////////////////////////

static Eina_Bool
_e_comp_cb_screensaver_active_delay(void *data EINA_UNUSED)
{
   if (!e_comp->nocomp)
     ecore_evas_manual_render_set(e_comp->ee, EINA_TRUE);
   timer_post_screensaver_on = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_comp_canvas_screensaver_active(void *d EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *sig EINA_UNUSED, const char *src EINA_UNUSED)
{
   if (timer_post_screensaver_on) return;
   /* thawed in _e_comp_screensaver_off() */
   timer_post_screensaver_on = ecore_timer_loop_add
     (1.0, _e_comp_cb_screensaver_active_delay, NULL);
}

static Eina_Bool
_e_comp_cb_timer_post_screensaver_lock(void *data EINA_UNUSED)
{
   // XXX: the below should instantly show desklock and have no transition
   // or animation
   e_desklock_show_autolocked();
   timer_post_screensaver_lock = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_comp_cb_screensaver_ponter_freeze_cb(void *data EINA_UNUSED)
{
   e_pointers_freeze_set(EINA_TRUE);
   timer_pointer_freeze = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_cb_screensaver_on()
{
   // XXX: this is not quite right. this here should be called after e_comp.c
   // and freinds have emitted "e,state,screensaver,on" AND gotten a signal
   // reply from the theme to say it's done with this animation before
   // doing the below
   if (e_config->desklock_autolock_screensaver)
     {
        E_FREE_FUNC(timer_post_screensaver_lock, ecore_timer_del);
        if (e_config->desklock_post_screensaver_time <= 1.0)
          e_desklock_show_autolocked();
        else
          timer_post_screensaver_lock = ecore_timer_loop_add
              (e_config->desklock_post_screensaver_time,
              _e_comp_cb_timer_post_screensaver_lock, NULL);
     }
   if (timer_pointer_freeze) ecore_timer_del(timer_pointer_freeze);
   timer_pointer_freeze = ecore_timer_add
     (10.0, _e_comp_cb_screensaver_ponter_freeze_cb, NULL);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_cb_screensaver_off()
{
   E_FREE_FUNC(timer_post_screensaver_lock, ecore_timer_del);
   E_FREE_FUNC(timer_post_screensaver_on, ecore_timer_del);
   e_pointers_freeze_set(EINA_FALSE);
   if (timer_pointer_freeze)
     {
        ecore_timer_del(timer_pointer_freeze);
        timer_pointer_freeze = NULL;
     }
   return ECORE_CALLBACK_PASS_ON;
}
////////////////////////////////////

static int
_e_comp_canvas_cb_zone_sort(const void *data1, const void *data2)
{
   const E_Zone *z1 = data1, *z2 = data2;

   return z1->num - z2->num;
}

static void
_e_comp_canvas_prerender(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Cb cb;
   Eina_List *l;
   E_Comp_Config *conf = e_comp_config_get();

   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_RENDER_BEGIN, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }

   e_comp->rendering = EINA_TRUE;

   if (conf->grab && (!e_comp->grabbed))
     {
        if (e_comp->grab_cb) e_comp->grab_cb();
        e_comp->grabbed = 1;
     }

   EINA_LIST_FOREACH(e_comp->pre_render_cbs, l, cb)
     cb();
}

static Eina_Bool
_e_comp_canvas_cb_idle_enterer(void *data EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
     {
        int info[4] = { E_COMP_FRAME_EVENT_IDLE_ENTER, 0, 0, 0 };
        e_comp_frame_event_add(info, ecore_time_get());
     }
   return EINA_TRUE;
}

static Eina_Bool
_e_comp_canvas_cb_idle_exiter(void *data EINA_UNUSED)
{
   E_Comp_Config *conf = e_comp_config_get();
   if (conf->fps_show)
   {
      int info[4] = { E_COMP_FRAME_EVENT_IDLE_EXIT, 0, 0, 0 };
      e_comp_frame_event_add(info, ecore_time_get());
   }
   return EINA_TRUE;
}

E_API Eina_Bool
e_comp_canvas_init(int w, int h)
{
   Evas_Object *o;
   Eina_List *screens;
   char *bsize;

   e_comp->w = w;
   e_comp->h = h;
   bsize = getenv("E_COMP_SIZE");
   if (bsize)
     {
        int ew = w, eh = h;

        if (sscanf(bsize, "%dx%d", &ew, &eh) == 2)
          {
             if ((w > 0) && (h > 0))
               e_comp->w = ew, e_comp->h = eh;
          }
     }

   e_comp->evas = ecore_evas_get(e_comp->ee);

   static int inited = 0;
   if (!inited)
     {
        inited = 1;
        ecore_idle_enterer_before_add(_e_comp_canvas_cb_idle_enterer, NULL);
        ecore_idle_exiter_add(_e_comp_canvas_cb_idle_exiter, NULL);
     }
   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_PRE, _e_comp_canvas_render_track_pre, NULL);
   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_POST, _e_comp_canvas_render_track_post, NULL);
   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_FLUSH_PRE, _e_comp_canvas_render_track_flush_pre, NULL);
   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_FLUSH_POST, _e_comp_canvas_render_track_flush_post, NULL);
   if (e_first_frame)
     evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_POST, _e_comp_canvas_cb_first_frame, NULL);
   o = evas_object_rectangle_add(e_comp->evas);
   e_comp->canvas->resize_object = o;
   evas_object_layer_set(o, E_LAYER_BOTTOM);
   evas_object_move(o, 0, 0);
   evas_object_resize(o, e_comp->w, e_comp->h);
   evas_object_color_set(o, 0, 0, 0, 255);
   evas_object_name_set(o, "comp->canvas->resize_object");
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, (Evas_Object_Event_Cb)_e_comp_canvas_cb_mouse_down, NULL);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, (Evas_Object_Event_Cb)_e_comp_canvas_cb_mouse_up, NULL);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_IN, (Evas_Object_Event_Cb)_e_comp_canvas_cb_mouse_in, NULL);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_WHEEL, (Evas_Object_Event_Cb)_e_comp_canvas_cb_mouse_wheel, NULL);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _e_comp_canvas_cb_del, NULL);
   evas_object_show(o);

   ecore_evas_name_class_set(e_comp->ee, "E", "Comp_EE");
   //   ecore_evas_manual_render_set(e_comp->ee, conf->lock_fps);
   ecore_evas_show(e_comp->ee);

   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_FLUSH_POST, _e_comp_canvas_render_post, NULL);

   e_comp->ee_win = ecore_evas_window_get(e_comp->ee);

   screens = (Eina_List *)e_xinerama_screens_get();
   if (screens)
     {
        E_Screen *scr;
        Eina_List *l;

        EINA_LIST_FOREACH(screens, l, scr)
          {
             E_Zone *zone = e_zone_new(scr->screen, scr->escreen,
                                       scr->x, scr->y, scr->w, scr->h);
             if (scr->id) zone->randr2_id = strdup(scr->id);
             e_desk_window_profile_update(zone);
          }
     }
   else
     e_zone_new(0, 0, 0, 0, e_comp->w, e_comp->h);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_ZONE_MOVE_RESIZE, _e_comp_cb_zone_change, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_ZONE_ADD, _e_comp_cb_zone_change, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_ZONE_DEL, _e_comp_cb_zone_change, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_KEY_DOWN, _e_comp_canvas_cb_key_down, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_KEY_UP, _e_comp_canvas_cb_key_up, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_ON, _e_comp_cb_screensaver_on, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_OFF, _e_comp_cb_screensaver_off, NULL);

   evas_event_callback_add(e_comp->evas, EVAS_CALLBACK_RENDER_PRE, _e_comp_canvas_prerender, NULL);
   ecore_evas_resize(e_comp->ee, w, h);
   if ((!after_restart) || (!e_comp_x))
     ecore_evas_pointer_warp(e_comp->ee, e_comp->w / 2, e_comp->h / 2);

#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     e_comp_wl_notidle();
#endif
   return EINA_TRUE;
}

EINTERN void
e_comp_canvas_clear(void)
{
   evas_event_freeze(e_comp->evas);
   edje_freeze();

   E_FREE_FUNC(e_comp->canvas->fps_fg, evas_object_del);
   E_FREE_FUNC(e_comp->canvas->fps_bg, evas_object_del);
   E_FREE_FUNC(e_comp->canvas->fps_gr, evas_object_del);
   E_FREE_FUNC(e_comp->autoclose.rect, evas_object_del);
   E_FREE_FUNC(e_comp->shape_job, ecore_job_del);
   E_FREE_FUNC(e_comp->pointer, e_object_del);
}

//////////////////////////////////////////////

E_API void
e_comp_canvas_resize(int w, int h)
{
   e_comp->w = w;
   e_comp->h = h;
   ecore_evas_resize(e_comp->ee, w, h);
}

E_API void
e_comp_all_freeze(void)
{
   evas_event_freeze(e_comp->evas);
}

E_API void
e_comp_all_thaw(void)
{
   evas_event_thaw(e_comp->evas);
}

E_API E_Zone *
e_comp_zone_xy_get(Evas_Coord x, Evas_Coord y)
{
   const Eina_List *l;
   E_Zone *zone;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     if (E_INSIDE(x, y, zone->x, zone->y, zone->w, zone->h)) return zone;
   return NULL;
}

E_API E_Zone *
e_comp_zone_number_get(int num)
{
   Eina_List *l = NULL;
   E_Zone *zone = NULL;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if ((int)zone->num == num) return zone;
     }
   return NULL;
}

E_API E_Zone *
e_comp_zone_id_get(int id)
{
   Eina_List *l = NULL;
   E_Zone *zone = NULL;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (zone->id == id) return zone;
     }
   return NULL;
}

E_API E_Desk *
e_comp_desk_window_profile_get(const char *profile)
{
   Eina_List *l = NULL;
   E_Zone *zone = NULL;
   int x, y;

   EINA_SAFETY_ON_NULL_RETURN_VAL(profile, NULL);

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        for (x = 0; x < zone->desk_x_count; x++)
          {
             for (y = 0; y < zone->desk_y_count; y++)
               {
                  E_Desk *desk = e_desk_at_xy_get(zone, x, y);
                  if (desk)
                    {
                       if (e_object_is_del(E_OBJECT(desk))) continue;
                       if (!e_util_strcmp(desk->window_profile, profile))
                         return desk;
                    }
               }
          }
     }

   return NULL;
}

E_API void
e_comp_canvas_zone_restarted(E_Zone *zone)
{
   if (edje_object_data_get(zone->over, "restarted"))
     {
        edje_object_signal_emit(zone->over, "e,state,sys,restarted,show", "e");
     }
}

E_API void
e_comp_canvas_zone_update(E_Zone *zone)
{
   Evas_Object *o;

   if (zone->over && zone->base)
     {
        e_theme_edje_object_set(zone->base, "base/theme/comp",
                                "e/comp/screen/base/default");
        edje_object_part_swallow(zone->base, "e.swallow.background",
                                 zone->transition_object ?: zone->bg_object);
        e_theme_edje_object_set(zone->over, "base/theme/comp",
                                "e/comp/screen/overlay/default");
        return;
     }
   E_FREE_FUNC(zone->base, evas_object_del);
   E_FREE_FUNC(zone->over, evas_object_del);
   zone->base = o = edje_object_add(e_comp->evas);
   evas_object_repeat_events_set(o, 1);
   evas_object_name_set(zone->base, "zone->base");
   e_theme_edje_object_set(o, "base/theme/comp", "e/comp/screen/base/default");
   edje_object_part_swallow(zone->base, "e.swallow.background", zone->transition_object ?: zone->bg_object);
   evas_object_move(o, zone->x, zone->y);
   evas_object_resize(o, zone->w, zone->h);
   evas_object_layer_set(o, E_LAYER_BG);
   evas_object_show(o);

   zone->over = o = edje_object_add(e_comp->evas);
   edje_object_signal_callback_add(o, "e,state,screensaver,active", "e", _e_comp_canvas_screensaver_active, NULL);
   evas_object_layer_set(o, E_LAYER_MAX);
   evas_object_raise(o);
   evas_object_name_set(zone->over, "zone->over");
   evas_object_pass_events_set(o, 1);
   e_theme_edje_object_set(o, "base/theme/comp", "e/comp/screen/overlay/default");
   evas_object_move(o, zone->x, zone->y);
   evas_object_resize(o, zone->w, zone->h);
   evas_object_raise(o);
   evas_object_show(o);
   if (starting && after_restart &&
       (edje_object_data_get(zone->over, "restarted")))
     {
        edje_object_signal_emit(zone->over, "e,state,sys,restarted", "e");
        edje_object_message_signal_process(zone->over);
     }
}

E_API void
e_comp_canvas_update(void)
{
   Eina_List *l, *screens, *zones = NULL, *ll;
   E_Zone *zone;
   E_Screen *scr;
   Eina_Bool changed = EINA_FALSE;

   screens = (Eina_List *)e_xinerama_screens_get();

   if (screens)
     {
        zones = e_comp->zones;
        e_comp->zones = NULL;
        printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
        EINA_LIST_FOREACH(screens, l, scr)
          {
             zone = NULL;

             printf("@ match screens %p[%i] = %i %i %ix%i -- %i\n",
                    scr, scr->escreen, scr->x, scr->y, scr->w, scr->h, scr->escreen);
             EINA_LIST_FOREACH(zones, ll, zone)
               {
                  if (zone->id == scr->escreen) break;
                  zone = NULL;
               }
             printf("@ matches existing zone %p\n", zone);
             if (zone)
               {
                  printf("   move resize %i %i %ix%i -> %i %i %ix%i\n",
                         zone->x, zone->y, zone->w, zone->h,
                         scr->x, scr->y, scr->w, scr->h);
                  changed |= e_zone_move_resize(zone, scr->x, scr->y, scr->w, scr->h);
                  if (changed)
                    printf("@@@ FOUND ZONE %i %i [%p]\n", zone->num, zone->id, zone);
                  zones = eina_list_remove(zones, zone);
                  e_comp->zones = eina_list_append(e_comp->zones, zone);
                  zone->num = scr->screen;
                  free(zone->randr2_id);
                  zone->randr2_id = NULL;
                  if (scr->id) zone->randr2_id = strdup(scr->id);
                  e_desk_window_profile_update(zone);
               }
             else
               {
                  zone = e_zone_new(scr->screen, scr->escreen,
                                    scr->x, scr->y, scr->w, scr->h);
                  if (scr->id) zone->randr2_id = strdup(scr->id);
                  e_desk_window_profile_update(zone);
                  printf("@@@ NEW ZONE = %p\n", zone);
                  changed = EINA_TRUE;
               }
             if (changed)
               printf("@@@ SCREENS: %i %i | %i %i %ix%i\n",
                      scr->screen, scr->escreen, scr->x, scr->y, scr->w, scr->h);
          }
        e_comp->zones = eina_list_sort(e_comp->zones, 0, _e_comp_canvas_cb_zone_sort);
        if (zones)
          {
             E_Zone *spare_zone;

             printf("@zones have been deleted....\n");
             changed = EINA_TRUE;
             spare_zone = eina_list_data_get(e_comp->zones);

             EINA_LIST_FREE(zones, zone)
               {
                  E_Client *ec;

                  printf("reassign all clients from deleted zone %p\n", zone);
                  E_CLIENT_FOREACH(ec)
                    {
                       if (ec->zone == zone)
                         {
                            const char *tmp;

                            if (ec->restore_zone_id)
                              tmp = eina_stringshare_add(ec->restore_zone_id);
                            else
                              tmp = NULL;
                            if (spare_zone)
                              {
                                 int dx, dy;
                                 E_Desk *desk;

                                 dx = dy = -1;
                                 if (ec->desk)
                                   {
                                      dx = ec->desk->x;
                                      dy = ec->desk->y;
                                   }
                                 e_client_res_change_geometry_save(ec);
                                 e_client_zone_set(ec, spare_zone);
                                 if ((dx >= 0) && (dy >= 0))
                                   {
                                      desk = e_desk_at_xy_get(spare_zone, dx, dy);
                                      if (desk) e_client_desk_set(ec, desk);
                                   }
                              }
                            else
                              printf("EEEK! should not be here - but no\n"
                                     "spare zones exist to move this\n"
                                     "window to!!! help!\n");
                            eina_stringshare_replace(&(ec->restore_zone_id), tmp);
                            if (tmp) eina_stringshare_del(tmp);
                         }
                    }
                  e_object_del(E_OBJECT(zone));
               }
          }
        if (changed) e_shelf_config_update();
     }
   else
     {
        E_Zone *z;

        z = e_comp_zone_number_get(0);
        if (z)
          {
             changed |= e_zone_move_resize(z, 0, 0, e_comp->w, e_comp->h);
             if (changed) e_shelf_zone_move_resize_handle(z);
          }
     }

   e_comp_clients_rescale();
   if (!changed) return;
   if (!starting)
     {
        e_object_ref(E_OBJECT(e_comp));
        ecore_event_add(E_EVENT_COMPOSITOR_UPDATE, NULL, _e_comp_canvas_event_compositor_resize_free, NULL);
     }

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        E_FREE_FUNC(zone->base, evas_object_del);
        E_FREE_FUNC(zone->over, evas_object_del);
        if (zone->bloff)
          {
             if (!e_comp_config_get()->nofade)
               {
                  if (e_backlight_mode_get(zone) != E_BACKLIGHT_MODE_NORMAL)
                    e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
                  e_backlight_level_set(zone, e_config->backlight.normal, -1.0);
               }
          }
        e_comp_canvas_zone_update(zone);
        if (!starting)
          e_bindings_edge_reset();
     }
   evas_object_resize(e_comp->canvas->resize_object, e_comp->w, e_comp->h);
}

E_API void
e_comp_canvas_fake_layers_init(void)
{
   unsigned int layer;

   /* init layers */
   for (layer = e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); layer <= e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); layer++)
     {
        Evas_Object *o2;

        o2 = e_comp->layers[layer].obj = evas_object_rectangle_add(e_comp->evas);
        evas_object_layer_set(o2, e_comp_canvas_layer_map_to(layer));
        evas_object_name_set(o2, "layer_obj");
     }
}

E_API void
e_comp_canvas_fps_toggle(void)
{
   E_Comp_Config *conf = e_comp_config_get();

   conf->fps_show = !conf->fps_show;
   e_comp_internal_save();
   e_comp_render_queue();
}

E_API E_Layer
e_comp_canvas_layer_map_to(unsigned int layer)
{
   switch (layer)
     {
      case 0: return E_LAYER_BOTTOM;
      case 1: return E_LAYER_BG;
      case 2: return E_LAYER_DESKTOP;
      case 3: return E_LAYER_DESKTOP_TOP;
      case 4: return E_LAYER_CLIENT_DESKTOP;
      case 5: return E_LAYER_CLIENT_BELOW;
      case 6: return E_LAYER_CLIENT_NORMAL;
      case 7: return E_LAYER_CLIENT_ABOVE;
      case 8: return E_LAYER_CLIENT_EDGE;
      case 9: return E_LAYER_CLIENT_FULLSCREEN;
      case 10: return E_LAYER_CLIENT_EDGE_FULLSCREEN;
      case 11: return E_LAYER_CLIENT_POPUP;
      case 12: return E_LAYER_CLIENT_TOP;
      case 13: return E_LAYER_CLIENT_DRAG;
      case 14: return E_LAYER_CLIENT_PRIO;
      case 15: return E_LAYER_POPUP;
      case 16: return E_LAYER_MENU;
      case 17: return E_LAYER_DESKLOCK;
      case 18: return E_LAYER_MAX;
      default: break;
     }
   return -INT_MAX;
}

E_API unsigned int
e_comp_canvas_layer_map(E_Layer layer)
{
   switch (layer)
     {
      case E_LAYER_BOTTOM: return 0;
      case E_LAYER_BG: return 1;
      case E_LAYER_DESKTOP: return 2;
      case E_LAYER_DESKTOP_TOP: return 3;
      case E_LAYER_CLIENT_DESKTOP: return 4;
      case E_LAYER_CLIENT_BELOW: return 5;
      case E_LAYER_CLIENT_NORMAL: return 6;
      case E_LAYER_CLIENT_ABOVE: return 7;
      case E_LAYER_CLIENT_EDGE: return 8;
      case E_LAYER_CLIENT_FULLSCREEN: return 9;
      case E_LAYER_CLIENT_EDGE_FULLSCREEN: return 10;
      case E_LAYER_CLIENT_POPUP: return 11;
      case E_LAYER_CLIENT_TOP: return 12;
      case E_LAYER_CLIENT_DRAG: return 13;
      case E_LAYER_CLIENT_PRIO: return 14;
      case E_LAYER_POPUP: return 15;
      case E_LAYER_MENU: return 16;
      case E_LAYER_DESKLOCK: return 17;
      case E_LAYER_MAX: return 18;
      default: break;
     }
   return 9999;
}

E_API unsigned int
e_comp_canvas_client_layer_map(E_Layer layer)
{
   switch (layer)
     {
      case E_LAYER_CLIENT_DESKTOP: return 0;
      case E_LAYER_CLIENT_BELOW: return 1;
      case E_LAYER_CLIENT_NORMAL: return 2;
      case E_LAYER_CLIENT_ABOVE: return 3;
      case E_LAYER_CLIENT_EDGE: return 4;
      case E_LAYER_CLIENT_FULLSCREEN: return 5;
      case E_LAYER_CLIENT_EDGE_FULLSCREEN: return 6;
      case E_LAYER_CLIENT_POPUP: return 7;
      case E_LAYER_CLIENT_TOP: return 8;
      case E_LAYER_CLIENT_DRAG: return 9;
      case E_LAYER_CLIENT_PRIO: return 10;
      default: break;
     }
   return 9999;
}

E_API E_Layer
e_comp_canvas_client_layer_map_nearest(int layer)
{
#define LAYER_MAP(X) \
   if (layer <= X) return X

   LAYER_MAP(E_LAYER_CLIENT_DESKTOP);
   LAYER_MAP(E_LAYER_CLIENT_BELOW);
   LAYER_MAP(E_LAYER_CLIENT_NORMAL);
   LAYER_MAP(E_LAYER_CLIENT_ABOVE);
   LAYER_MAP(E_LAYER_CLIENT_EDGE);
   LAYER_MAP(E_LAYER_CLIENT_FULLSCREEN);
   LAYER_MAP(E_LAYER_CLIENT_EDGE_FULLSCREEN);
   LAYER_MAP(E_LAYER_CLIENT_POPUP);
   LAYER_MAP(E_LAYER_CLIENT_TOP);
   LAYER_MAP(E_LAYER_CLIENT_DRAG);
   return E_LAYER_CLIENT_PRIO;
}

E_API void
e_comp_canvas_keys_grab(void)
{
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_bindings_key_grab(E_BINDING_CONTEXT_ANY, e_comp->root);
}

E_API void
e_comp_canvas_keys_ungrab(void)
{
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_bindings_key_ungrab(E_BINDING_CONTEXT_ANY, e_comp->root);
}

E_API void
e_comp_canvas_feed_mouse_up(unsigned int activate_time)
{
   int button_mask, i;

   button_mask = evas_pointer_button_down_mask_get(e_comp->evas);
   for (i = 0; i < 32; i++)
     {
       if ((button_mask & (1 << i)))
         evas_event_feed_mouse_up(e_comp->evas, i + 1, EVAS_BUTTON_NONE, activate_time, NULL);
     }
}

E_API void
e_comp_canvas_notidle(void)
{
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL) e_comp_wl_notidle();
#endif
}

E_API Evas_Object *
e_comp_canvas_event_grabber_add(void)
{
   E_Zone *zone;
   Eina_List *l;
   Evas_Object *o;

   o = evas_object_event_grabber_add(e_comp->evas);
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
#define EDGE_ADD(E) \
        if (zone->E) \
          evas_object_smart_member_add(zone->E, o)

        EDGE_ADD(edge.top);
        EDGE_ADD(edge.right);
        EDGE_ADD(edge.bottom);
        EDGE_ADD(edge.left);

        EDGE_ADD(corner.left_top);
        EDGE_ADD(corner.right_top);
        EDGE_ADD(corner.top_left);
        EDGE_ADD(corner.top_right);
        EDGE_ADD(corner.left_bottom);
        EDGE_ADD(corner.right_bottom);
        EDGE_ADD(corner.bottom_left);
        EDGE_ADD(corner.bottom_right);
     }
   return o;
}

EINTERN void
e_comp_canvas_intercept(void)
{
   ecore_event_init();
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_MOUSE_BUTTON_DOWN, _e_comp_cb_mouse_down, NULL);
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_MOUSE_BUTTON_UP, _e_comp_cb_mouse_up, NULL);
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_MOUSE_WHEEL, _e_comp_cb_mouse_wheel, NULL);
#ifdef HAVE_WAYLAND
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_MOUSE_MOVE, _e_comp_cb_mouse_move, NULL);
#endif
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_KEY_DOWN, _e_comp_cb_key_down, NULL);
   E_LIST_HANDLER_APPEND_PRE(handlers, ECORE_EVENT_KEY_UP, _e_comp_cb_key_up, NULL);
}
#include "e.h"
#include "e_comp_cfdata.h"

E_API void
e_comp_cfdata_edd_init(E_Config_DD **conf_edd, E_Config_DD **match_edd)
{
   *match_edd = E_CONFIG_DD_NEW("Comp_Match", E_Comp_Match);
#undef T
#undef D
#define T E_Comp_Match
#define D *match_edd
   E_CONFIG_VAL(D, T, title, STR);
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, clas, STR);
   E_CONFIG_VAL(D, T, role, STR);
   E_CONFIG_VAL(D, T, primary_type, INT);
   E_CONFIG_VAL(D, T, borderless, CHAR);
   E_CONFIG_VAL(D, T, dialog, CHAR);
   E_CONFIG_VAL(D, T, accepts_focus, CHAR);
   E_CONFIG_VAL(D, T, argb, CHAR);
   E_CONFIG_VAL(D, T, fullscreen, CHAR);
   E_CONFIG_VAL(D, T, modal, CHAR);
   E_CONFIG_VAL(D, T, focus, CHAR);
   E_CONFIG_VAL(D, T, urgent, CHAR);
   E_CONFIG_VAL(D, T, no_shadow, CHAR);
   E_CONFIG_VAL(D, T, shadow_style, STR);
   E_CONFIG_VAL(D, T, visibility_effect, STR);

   *conf_edd = E_CONFIG_DD_NEW("Comp_Config", E_Comp_Config);
#undef T
#undef D
#define T E_Comp_Config
#define D *conf_edd
   E_CONFIG_VAL(D, T, version, INT);
   E_CONFIG_VAL(D, T, shadow_style, STR);
   E_CONFIG_VAL(D, T, engine, INT);
   //E_CONFIG_VAL(D, T, max_unmapped_pixels, INT);
   E_CONFIG_VAL(D, T, max_unmapped_time, INT);
   E_CONFIG_VAL(D, T, min_unmapped_time, INT);
   E_CONFIG_VAL(D, T, fps_corner, UCHAR);
   E_CONFIG_VAL(D, T, fps_show, UCHAR);
   E_CONFIG_VAL(D, T, indirect, UCHAR);
   E_CONFIG_VAL(D, T, texture_from_pixmap, UCHAR);
//   E_CONFIG_VAL(D, T, lock_fps, UCHAR);
   E_CONFIG_VAL(D, T, grab, UCHAR);
   E_CONFIG_VAL(D, T, vsync, UCHAR);
   E_CONFIG_VAL(D, T, no_dither, UCHAR);
   E_CONFIG_VAL(D, T, swap_mode, UCHAR);
   //E_CONFIG_VAL(D, T, keep_unmapped, UCHAR);
   E_CONFIG_VAL(D, T, send_flush, UCHAR);
   E_CONFIG_VAL(D, T, send_dump, UCHAR);
   E_CONFIG_VAL(D, T, nocomp_fs, UCHAR);
   E_CONFIG_VAL(D, T, nofade, UCHAR);
   E_CONFIG_VAL(D, T, smooth_windows, UCHAR);
   E_CONFIG_VAL(D, T, first_draw_delay, DOUBLE);
   E_CONFIG_VAL(D, T, enable_advanced_features, UCHAR);
   E_CONFIG_LIST(D, T, match.popups, *match_edd);
   E_CONFIG_LIST(D, T, match.borders, *match_edd);
   E_CONFIG_LIST(D, T, match.overrides, *match_edd);
   E_CONFIG_LIST(D, T, match.menus, *match_edd);
   E_CONFIG_LIST(D, T, match.objects, *match_edd);
}

E_API E_Comp_Config *
e_comp_cfdata_config_new(void)
{
   E_Comp_Config *cfg;
   E_Comp_Match *mat;

   cfg = E_NEW(E_Comp_Config, 1);
   cfg->version = E_COMP_VERSION;
   cfg->shadow_style = eina_stringshare_add("default");
   cfg->engine = E_COMP_ENGINE_SW;
   cfg->max_unmapped_pixels = 32 * 1024;  // implement
   cfg->max_unmapped_time = 10 * 3600; // implement
   cfg->min_unmapped_time = 5 * 60; // implement
   cfg->fps_corner = 0;
   cfg->fps_show = 0;
   cfg->indirect = 0;
   cfg->texture_from_pixmap = 1;
//   cfg->lock_fps = 0;
   cfg->grab = 0;
   cfg->vsync = 1;
   cfg->no_dither = 0;
#ifdef ECORE_EVAS_GL_X11_OPT_SWAP_MODE
   cfg->swap_mode = ECORE_EVAS_GL_X11_SWAP_MODE_AUTO;
#else
   cfg->swap_mode = 0;
#endif
   cfg->keep_unmapped = 1;
   cfg->send_flush = 1; // implement
   cfg->send_dump = 1; // implement
   cfg->nocomp_fs = 1;
   cfg->nofade = 0;
   cfg->smooth_windows = 0; // 1 if gl, 0 if not
   cfg->first_draw_delay = 0.15;

   cfg->match.popups = NULL;

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.popups = eina_list_append(cfg->match.popups, mat);
   mat->name = eina_stringshare_add("shelf");
   mat->shadow_style = eina_stringshare_add("popup");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.popups = eina_list_append(cfg->match.popups, mat);
   mat->name = eina_stringshare_add("_e_popup_desklock");
   mat->shadow_style = eina_stringshare_add("still");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.popups = eina_list_append(cfg->match.popups, mat);
   mat->name = eina_stringshare_add("_e_popup_notification");
   mat->shadow_style = eina_stringshare_add("still");
   mat->no_shadow = 1;
   mat->focus = 1;

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.popups = eina_list_append(cfg->match.popups, mat);
   mat->name = eina_stringshare_add("E Drag");
   mat->shadow_style = eina_stringshare_add("still");
   mat->no_shadow = 1;

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.popups = eina_list_append(cfg->match.popups, mat);
   mat->shadow_style = eina_stringshare_add("popup");

   cfg->match.borders = NULL;

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.borders = eina_list_append(cfg->match.borders, mat);
   mat->fullscreen = 1;
   mat->shadow_style = eina_stringshare_add("fullscreen");

   cfg->match.overrides = NULL;

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->name = eina_stringshare_add("E");
   mat->clas = eina_stringshare_add("everything");
   mat->shadow_style = eina_stringshare_add("everything");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_DROPDOWN_MENU;
   mat->shadow_style = eina_stringshare_add("menu");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_POPUP_MENU;
   mat->shadow_style = eina_stringshare_add("menu");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_COMBO;
   mat->shadow_style = eina_stringshare_add("menu");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_TOOLTIP;
   mat->shadow_style = eina_stringshare_add("menu");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_MENU;
   mat->shadow_style = eina_stringshare_add("menu");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_DND;
   mat->shadow_style = eina_stringshare_add("still");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->primary_type = E_WINDOW_TYPE_DOCK;
   mat->shadow_style = eina_stringshare_add("none");

   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.overrides = eina_list_append(cfg->match.overrides, mat);
   mat->shadow_style = eina_stringshare_add("popup");

   cfg->match.menus = NULL;
   mat = E_NEW(E_Comp_Match, 1);
   cfg->match.menus = eina_list_append(cfg->match.menus, mat);
   mat->shadow_style = eina_stringshare_add("menu");

   return cfg;
}

E_API void
e_comp_cfdata_match_free(E_Comp_Match *m)
{
   if (!m) return;
   eina_stringshare_del(m->title);
   eina_stringshare_del(m->name);
   eina_stringshare_del(m->clas);
   eina_stringshare_del(m->role);
   eina_stringshare_del(m->shadow_style);
   eina_stringshare_del(m->visibility_effect);
   free(m);
}

E_API void
e_comp_cfdata_config_free(E_Comp_Config *cfg)
{
   if (!cfg) return;
   eina_stringshare_del(cfg->shadow_style);

   E_FREE_LIST(cfg->match.popups, e_comp_cfdata_match_free);
   E_FREE_LIST(cfg->match.borders, e_comp_cfdata_match_free);
   E_FREE_LIST(cfg->match.overrides, e_comp_cfdata_match_free);
   E_FREE_LIST(cfg->match.menus, e_comp_cfdata_match_free);
   E_FREE_LIST(cfg->match.objects, e_comp_cfdata_match_free);

   free(cfg);
}

#include "e.h"

/* data keys:

   = keys that return objects =
   - E_Client: the client associated with the object (E_Client*)
   - comp_smart_obj: cw->smart_obj (Evas_Object*)
   - comp_obj: cw (E_Comp_Object*)

   = keys that are bool flags =
   - client_restack: client needs a protocol-level restack
   - comp_override: object is triggering a nocomp override to force compositing
   - comp_ref: object has a ref from visibility animations
   - comp_showing: object is currently running its show animation
   - comp_hiding: object is currently running its hiding animation
   - comp_object: object is a compositor-created object
   - comp_object_skip: object has a name which prohibits theme shadows
   - comp_object-to_del: list of objects which will be deleted when this object is deleted
   - comp_mirror: object is the result of e_comp_object_util_mirror_add()
*/

#define UPDATE_MAX 512 // same as evas
#define FAILURE_MAX 2 // seems reasonable
#define SMART_NAME     "e_comp_object"

/* for non-util functions */
#define API_ENTRY      E_Comp_Object *cw; \
                       cw = evas_object_smart_data_get(obj); \
                       if ((!obj) || (!cw) || (e_util_strcmp(evas_object_type_get(obj), SMART_NAME))) return

/* for util functions (obj may or may not be E_Comp_Object */
#define SOFT_ENTRY(...)      E_Comp_Object *cw; \
                       if (!obj) \
                         { \
                            CRI("YOU PASSED NULL! ARGH!"); \
                            return __VA_ARGS__; \
                         } \
                       cw = evas_object_smart_data_get(obj); \
                       if ((!cw) || (e_util_strcmp(evas_object_type_get(obj), SMART_NAME))) \
                         cw = NULL
#define INTERNAL_ENTRY E_Comp_Object *cw; cw = evas_object_smart_data_get(obj);

/* enable for lots of client size info in console output */
#if 1
# define e_util_size_debug_set(x, y)
#endif

/* enable along with display-specific damage INF calls to enable render tracing
 * SLOW!
 */
static int render_debug_enabled;
#define RENDER_DEBUG(...) do { if ((render_debug_enabled == 1) || ((render_debug_enabled == -1) && cw->ec->focused)) INF(__VA_ARGS__); } while (0)

typedef struct _E_Comp_Object
{
   //EINA_INLIST;

   int                  x, y, w, h;  // geometry
   Eina_Tiler          *input_area;

   E_Client *ec;

   E_Comp_Object_Frame client_inset;
   struct {
      double          start;
      double          val;
      int             x, y;
      E_Direction     dir;
      Ecore_Animator *anim;
   } shade;
   struct {
      int             bx, by, bxx, byy, w, h;
   } border;

   Eina_Stringshare   *frame_theme;
   Eina_Stringshare   *frame_name;
   Eina_Stringshare   *visibility_effect; //effect when toggling visibility

   Evas_Object         *smart_obj;  // smart object
   Evas_Object         *clip; // clipper over effect object
   Eina_Array          *input_objs; // input rect
   Evas_Object         *obj;  // composite object
   Evas_Object         *frame_object; // for client frames
   Evas_Object         *frame_icon; // for client frames
   Evas_Object         *zoomobj; // zoomap
   Evas_Object         *shobj;  // shadow object
   Evas_Object         *effect_obj; // effects object
   Evas_Object         *frame_volume; // volume level object
   unsigned int         layer; //e_comp_canvas_layer_map(cw->ec->layer)
   Eina_List           *obj_mirror;  // extra mirror objects
   Eina_List           *obj_agent;  // extra agent objects
   Eina_Tiler          *updates; //render update regions
   Eina_Tiler          *pending_updates; //render update regions which are about to render

   Evas_Native_Surface *ns; //for custom gl rendering

   double               action_client_loop_time; //loop time when client's action ended

   unsigned int         update_count;  // how many updates have happened to this obj

   unsigned int         opacity;  // opacity set with _NET_WM_WINDOW_OPACITY

   unsigned int         animating;  // it's busy animating
   unsigned int         failures; //number of consecutive e_pixmap_image_draw() failures
   unsigned int         force_visible; //number of visible obj_mirror objects
   Eina_Bool            deleted E_BITFIELD;  // deleted
   Eina_Bool            defer_hide E_BITFIELD;  // flag to get hide to work on deferred hide
   Eina_Bool            showing E_BITFIELD;  // object is currently in "show" animation
   Eina_Bool            visible E_BITFIELD;  // is visible

   Eina_Bool            shaped E_BITFIELD;  // is shaped
   Eina_Bool            update E_BITFIELD;  // has updates to fetch
   Eina_Bool            redirected E_BITFIELD;  // has updates to fetch
   Eina_Bool            native E_BITFIELD;  // native

   Eina_Bool            nocomp E_BITFIELD;  // nocomp applied
   Eina_Bool            nocomp_need_update E_BITFIELD;  // nocomp in effect, but this window updated while in nocomp mode
   Eina_Bool            real_hid E_BITFIELD;  // last hide was a real window unmap

   Eina_Bool            effect_set E_BITFIELD; //effect_obj has a valid group
   Eina_Bool            effect_running E_BITFIELD; //effect_obj is playing an animation
   Eina_Bool            effect_clip E_BITFIELD; //effect_obj is clipped
   Eina_Bool            effect_clip_able E_BITFIELD; //effect_obj will be clipped for effects

   Eina_Bool            zoomap_disabled E_BITFIELD; //whether zoomap is usable
   Eina_Bool            updates_exist E_BITFIELD;
   Eina_Bool            updates_full E_BITFIELD; // entire object will be updated

   Eina_Bool            force_move E_BITFIELD;
   Eina_Bool            frame_extends E_BITFIELD; //frame may extend beyond object size
   Eina_Bool            blanked E_BITFIELD; //window is rendering blank content (externally composited)

   Eina_Bool            agent_updating E_BITFIELD; //updating agents
} E_Comp_Object;


struct E_Comp_Object_Mover
{
   EINA_INLIST;
   E_Comp_Object_Mover_Cb func;
   const char *sig;
   void *data;
   int pri;
};

static Eina_Inlist *_e_comp_object_movers = NULL;
static Evas_Smart *_e_comp_smart = NULL;

/* sekrit functionzzz */
EINTERN void e_client_focused_set(E_Client *ec);

/* emitted every time a new noteworthy comp object is added */
E_API int E_EVENT_COMP_OBJECT_ADD = -1;

static void
_e_comp_object_event_free(void *d EINA_UNUSED, void *event)
{
   E_Event_Comp_Object *ev = event;
   E_Client *ec;

   ec = evas_object_data_get(ev->comp_object, "E_Client");
   if (ec)
     {
        UNREFD(ec, 1);
        e_object_unref(E_OBJECT(ec));
     }
   evas_object_unref(ev->comp_object);
   free(ev);
}

static void
_e_comp_object_event_add(Evas_Object *obj)
{
   E_Event_Comp_Object *ev;
   E_Client *ec;

   ev = E_NEW(E_Event_Comp_Object, 1);
   evas_object_ref(obj);
   ev->comp_object = obj;
   ec = evas_object_data_get(ev->comp_object, "E_Client");
   if (ec)
     {
        REFD(ec, 1);
        e_object_ref(E_OBJECT(ec));
     }
   ecore_event_add(E_EVENT_COMP_OBJECT_ADD, ev, _e_comp_object_event_free, NULL);
}

/////////////////////////////////////

static void
_e_comp_object_cb_agent_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   cw->obj_agent = eina_list_remove(cw->obj_agent, obj);
}

static void
_e_comp_object_cb_agent_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;
   int w, h;

   if (cw->agent_updating) return;
   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   evas_object_resize(cw->smart_obj, w, h);
}

static void
_e_comp_object_cb_agent_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;
   int x, y;

   if (cw->agent_updating) return;
   evas_object_geometry_get(obj, &x, &y, NULL, NULL);
   evas_object_move(cw->smart_obj, x, y);
}

/////////////////////////////////////

static void
_e_comp_object_cb_mirror_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   cw->obj_mirror = eina_list_remove(cw->obj_mirror, obj);
}

static void
_e_comp_object_cb_mirror_show(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if ((!cw->force_visible) && (!cw->deleted) && (!e_object_is_del(E_OBJECT(cw->ec))))
     evas_object_smart_callback_call(cw->smart_obj, "visibility_force", cw->ec);
   cw->force_visible++;
   if ((!cw->native) && cw->pending_updates && (!cw->update) && cw->real_hid)
     e_comp_object_render(cw->smart_obj);
}

static void
_e_comp_object_cb_mirror_hide(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   cw->force_visible--;
   if ((!cw->force_visible) && (!cw->deleted) && (!e_object_is_del(E_OBJECT(cw->ec))))
     evas_object_smart_callback_call(cw->smart_obj, "visibility_normal", cw->ec);
}

/////////////////////////////////////

static inline Eina_Bool
_e_comp_shaped_check(int w, int h, const Eina_Rectangle *rects, int num)
{
   if (num > 1) return EINA_TRUE;
   if ((rects[0].x == 0) && (rects[0].y == 0) &&
       ((int)rects[0].w == w) && ((int)rects[0].h == h))
     return EINA_FALSE;
   return EINA_TRUE;
}

/////////////////////////////////////

/* add a client to the layer-client list */
static void
_e_comp_object_layers_add(E_Comp_Object *cw, E_Comp_Object *above, E_Comp_Object *below, Eina_Bool prepend)
{
   E_Comp_Object *layer_cw = NULL;

   /* try to get the internal data for the layer;
    * will return NULL for fake layers (eg. wayland)
    */
   if (e_comp->layers[cw->layer].obj)
     layer_cw = evas_object_smart_data_get(e_comp->layers[cw->layer].obj);
   if (layer_cw == cw) layer_cw = NULL;
/*
   if (above)
     e_comp->layers[cw->layer].objs = eina_inlist_append_relative(e_comp->layers[cw->layer].objs, EINA_INLIST_GET(cw), EINA_INLIST_GET(cw2));
   else if (below)
     e_comp->layers[cw->layer].objs = eina_inlist_prepend_relative(e_comp->layers[cw->layer].objs, EINA_INLIST_GET(cw), EINA_INLIST_GET(cw2));
   else
     {
        if (prepend)
          e_comp->layers[cw->layer].objs = eina_inlist_prepend(e_comp->layers[cw->layer].objs, EINA_INLIST_GET(cw));
        else
          e_comp->layers[cw->layer].objs = eina_inlist_append(e_comp->layers[cw->layer].objs, EINA_INLIST_GET(cw));
     }
   e_comp->layers[cw->layer].objs_count++;
   if (!cw->ec) return;
*/
   if (above)
     {
        cw->layer = above->layer;
        e_comp->layers[above->layer].clients = eina_inlist_append_relative(e_comp->layers[above->layer].clients, EINA_INLIST_GET(cw->ec), EINA_INLIST_GET(above->ec));
        e_comp->layers[above->layer].clients_count++;
     }
   else if (below)
     {
        cw->layer = below->layer;
        e_comp->layers[below->layer].clients = eina_inlist_prepend_relative(e_comp->layers[below->layer].clients, EINA_INLIST_GET(cw->ec), EINA_INLIST_GET(below->ec));
        e_comp->layers[below->layer].clients_count++;
     }
   else
     {
        if (prepend)
          e_comp->layers[cw->layer].clients = eina_inlist_prepend(e_comp->layers[cw->layer].clients, EINA_INLIST_GET(cw->ec));
        else if (layer_cw)
          e_comp->layers[cw->layer].clients = eina_inlist_prepend_relative(e_comp->layers[cw->layer].clients, EINA_INLIST_GET(cw->ec), EINA_INLIST_GET(layer_cw->ec));
        else //this is either the layer object or a tough actin tinactin^W^W^Wfast stacking client
          e_comp->layers[cw->layer].clients = eina_inlist_append(e_comp->layers[cw->layer].clients, EINA_INLIST_GET(cw->ec));
        e_comp->layers[cw->layer].clients_count++;
     }
#ifndef E_RELEASE_BUILD
   if (layer_cw)
     {
        E_Client *below_ec = e_client_below_get(cw->ec);
        if (below_ec)
          {
             if (e_comp->layers[cw->layer].obj == below_ec->frame)
               ERR("ACK!");
          }
     }
#endif
}

static void
_e_comp_object_layers_remove(E_Comp_Object *cw)
{
   if (cw->ec && e_comp->layers[cw->layer].clients)
     {
        e_comp->layers[cw->layer].clients = eina_inlist_remove(e_comp->layers[cw->layer].clients, EINA_INLIST_GET(cw->ec));
        e_comp->layers[cw->layer].clients_count--;
     }
/*
   e_comp->layers[cw->layer].objs = eina_inlist_remove(e_comp->layers[cw->layer].objs, EINA_INLIST_GET(cw));
   e_comp->layers[cw->layer].objs_count--;
*/
}

/////////////////////////////////////
static void
_e_comp_object_updates_init(E_Comp_Object *cw)
{
   int pw, ph;

   if (cw->updates) return;
   pw = cw->ec->client.w, ph = cw->ec->client.h;
   if ((!pw) || (!ph))
     e_pixmap_size_get(cw->ec->pixmap, &pw, &ph);
   if ((!pw) || (!ph)) return;
   cw->updates = eina_tiler_new(pw, ph);
   if (cw->updates)
     eina_tiler_tile_size_set(cw->updates, 1, 1);
}


static void
_e_comp_object_alpha_set(E_Comp_Object *cw)
{
   Eina_Bool alpha;

   if (!e_pixmap_is_x(cw->ec->pixmap))
     alpha = e_pixmap_image_is_argb(cw->ec->pixmap);
   else
     alpha = cw->ec->argb;

   if (cw->blanked || cw->ns || cw->ec->shaped) alpha = EINA_TRUE;

   evas_object_image_alpha_set(cw->obj, alpha);
}

static void
_e_comp_object_shadow(E_Comp_Object *cw)
{
   if (e_client_util_shadow_state_get(cw->ec))
     edje_object_signal_emit(cw->frame_object ?: cw->shobj, "e,state,shadow,on", "e");
   else
     edje_object_signal_emit(cw->frame_object ?: cw->shobj, "e,state,shadow,off", "e");
   if (cw->frame_object)
     edje_object_signal_emit(cw->shobj, "e,state,shadow,off", "e");
   evas_object_smart_callback_call(cw->smart_obj, "shadow_change", cw->ec);
}

/* trigger e_binding from an edje signal on a client frame */
static void
_e_comp_object_cb_signal_bind(void *data, Evas_Object *obj EINA_UNUSED, const char *emission, const char *source)
{
   E_Comp_Object *cw = data;

#ifndef HAVE_WAYLAND_ONLY
   if (e_dnd_active()) return;
#endif
   if (cw->ec->iconic || cw->ec->cur_mouse_action) return;
   if (!dblequal(cw->action_client_loop_time, ecore_loop_time_get()))
     e_bindings_signal_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(cw->ec),
                              emission, source);
}

/////////////////////////////////////

/* handle evas mouse-in events on client object */
static void
_e_comp_object_cb_mouse_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_In *ev = event_info;
   E_Comp_Object *cw = data;

   if (e_pixmap_is_x(cw->ec->pixmap))
     {
        if (!e_comp_object_frame_allowed(obj)) return;
        if (E_INSIDE(ev->output.x, ev->output.y, cw->ec->client.x, cw->ec->client.y,
            cw->ec->client.w, cw->ec->client.h)) return;
     }
   if (e_grabinput_mouse_win_get() && (e_grabinput_mouse_win_get() != e_client_util_win_get(cw->ec)))
     return;
   e_client_mouse_in(cw->ec, ev->output.x, ev->output.y);
}

/* handle evas mouse-out events on client object */
static void
_e_comp_object_cb_mouse_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Out *ev = event_info;
   E_Comp_Object *cw = data;

   if (e_grabinput_mouse_win_get() && (e_grabinput_mouse_win_get() != e_client_util_win_get(cw->ec)))
     return;
   e_client_mouse_out(cw->ec, ev->output.x, ev->output.y);
}

/* handle evas mouse wheel events on client object */
static void
_e_comp_object_cb_mouse_wheel(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Wheel *ev = event_info;
   E_Comp_Object *cw = data;
   E_Binding_Event_Wheel ev2;

   if (!cw->ec) return;
   if (e_client_action_get()) return;
   e_bindings_evas_event_mouse_wheel_convert(ev, &ev2);
   e_client_mouse_wheel(cw->ec, &ev->output, &ev2);
}

/* handle evas mouse down events on client object */
static void
_e_comp_object_cb_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   E_Comp_Object *cw = data;
   E_Binding_Event_Mouse_Button ev2;

   if (!cw->ec) return;
   if (e_client_action_get()) return;
   e_bindings_evas_event_mouse_button_convert(ev, &ev2);
   e_client_mouse_down(cw->ec, ev->button, &ev->output, &ev2);
}

/* handle evas mouse up events on client object */
static void
_e_comp_object_cb_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   E_Comp_Object *cw = data;
   E_Binding_Event_Mouse_Button ev2;
   Eina_Bool acting;

   if (!cw->ec) return;
   if (e_client_action_get() && (e_client_action_get() != cw->ec)) return;
   e_bindings_evas_event_mouse_button_convert(ev, &ev2);
   acting = !!cw->ec->cur_mouse_action;
   e_client_mouse_up(cw->ec, ev->button, &ev->output, &ev2);
   if (acting && (!e_client_action_get()))
     cw->action_client_loop_time = ecore_loop_time_get();
}

/* handle evas mouse movement events on client object */
static void
_e_comp_object_cb_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev = event_info;
   E_Comp_Object *cw = data;

   if (!cw->ec) return;
   if (e_client_action_get() && (e_client_action_get() != cw->ec)) return;
   e_client_mouse_move(cw->ec, &ev->cur.output);
}
/////////////////////////////////////

/* helper function for checking compositor themes based on user-defined matches */
static Eina_Bool
_e_comp_object_shadow_client_match(const E_Client *ec, E_Comp_Match *m)
{
   if (((m->title) && (!ec->netwm.name)) ||
       ((ec->netwm.name) && (m->title) && (!e_util_glob_match(ec->netwm.name, m->title))))
     return EINA_FALSE;
   if (((m->clas) && (!ec->icccm.class)) ||
       ((ec->icccm.class) && (m->clas) && (!e_util_glob_match(ec->icccm.class, m->clas))))
     return EINA_FALSE;
   if (((m->role) && (!ec->icccm.window_role)) ||
       ((ec->icccm.window_role) && (m->role) && (!e_util_glob_match(ec->icccm.window_role, m->role))))
     return EINA_FALSE;
   if (m->primary_type)
     {
        if (ec->netwm.type)
          {
             if ((int)ec->netwm.type != m->primary_type)
               return EINA_FALSE;
          }
        else if (m->primary_type != E_WINDOW_TYPE_REAL_UNKNOWN)
          return EINA_FALSE;
     }
  if (m->borderless != 0)
    {
       int borderless = 0;

       if (e_client_util_borderless(ec))
         borderless = 1;
       if (!(((m->borderless == -1) && (!borderless)) ||
             ((m->borderless == 1) && (borderless))))
         return EINA_FALSE;
    }
  if (m->dialog != 0)
    {
       int dialog = 0;

       if (((ec->icccm.transient_for != 0) ||
            (ec->dialog)))
         dialog = 1;
       if (!(((m->dialog == -1) && (!dialog)) ||
             ((m->dialog == 1) && (dialog))))
         return EINA_FALSE;
    }
  if (m->accepts_focus != 0)
    {
       int accepts_focus = 0;

       if (ec->icccm.accepts_focus)
         accepts_focus = 1;
       if (!(((m->accepts_focus == -1) && (!accepts_focus)) ||
             ((m->accepts_focus == 1) && (accepts_focus))))
         return EINA_FALSE;
    }
  if (m->vkbd != 0)
    {
       int vkbd = 0;

       if (ec->vkbd.vkbd)
         vkbd = 1;
       if (!(((m->vkbd == -1) && (!vkbd)) ||
             ((m->vkbd == 1) && (vkbd))))
         return EINA_FALSE;
    }
  if (m->argb != 0)
    {
       if (!(((m->argb == -1) && (!ec->argb)) ||
             ((m->argb == 1) && (ec->argb))))
         return EINA_FALSE;
    }
  if (m->fullscreen != 0)
    {
       int fullscreen = ec->fullscreen;

       if (!(((m->fullscreen == -1) && (!fullscreen)) ||
             ((m->fullscreen == 1) && (fullscreen))))
         return EINA_FALSE;
    }
  if (m->modal != 0)
    {
       int modal = 0;

       if (ec->netwm.state.modal)
         modal = 1;
       if (!(((m->modal == -1) && (!modal)) ||
             ((m->modal == 1) && (modal))))
         return EINA_FALSE;
    }
  return EINA_TRUE;
}

/* function for setting up a client's compositor frame theme (cw->shobj) */
static Eina_Bool
_e_comp_object_shadow_setup(E_Comp_Object *cw)
{
   int ok = 0;
   char buf[4096];
   Eina_List *list = NULL, *l;
   E_Comp_Match *m;
   Eina_Stringshare *reshadow_group = NULL;
   Eina_Bool focus = EINA_FALSE, urgent = EINA_FALSE, skip = EINA_FALSE,
             reshadow = EINA_FALSE, no_shadow = EINA_FALSE, override;
   Eina_Stringshare *name, *title;
   E_Comp_Config *conf = e_comp_config_get();

   edje_object_file_get(cw->shobj, NULL, &reshadow_group);
   override = cw->ec->override || (cw->ec->netwm.type == E_WINDOW_TYPE_POPUP_MENU);
   /* match correct client type */
   list = override ? conf->match.overrides : conf->match.borders;
   name = cw->ec->icccm.name;
   title = cw->ec->icccm.title;
   skip = (title && (!strncmp(title, "noshadow", 8)));

   if (!skip)
     {
        EINA_LIST_FOREACH(list, l, m)
          {
             if (((m->name) && (!name)) ||
                 ((name) && (m->name) && (!e_util_glob_match(name, m->name))))
               continue;
             if (!_e_comp_object_shadow_client_match(cw->ec, m)) continue;

             focus = m->focus;
             urgent = m->urgent;
             no_shadow = m->no_shadow;
             if (m->shadow_style)
               {
                  if (!ok)
                    {
                       snprintf(buf, sizeof(buf), "e/comp/frame/%s", m->shadow_style);
                       reshadow = ok = !e_util_strcmp(reshadow_group, buf);
                       if (!ok)
                         ok = e_theme_edje_object_set(cw->shobj, "base/theme/comp", buf);
                    }
                  if (ok && m->visibility_effect)
                    eina_stringshare_refplace(&cw->visibility_effect, m->visibility_effect);
                  if (ok) break;
               }
          }
     }
   while (!ok)
     {
        if (skip || (cw->ec->e.state.video))
          {
             reshadow = ok = !e_util_strcmp(reshadow_group, "e/comp/frame/none");
             if (!ok)
               ok = e_theme_edje_object_set(cw->shobj, "base/theme/comp", "e/comp/frame/none");
          }
        if (ok) break;
        if (conf->shadow_style)
          {
             if (!ok)
               {
                  snprintf(buf, sizeof(buf), "e/comp/frame/%s", conf->shadow_style);
                  reshadow = ok = !e_util_strcmp(reshadow_group, buf);
                  if (!ok)
                    ok = e_theme_edje_object_set(cw->shobj, "base/theme/comp", buf);
               }
          }
        if (!ok)
          {
             reshadow = ok = !e_util_strcmp(reshadow_group, "e/comp/frame/default");
             if (!ok)
               ok = e_theme_edje_object_set(cw->shobj, "base/theme/comp", "e/comp/frame/default");
          }
        break;
     }
   /* reshadow means this entire function call has been a no-op since we're re-setting the current style */
   if (reshadow)
     {
        if (cw->zoomap_disabled)
          {
             if (cw->frame_object && (cw->zoomobj && e_zoomap_child_get(cw->zoomobj) == cw->frame_object)) return EINA_FALSE;
          }
        else
          {
             if (cw->frame_object && (edje_object_part_swallow_get(cw->shobj, "e.swallow.content") == cw->frame_object)) return EINA_FALSE;
          }
     }
   if (override)
     {
        if ((!cw->ec->shaped) && (!no_shadow) && (!cw->ec->argb))
          edje_object_signal_emit(cw->shobj, "e,state,shadow,on", "e");
        else
          edje_object_signal_emit(cw->shobj, "e,state,shadow,off", "e");
        evas_object_smart_callback_call(cw->smart_obj, "shadow_change", cw->ec);
     }
   else
     {
        if (no_shadow)
          {
             edje_object_signal_emit(cw->shobj, "e,state,shadow,off", "e");
             evas_object_smart_callback_call(cw->smart_obj, "shadow_change", cw->ec);
          }
        else
          _e_comp_object_shadow(cw);
     }

   if (focus || cw->ec->focused || override)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,focused", "e");
   else
     e_comp_object_signal_emit(cw->smart_obj, "e,state,unfocused", "e");
   if (urgent || cw->ec->urgent)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,urgent", "e");
   else
     e_comp_object_signal_emit(cw->smart_obj, "e,state,not_urgent", "e");
   if (cw->ec->shaded)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,shaded", "e");
   if (cw->ec->sticky)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,sticky", "e");
   if (cw->ec->hung)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,hung", "e");
   /* visibility must always be enabled for re_manage clients to prevent
    * pop-in animations every time the user sees a persistent client again;
    * applying visibility for iconic clients prevents the client from getting
    * stuck as hidden
    */
   if (cw->visible || cw->ec->iconic || cw->ec->re_manage)
     {
        if ((cw->w > 0) && (cw->h > 0))
          e_comp_object_signal_emit(cw->smart_obj, "e,state,visible", "e");
     }
   else
     e_comp_object_signal_emit(cw->smart_obj, "e,state,hidden", "e");
   if (e_comp_object_frame_allowed(cw->smart_obj))
     e_comp_object_signal_emit(cw->smart_obj, "e,state,focus,enabled", "e");
   else
     e_comp_object_signal_emit(cw->smart_obj, "e,state,focus,disabled", "e");

   /* breaks animation counter */
   //if (cw->ec->iconic)
     //e_comp_object_signal_emit(cw->smart_obj, "e,action,iconify", "e");
   if ((!cw->zoomap_disabled) && (cw->zoomobj))
     e_zoomap_child_set(cw->zoomobj, NULL);
   if (cw->frame_object)
     {
        edje_object_part_swallow(cw->frame_object, "e.swallow.client", cw->obj);
        edje_object_part_swallow(cw->frame_object, "e.swallow.icon", cw->frame_icon);
        edje_object_part_swallow(cw->frame_object, "e.swallow.volume", cw->frame_volume);
        if (cw->zoomap_disabled)
          edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->frame_object);
        else if (cw->zoomobj)
          {
             e_zoomap_child_set(cw->zoomobj, cw->frame_object);
             edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->zoomobj);
          }
        no_shadow = 1;
     }
   else
     {
        no_shadow = 1;
        if (cw->zoomobj)
          {
             e_zoomap_child_set(cw->zoomobj, cw->obj);
             edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->zoomobj);
          }
        else
          edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->obj);
     }
   if (cw->input_objs)
     evas_object_pass_events_set(cw->obj, 1);
   else
     evas_object_pass_events_set(cw->obj, 0);
#ifdef BORDER_ZOOMAPS
   if (cw->zoomobj) e_zoomap_child_edje_solid_setup(cw->zoomobj);
#endif
   return EINA_TRUE;
}

/////////////////////////////////////////////

static void
_e_comp_object_animating_begin(E_Comp_Object *cw)
{
   cw->animating++;
   if (cw->animating == 1)
     {
        e_comp->animating++;
        REFD(cw->ec, 2);
        e_object_ref(E_OBJECT(cw->ec));
        evas_object_image_border_center_fill_set(cw->obj, EVAS_BORDER_FILL_DEFAULT);
     }
}

static Eina_Bool
_e_comp_object_animating_end(E_Comp_Object *cw)
{
   if (cw->animating)
     {
        cw->animating--;
        if (!cw->animating)
          {
             e_comp->animating--;
             cw->showing = 0;
             evas_object_image_border_center_fill_set(cw->obj, EVAS_BORDER_FILL_SOLID);
             UNREFD(cw->ec, 2);
             /* remove ref from animation start, account for possibility of deletion from unref */
             return !!e_object_unref(E_OBJECT(cw->ec));
          }
     }
   return EINA_TRUE;
}

/* handle the end of a compositor animation */
static void
_e_comp_object_done_defer(void *data, Evas_Object *obj EINA_UNUSED, const char *emission, const char *source EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   //INF("DONE DEFER %p: %dx%d - %s", cw->ec, cw->w, cw->h, emission);
   /* visible clients which have never been sized are a bug */
   if ((!cw->ec->new_client) && (!cw->ec->changes.size) && ((cw->w < 0) || (cw->h < 0)) && (!strcmp(emission, "e,action,show,done")))
     ERR("ACK!");
   if (!_e_comp_object_animating_end(cw)) return;
   if (cw->animating) return;
   /* hide only after animation finishes to guarantee a full run of the animation */
   if (cw->defer_hide && ((!strcmp(emission, "e,action,hide,done")) || (!strcmp(emission, "e,action,done"))))
     evas_object_hide(cw->smart_obj);
   else if (!cw->deleted)
     {
        e_comp_shape_queue();
        if (cw->visible && cw->updates_exist)
          e_comp_object_render_update_add(cw->smart_obj);
     }
}

/* run a visibility compositor effect if available, return false if object is dead */
static Eina_Bool
_e_comp_object_effect_visibility_start(E_Comp_Object *cw, Eina_Bool state)
{
   int x, y, zw, zh;

   if ((!cw->visibility_effect) || (!e_comp_object_effect_allowed_get(cw->smart_obj))) return EINA_TRUE;;
   if (!cw->effect_running)
     _e_comp_object_animating_begin(cw);
   if (!e_comp_object_effect_stop(cw->smart_obj, _e_comp_object_done_defer))
     return _e_comp_object_animating_end(cw);
   if (!e_comp_object_effect_set(cw->smart_obj, cw->visibility_effect))
     return _e_comp_object_animating_end(cw);
   /* mouse position is not available for some windows under X11
    * only fetch pointer position if absolutely necessary
    */
   if (edje_object_data_get(cw->effect_obj, "need_pointer") &&
       (e_comp->comp_type == E_PIXMAP_TYPE_X))
     ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   else
     evas_pointer_canvas_xy_get(e_comp->evas, &x, &y);
   x -= cw->ec->x;
   y -= cw->ec->y;
   if (cw->ec->zone)
     zw = cw->ec->zone->w, zh = cw->ec->zone->h;
   else
     {
        E_Zone *zone;

        zone = e_comp_object_util_zone_get(cw->smart_obj);
        if (!zone) zone = e_zone_current_get();
        zw = zone->w, zh = zone->h;
     }
   e_comp_object_effect_params_set(cw->smart_obj, 1, (int[]){cw->ec->x, cw->ec->y,
      cw->ec->w, cw->ec->h, zw, zh, x, y}, 8);
   e_comp_object_effect_params_set(cw->smart_obj, 0, (int[]){state}, 1);
   e_comp_object_effect_start(cw->smart_obj, _e_comp_object_done_defer, cw);
   return EINA_TRUE;
}
/////////////////////////////////////////////

/* create necessary objects for clients that e manages */
static void
_e_comp_object_setup(E_Comp_Object *cw)
{
   cw->clip = evas_object_rectangle_add(e_comp->evas);
   evas_object_resize(cw->clip, 999999, 999999);
   evas_object_smart_member_add(cw->clip, cw->smart_obj);
   cw->effect_obj = edje_object_add(e_comp->evas);
   evas_object_move(cw->effect_obj, cw->x, cw->y);
   evas_object_clip_set(cw->effect_obj, cw->clip);
   evas_object_smart_member_add(cw->effect_obj, cw->smart_obj);
   e_theme_edje_object_set(cw->effect_obj, "base/theme/comp", "e/comp/effects/none");
   cw->shobj = edje_object_add(e_comp->evas);
   evas_object_data_set(cw->shobj, "comp_smart_obj", cw->smart_obj);
   edje_object_part_swallow(cw->effect_obj, "e.swallow.content", cw->shobj);
   edje_object_signal_callback_add(cw->shobj, "e,action,*,done", "e", _e_comp_object_done_defer, cw);

   /* name objects appropriately for nicer printing when using e_comp_util_wins_print() */
   if (cw->ec->override)
     {
        evas_object_name_set(cw->shobj, "cw->shobj::WINDOW");
        evas_object_name_set(cw->effect_obj, "cw->effect_obj::WINDOW");
        evas_object_name_set(cw->clip, "cw->clip::WINDOW");
     }
   else if (!cw->ec->input_only)
     {
        evas_object_name_set(cw->shobj, "cw->shobj::CLIENT");
        evas_object_name_set(cw->effect_obj, "cw->effect_obj::CLIENT");
        evas_object_name_set(cw->clip, "cw->clip::CLIENT");
     }
   cw->real_hid = !cw->ec->input_only;
   if (!cw->ec->input_only)
     {
        //e_util_size_debug_set(cw->clip, 1);
        e_util_size_debug_set(cw->effect_obj, 1);
        //e_util_size_debug_set(cw->shobj, 1);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_IN, _e_comp_object_cb_mouse_in, cw);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_OUT, _e_comp_object_cb_mouse_out, cw);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_DOWN, _e_comp_object_cb_mouse_down, cw);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_UP, _e_comp_object_cb_mouse_up, cw);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_MOVE, _e_comp_object_cb_mouse_move, cw);
        evas_object_event_callback_add(cw->smart_obj, EVAS_CALLBACK_MOUSE_WHEEL, _e_comp_object_cb_mouse_wheel, cw);
     }
}

static void
_e_comp_object_mirror_pixels_get(void *data, Evas_Object *obj)
{
   E_Comp_Object *cw = data;
   E_Client *ec = cw->ec;
   int pw, ph;

   if ((!ec->pixmap) || (!e_pixmap_size_get(ec->pixmap, &pw, &ph)))
     {
        evas_object_image_data_set(obj, NULL);
        return;
     }

   /* This is a big fat hack - ideally we're already on this list
    * if the parent is visible, but there are some circumstances
    * where a client receives damage while visible but its own pixels_get
    * callback doesn't fire (new damage during the start frame of a desk
    * switch animation).
    * Thus we can't make this addition conditional on visibility or we can
    * (under wayland at least) lose a frame callback and stop updating.
    *
    * e_comp_client_post_update_add() prevents clients from being
    * on the list twice, so this is theoretically not harmful.
    */
   e_comp_client_post_update_add(ec);

   if (cw->native) return;

   evas_object_image_data_set(obj, e_pixmap_image_data_get(cw->ec->pixmap));
}

/////////////////////////////////////////////

/* for fast path evas rendering; only called during render */
static void
_e_comp_object_pixels_get(void *data, Evas_Object *obj)
{
   E_Comp_Object *cw = data;
   E_Client *ec = cw->ec;
   int pw, ph;

   if ((!ec->pixmap) || (!e_pixmap_size_get(ec->pixmap, &pw, &ph)))
     {
        evas_object_image_data_set(obj, NULL);
        return;
     }
   //INF("PIXEL GET %p: %dx%d || %dx%d", ec, ec->w, ec->h, pw, ph);

   /* queue another render if client is still dirty; cannot refresh here. */
   if (e_pixmap_dirty_get(ec->pixmap) && e_pixmap_size_get(ec->pixmap, &pw, &ph))
     {
        e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
        /* if updates for existing pixmap don't exist then avoid unsetting existing image */
        if ((!cw->pending_updates) || eina_tiler_empty(cw->pending_updates)) return;
     }

   if (cw->native)
     {
        E_FREE_FUNC(cw->pending_updates, eina_tiler_free);
        e_comp_client_post_update_add(cw->ec);
     }
   else if (e_comp_object_render(ec->frame))
     {
        /* apply shape mask if necessary */
        if ((!cw->native) && (ec->shaped || ec->shape_changed))
          e_comp_object_shape_apply(ec->frame);
        ec->shape_changed = 0;
     }
   if (e_object_is_del(E_OBJECT(ec))) return;
   /* shaped clients get precise mouse events to handle transparent pixels */
   evas_object_precise_is_inside_set(cw->obj, ec->shaped);

   //INF("%p PX(%dx%d) EC(%dx%d) CW(%dx%d)", ec, pw, ph, ec->w, ec->h, cw->w, cw->h);
   //e_comp_object_frame_wh_adjust(cw->smart_obj, pw, ph, &pw, &ph);
   //if ((ec->w != pw) || (ec->h != ph))
     //{
        ///* DO NOT RESIZE HERE. */
        //INF("CW RSZ FIX: %dx%d -> %dx%d", ec->w, ec->h, pw, ph);
        //ec->w = pw, ec->h = ph;
        //ec->changes.size = 1;
        //EC_CHANGED(ec);
     //}
}

/////////////////////////////////////////////

static void
_e_comp_object_ssd_mouse_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Comp_Object *cw = data;

   evas_object_smart_callback_call(cw->smart_obj, "mouse_in", event_info);
}

static void
_e_comp_object_ssd_mouse_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Comp_Object *cw = data;

   evas_object_smart_callback_call(cw->smart_obj, "mouse_out", event_info);
}

/////////////////////////////////////////////

static void
_e_comp_object_client_pending_resize_add(E_Client *ec,
                                         int w,
                                         int h,
                                         unsigned int serial)
{
   E_Client_Pending_Resize *pnd;

   pnd = E_NEW(E_Client_Pending_Resize, 1);
   if (!pnd) return;
   pnd->w = w;
   pnd->h = h;
   pnd->serial = serial;
   ec->pending_resize = eina_list_append(ec->pending_resize, pnd);
}

static void
_e_comp_intercept_move(void *data, Evas_Object *obj, int x, int y)
{
   E_Comp_Object *cw = data;
   int ix, iy, fx, fy;

   /* if frame_object does not exist, client_inset indicates CSD.
    * this means that ec->client matches cw->x/y, the opposite
    * of SSD.
    */
   fx = (!cw->frame_object) * cw->client_inset.l;
   fy = (!cw->frame_object) * cw->client_inset.t;
   if ((cw->x == x + fx) && (cw->y == y + fy))
     {
        if ((cw->ec->x != x) || (cw->ec->y != y))
          {
             /* handle case where client tries to move to position and back very quickly */
             cw->ec->x = x, cw->ec->y = y;
             cw->ec->client.x = x + cw->client_inset.l;
             cw->ec->client.y = y + cw->client_inset.t;
          }
        return;
     }
   if (!cw->ec->maximize_override)
     {
        /* prevent moving in some directions while directionally maximized */
        if ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_VERTICAL)
          y = cw->y;
        if ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_HORIZONTAL)
          x = cw->x;
     }
   ix = x + cw->client_inset.l;
   iy = y + cw->client_inset.t;
   if (cw->ec->maximized && (!cw->ec->maximize_override) && ((cw->ec->x != x) || (cw->ec->y != y)) &&
       ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) != E_MAXIMIZE_VERTICAL) &&
       ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) != E_MAXIMIZE_HORIZONTAL))
     {
        /* prevent moving at all if move isn't allowed in current maximize state */
        if ((!e_config->allow_manip) && ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_BOTH)) return;
        /* queue unmaximize if we are allowing move and update unmaximize geometry */
        if ((!cw->ec->shading) && (!cw->ec->shaded))
          {
             cw->ec->changes.need_unmaximize = 1;
             cw->ec->saved.x = ix - cw->ec->zone->x;
             cw->ec->saved.y = iy - cw->ec->zone->y;
             cw->ec->saved.w = cw->ec->client.w;
             cw->ec->saved.h = cw->ec->client.h;
             EC_CHANGED(cw->ec);
             return;
          }
        return;
     }
   /* only update during resize if triggered by resize */
   if (e_client_util_resizing_get(cw->ec) && (!cw->force_move)) return;
   cw->ec->x = x, cw->ec->y = y;
   if (cw->ec->new_client)
     {
        /* don't actually do anything until first client idler loop */
        if (!cw->ec->placed)
          cw->ec->placed = ((!cw->ec->dialog) && (!cw->ec->parent));
        cw->ec->changes.pos = 1;
        EC_CHANGED(cw->ec);
     }
   else
     {
        /* only update xy position of client to avoid invalid
         * first damage region if it is not a new_client. */
        cw->ec->placed = 1;
        if (!cw->ec->shading)
          {
             cw->ec->client.x = ix;
             cw->ec->client.y = iy;
          }
        /* flip SSD->CSD */
        if (!cw->frame_object)
          x = ix, y = iy;
        evas_object_move(obj, x, y);
     }
}

static void
_e_comp_intercept_resize(void *data, Evas_Object *obj, int w, int h)
{
   E_Comp_Object *cw = data;
   int pw = 0, ph = 0, fw, fh, iw, ih, prev_w, prev_h, x, y;


   if ((w < 1) || (h < 1)) return;
   /* if frame_object does not exist, client_inset indicates CSD.
    * this means that ec->client matches cw->w/h, the opposite
    * of SSD.
    */
   fw = (!cw->frame_object) * (-cw->client_inset.l - cw->client_inset.r);
   fh = (!cw->frame_object) * (-cw->client_inset.t - cw->client_inset.b);
   if ((cw->w == w + fw) && (cw->h == h + fh))
     {
        if (cw->ec->shading || cw->ec->shaded) return;
        if (((cw->ec->w != w) || (cw->ec->h != h)) ||
            (cw->ec->client.w != w - cw->client_inset.l - cw->client_inset.r) ||
            (cw->ec->client.h != h - cw->client_inset.t - cw->client_inset.b))
          {
             /* handle case where client tries to resize itself and back very quickly */
             cw->ec->w = w, cw->ec->h = h;
             cw->ec->client.w = w - cw->client_inset.l - cw->client_inset.r;
             cw->ec->client.h = h - cw->client_inset.t - cw->client_inset.b;
             evas_object_smart_callback_call(obj, "client_resize", NULL);
          }
        return;
     }
   /* guarantee that fullscreen is fullscreen */
   if (cw->ec->fullscreen && ((w != cw->ec->zone->w) || (h != cw->ec->zone->h)))
     return;
   /* calculate client size */
   iw = w - cw->client_inset.l - cw->client_inset.r;
   ih = h - cw->client_inset.t - cw->client_inset.b;
   if (cw->ec->maximized && (!cw->ec->maximize_override) && ((cw->ec->w != w) || (cw->ec->h != h)))
     {
        /* prevent resizing while maximized depending on direction and config */
        if ((!e_config->allow_manip) && ((cw->ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_BOTH)) return;
        if ((!cw->ec->shading) && (!cw->ec->shaded))
          {
             Eina_Bool reject = EINA_FALSE;
             if (cw->ec->maximized & E_MAXIMIZE_VERTICAL)
               {
                  if (cw->ec->client.h != ih)
                    {
                       cw->ec->saved.h = ih;
                       cw->ec->saved.y = cw->ec->client.y - cw->ec->zone->y;
                       reject = cw->ec->changes.need_unmaximize = 1;
                    }
               }
             if (cw->ec->maximized & E_MAXIMIZE_HORIZONTAL)
               {
                  if (cw->ec->client.w != iw)
                    {
                       cw->ec->saved.w = iw;
                       cw->ec->saved.x = cw->ec->client.x - cw->ec->zone->x;
                       reject = cw->ec->changes.need_unmaximize = 1;
                    }
               }
             if (reject)
               {
                  EC_CHANGED(cw->ec);
                  return;
               }
          }
     }
   if (cw->ec->new_client || (!cw->ec->visible) || (!cw->effect_obj))
     {
        /* do nothing until client idler loops */
        if (!cw->ec->maximized)
          {
             /* NO. DO NOT DO THIS OR YOU WILL BREAK WINDOW RESIZING. */
             //if ((w != cw->ec->w) || (h != cw->ec->h))
               {
                  cw->ec->w = w, cw->ec->h = h;
                  cw->ec->changes.size = 1;
                  EC_CHANGED(cw->ec);
               }
          }
        return;
     }
   if ((!cw->ec->internal) && e_client_util_resizing_get(cw->ec) && cw->ec->netwm.sync.request &&
       ((cw->ec->w != w) || (cw->ec->h != h)))
     {
        /* this is ugly. */
        //INF("PENDING %dx%d", iw, ih);
        /* netwm sync resizes queue themselves and then trigger later on */
        _e_comp_object_client_pending_resize_add(cw->ec, iw, ih, cw->ec->netwm.sync.serial);
     }
   cw->ec->w = w, cw->ec->h = h;
   if ((!cw->ec->shading) && (!cw->ec->shaded))
     {
        /* client geom never changes when shading since the client is never altered */
        INF("%p: CUR(%dx%d) || REQ(%dx%d)", cw->ec, cw->ec->client.w, cw->ec->client.h, iw, ih);
        cw->ec->client.w = iw;
        cw->ec->client.h = ih;
        if ((cw->ec->client.w < 0) || (cw->ec->client.h < 0)) ERR("WTF");
     }
   if ((!cw->ec->input_only) && cw->redirected && (!cw->ec->shading) && (!cw->ec->shaded) &&
       (e_pixmap_dirty_get(cw->ec->pixmap) || (!e_pixmap_size_get(cw->ec->pixmap, &pw, &ph))))
     {
        if (e_comp->comp_type != E_PIXMAP_TYPE_X) return;
        if (e_object_is_del(E_OBJECT(cw->ec))) return;
        /* shapeless clients smh */
        if (cw->ec->shaped && (!cw->ec->shape_rects)) return;
        /* pending shape change gtfo */
        if (!cw->ec->changes.shape)
          {
             /* client can't be resized if its pixmap isn't usable, try again */
             e_pixmap_dirty(cw->ec->pixmap);
             if (e_comp->nocomp)
               e_pixmap_refresh(cw->ec->pixmap);
             else
               {
                  e_comp_object_render_update_add(obj);
                  e_comp_render_queue();
               }
          }
        cw->ec->changes.size = 1;
        EC_CHANGED(cw->ec);
        return;
     }
   if (e_pixmap_failures_get(cw->ec->pixmap) && (!cw->redirected))
     {
        e_comp_object_redirected_set(obj, 1);
        return;
     }
   prev_w = cw->w, prev_h = cw->h;
   e_comp_object_frame_wh_adjust(obj, 0, 0, &fw, &fh);
   /* check shading and clamp to pixmap size for regular clients */
   if ((!cw->ec->shading) && (!cw->ec->shaded) && (!cw->ec->input_only) && (!cw->ec->override) &&
       (((w - fw != pw) || (h - fh != ph))))
     {
        //INF("CALLBACK: REQ(%dx%d) != CUR(%dx%d)", w - fw, h - fh, pw, ph);
        evas_object_smart_callback_call(obj, "client_resize", NULL);
        /* flip for CSD */
        if (cw->frame_object || cw->ec->input_only)
          e_comp_object_frame_wh_adjust(obj, pw, ph, &w, &h);
        else
          w = pw, h = ph;
        if ((cw->w == w) && (cw->h == h))
          {
             /* going to be a noop resize which won't trigger smart resize */
             RENDER_DEBUG("DAMAGE RESIZE(%p): %dx%d", cw->ec, cw->ec->client.w, cw->ec->client.h);
             if (cw->updates) eina_tiler_area_size_set(cw->updates, cw->ec->client.w, cw->ec->client.h);
          }
        evas_object_resize(obj, w, h);
     }
   else
     {
        /* flip for CSD */
        if ((!cw->frame_object) && (!cw->ec->input_only))
          w = pw, h = ph;
        /* "just do it" for overrides */
        //INF("INTERCEPT %dx%d", w, h);
        evas_object_resize(obj, w, h);
     }
   if (!cw->ec->override)
     {
        /* shape probably changed for non-overrides */
        cw->ec->need_shape_merge |= cw->ec->shaped || cw->ec->shaped_input;
        cw->ec->need_shape_export |= cw->ec->shaped;
        if (cw->ec->shaped || cw->ec->shaped_input)
          EC_CHANGED(cw->ec);
     }

   /* this fixes positioning jiggles when using a resize mode
    * which also changes the client's position
    */
   cw->force_move = 1;
   if (cw->frame_object)
     x = cw->x, y = cw->y;
   else
     x = cw->ec->x, y = cw->ec->y;
   switch (cw->ec->resize_mode)
     {
      case E_POINTER_RESIZE_BL:
      case E_POINTER_RESIZE_L:
        evas_object_move(obj, x + prev_w - cw->w, y);
        break;
      case E_POINTER_RESIZE_TL:
        evas_object_move(obj, x + prev_w - cw->w, y + prev_h - cw->h);
        break;
      case E_POINTER_RESIZE_T:
      case E_POINTER_RESIZE_TR:
        evas_object_move(obj, x, y + prev_h - cw->h);
        break;
      default:
        break;
     }
   if (cw->ec->internal_elm_win && (!cw->ec->moving) && (!e_client_util_resizing_get(cw->ec)) &&
       (!cw->ec->fullscreen) && (!cw->ec->maximized) &&
       e_win_centered_get(cw->ec->internal_elm_win))
     {
        e_comp_object_util_center(obj);
     }
   cw->force_move = 0;
}

static void
_e_comp_intercept_layer_set(void *data, Evas_Object *obj, int layer)
{
   E_Comp_Object *cw = data;
   unsigned int l = e_comp_canvas_layer_map(layer);
   int oldraise;
   E_Client *ec;

   if (cw->ec->layer_block)
     {
        /* doing a compositor effect, follow directions */
        evas_object_layer_set(obj, layer);
        if (layer == cw->ec->layer) //trying to put layer back
          {
             if (cw->visible)
               {
                  e_comp_shape_queue();
                  e_comp_render_queue();
               }
             ec = e_client_above_get(cw->ec);
             if (ec && (evas_object_layer_get(ec->frame) != evas_object_layer_get(obj)))
               {
                  ec = e_client_below_get(cw->ec);
                  if (ec && (evas_object_layer_get(ec->frame) == evas_object_layer_get(cw->smart_obj)))
                    {
                       evas_object_stack_above(obj, ec->frame);
                       return;
                    }
                  ec = NULL;
               }
             if (ec && (cw->ec->parent == ec))
               evas_object_stack_above(obj, ec->frame);
             else
               evas_object_stack_below(obj, ec ? ec->frame : e_comp->layers[cw->layer].obj);
          }
        return;
     }
   if (cw->layer == l) return;
   if (e_comp_canvas_client_layer_map(layer) == 9999)
     return; //invalid layer for clients not doing comp effects
   if (cw->ec->fullscreen)
     {
        cw->ec->saved.layer = layer;
        return;
     }
   oldraise = e_config->transient.raise;
   _e_comp_object_layers_remove(cw);
   /* clamp to valid client layer */
   layer = e_comp_canvas_client_layer_map_nearest(layer);
   ec = cw->ec;
   ec->layer = layer;
   if (ec->stack.prev || ec->stack.next)
     {
        if (ec->stack.ignore == 0)
          {
             Eina_List *ll, *list = e_client_stack_list_prepare(ec);
             E_Client *child;

             EINA_LIST_FOREACH(list, ll, child)
               {
                  if (child == ec) continue;
                  evas_object_layer_set(child->frame, layer);
               }
             e_client_stack_list_finish(list);
             evas_object_raise(ec->frame);
          }
     }
   else
     {
        if (e_config->transient.layer)
          {
             E_Client *child;
             Eina_List *list = eina_list_clone(cw->ec->transients);

             /* We need to set raise to one, else the child won't
              * follow to the new layer. It should be like this,
              * even if the user usually doesn't want to raise
              * the transients.
              */
             e_config->transient.raise = 1;
             EINA_LIST_FREE(list, child)
               evas_object_layer_set(child->frame, layer);
          }
     }
   if (!cw->ec->override)
     {
        /* set client stacking hints based on layer */
        if (layer == E_LAYER_CLIENT_BELOW)
          e_hints_window_stacking_set(cw->ec, E_STACKING_BELOW);
        else if (layer == E_LAYER_CLIENT_ABOVE)
          e_hints_window_stacking_set(cw->ec, E_STACKING_ABOVE);
        else
          e_hints_window_stacking_set(cw->ec, E_STACKING_NONE);
     }

   e_config->transient.raise = oldraise;
   cw->layer = e_comp_canvas_layer_map(layer);
   _e_comp_object_layers_add(cw, NULL, NULL, 0);
   //if (cw->ec->new_client)
     //INF("CLIENT STACKED %p: %u", cw->ec, layer);
   evas_object_layer_set(obj, layer);
   if (!e_comp->layers[cw->layer].obj) return; //this is a layer marker
   evas_object_stack_below(obj, e_comp->layers[cw->layer].obj);
   if (evas_object_below_get(obj) == e_comp->layers[cw->layer].obj)
     {
        /* can't stack a client above its own layer marker */
        ERR("STACKING ERROR!!!");
     }
   if (!cw->visible) return;
   e_comp_render_queue();
   e_comp_shape_queue();
}

typedef void (*E_Comp_Object_Stack_Func)(Evas_Object *obj, Evas_Object *stack);

static void
_e_comp_intercept_stack_helper(E_Comp_Object *cw, Evas_Object *stack, E_Comp_Object_Stack_Func stack_cb)
{
   E_Comp_Object *cw2 = NULL;
   E_Client *ecstack;
   short layer;
   Evas_Object *o = stack;
   Eina_Bool raising = stack_cb == evas_object_stack_above;

   if (cw->ec->layer_block)
     {
        /* obey compositor effects! */
        if (cw->ec->layer == evas_object_layer_get(cw->smart_obj))
          evas_object_data_set(cw->smart_obj, "client_restack", (void*)1);
        stack_cb(cw->smart_obj, stack);
        if (cw->ec->layer == evas_object_layer_get(cw->smart_obj))
          evas_object_data_del(cw->smart_obj, "client_restack");
        return;
     }
   /* assume someone knew what they were doing during client init */
   if (cw->ec->new_client)
     layer = cw->ec->layer;
   else
     layer = evas_object_layer_get(stack);
   ecstack = e_client_below_get(cw->ec);
   if (layer != e_comp_canvas_layer_map_to(cw->layer))
     {
        /* some FOOL is trying to restack a layer marker */
        if (cw->smart_obj == e_comp->layers[cw->layer].obj) return;
        evas_object_layer_set(cw->smart_obj, layer);
        /* we got our layer wrangled, return now! */
        if (layer != e_comp_canvas_layer_map_to(cw->layer)) return;
     }

   /* check if we're stacking below another client */
   cw2 = evas_object_data_get(o, "comp_obj");
   while (!cw2)
     {
        /* check for non-client layer object */
        if (!e_util_strcmp(evas_object_name_get(o), "layer_obj"))
          break;
        /* find an existing client to use for layering
         * by walking up the object stack
         *
         * this is guaranteed to be pretty quick since we'll either:
         * - run out of client layers
         * - find a stacking client
         */
        o = evas_object_above_get(o);
        if ((!o) || (o == cw->smart_obj)) break;
        if (evas_object_layer_get(o) != layer)
          {
             /* reached the top client layer somehow
              * use top client object
              */
             o = e_comp->layers[e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO)].obj;
          }
        if (!o)
          /* top client layer window hasn't been stacked yet. this probably shouldn't happen?
           * return here since the top client layer window
           */
          {
             E_Client *ec;

             ec = e_client_top_get();
             if (ec)
               o = ec->frame;
             //else //wat
          }
        if (o) cw2 = evas_object_data_get(o, "comp_obj");
     }


   /* remove existing layers */
   _e_comp_object_layers_remove(cw);
   if (cw2)
     {
        if (o == stack) //if stacking above, cw2 is above; else cw2 is below
          _e_comp_object_layers_add(cw, raising ? cw2 : NULL, raising ? NULL : cw2, 0);
        else if (o == cw->smart_obj) //prepend (lower) if not stacking above
          _e_comp_object_layers_add(cw, NULL, NULL, !raising);
        else //if no stacking objects found, either raise or lower
          _e_comp_object_layers_add(cw, raising ? NULL : cw2, raising ? cw2 : NULL, 0);
     }
   else
     _e_comp_object_layers_add(cw, NULL, NULL, 0);
   /* set restack if stacking has changed */
   if (cw->ec->new_client || (!ecstack) || (ecstack->frame != o))
     evas_object_data_set(cw->smart_obj, "client_restack", (void*)1);
   stack_cb(cw->smart_obj, stack);
   if (e_comp->layers[cw->layer].obj)
     if (evas_object_below_get(cw->smart_obj) == e_comp->layers[cw->layer].obj)
       {
          ERR("STACKING ERROR!!!");
       }
   if (cw->ec->new_client || (!ecstack) || (ecstack->frame != o))
     evas_object_data_del(cw->smart_obj, "client_restack");
   if (!cw->visible) return;
   e_comp_render_queue();
   e_comp_shape_queue();
}

static void
_e_comp_intercept_stack_above(void *data, Evas_Object *obj, Evas_Object *above)
{
   EINA_SAFETY_ON_TRUE_RETURN(obj == above);
   if (evas_object_below_get(obj) == above) return;
   _e_comp_intercept_stack_helper(data, above, evas_object_stack_above);
}

static void
_e_comp_intercept_stack_below(void *data, Evas_Object *obj, Evas_Object *below)
{
   EINA_SAFETY_ON_TRUE_RETURN(obj == below);
   if (evas_object_above_get(obj) == below) return;
   _e_comp_intercept_stack_helper(data, below, evas_object_stack_below);
}

static void
_e_comp_intercept_lower(void *data, Evas_Object *obj)
{
   E_Comp_Object *cw = data;
   Evas_Object *o;

   if (cw->ec->layer_block)
     {
        evas_object_lower(obj);
        return;
     }
   if (!EINA_INLIST_GET(cw->ec)->prev) return; //already lowest on layer
   o = evas_object_below_get(obj);
   _e_comp_object_layers_remove(cw);
   /* prepend to client list since this client should be the first item now */
   _e_comp_object_layers_add(cw, NULL, NULL, 1);
   if (evas_object_layer_get(o) != evas_object_layer_get(obj)) return; //already at bottom!
   if (obj == e_comp->layers[cw->layer].obj) return; //never lower a layer marker!
   evas_object_data_set(obj, "client_restack", (void*)1);
   evas_object_lower(obj);
   evas_object_data_del(obj, "client_restack");
   if (!cw->visible) return;
   e_comp_render_queue();
   e_comp_shape_queue();
}

static void
_e_comp_intercept_raise(void *data, Evas_Object *obj)
{
   E_Comp_Object *cw = data;
   Evas_Object *o;

   if (cw->ec->layer_block)
     {
        evas_object_raise(obj);
        return;
     }
   if (!EINA_INLIST_GET(cw->ec)->next) return;//already highest on layer
   o = evas_object_above_get(obj);
   {
      E_Client *ecabove = e_client_above_get(cw->ec);
      if (ecabove && (ecabove->frame == e_comp->layers[cw->layer].obj) &&
          (ecabove->frame == o)) return; //highest below marker
   }
   if (evas_object_layer_get(o) != evas_object_layer_get(obj)) return; //already at top!
   if (obj == e_comp->layers[cw->layer].obj) //never raise a non-layer marker!
     evas_object_raise(obj);
   else
     {
        Evas_Object *op;

        /* still stack below override below the layer marker */
        for (op = o = e_comp->layers[cw->layer].obj;
             o && o != e_comp->layers[cw->layer - 1].obj;
             op = o, o = evas_object_below_get(o))
          {
             E_Client *ec;

             ec = e_comp_object_client_get(o);
             if (ec && (!ec->override)) break;
          }
        evas_object_stack_below(obj, op);
        if (e_client_focus_track_enabled())
          e_client_raise_latest_set(cw->ec); //modify raise list if necessary
     }
   if (!cw->visible) return;
   e_comp_render_queue();
   e_comp_shape_queue();
}

static void
_e_comp_intercept_hide(void *data, Evas_Object *obj)
{
   E_Comp_Object *cw = data;

   if (cw->ec->fullscreen)
     {
        cw->ec->desk->fullscreen_clients =
          eina_list_remove(cw->ec->desk->fullscreen_clients, cw->ec);
     }
   if (cw->ec->hidden)
     {
        /* hidden flag = just do it */
        evas_object_hide(obj);
        return;
     }

   if (cw->ec->input_only)
     {
        /* input_only = who cares */
        evas_object_hide(obj);
        return;
     }
   /* already hidden or currently animating */
   if ((!cw->visible) || (cw->animating && (!cw->showing) && (!cw->ec->iconic))) return;

   /* don't try hiding during shutdown */
   cw->defer_hide |= stopping;
   if (!cw->defer_hide)
     {
        if ((!cw->ec->iconic) && (!cw->ec->override))
          /* unset delete requested so the client doesn't break */
          cw->ec->delete_requested = 0;
        if ((!cw->animating) || cw->showing || cw->ec->iconic)
          {
             if (cw->ec->iconic)
               e_comp_object_signal_emit(obj, "e,action,iconify", "e");
             if ((!cw->ec->iconic) || (cw->ec->iconic && (!cw->animating)))
               {
                  e_comp_object_signal_emit(obj, "e,state,hidden", "e");
                  if (!cw->showing)
                    _e_comp_object_animating_begin(cw);
                  if (!_e_comp_object_effect_visibility_start(cw, 0)) return;
               }
             evas_object_smart_callback_call(obj, "hiding", cw->ec);
             cw->defer_hide = !!cw->animating;
             if (!cw->animating)
               e_comp_object_effect_set(obj, NULL);
          }
     }
   if (cw->animating) return;
   /* if we have no animations running, go ahead and hide */
   cw->defer_hide = 0;
   evas_object_hide(obj);
}

static void
_e_comp_intercept_show_helper(E_Comp_Object *cw)
{
   int w = 0, h = 0;

   if (cw->ec->fullscreen)
     {
        if (!eina_list_data_find(cw->ec->desk->fullscreen_clients, cw->ec))
          cw->ec->desk->fullscreen_clients =
            eina_list_append(cw->ec->desk->fullscreen_clients, cw->ec);
     }
   if (cw->ec->sticky)
     e_comp_object_signal_emit(cw->smart_obj, "e,state,sticky", "e");
   if (cw->visible)
     {
        if (cw->ec->iconic && cw->animating)
          {
             /* triggered during iconify animation */
             e_comp_object_signal_emit(cw->smart_obj, "e,action,uniconify", "e");
             cw->defer_hide = 0;
          }
        return;
     }
   if ((!cw->updates) && (!cw->ec->input_only) && (!cw->ec->ignored))
     {
        _e_comp_object_updates_init(cw);
        if (!cw->updates)
          {
             cw->ec->changes.visible = !cw->ec->hidden;
             cw->ec->visible = 1;
             EC_CHANGED(cw->ec);
             return;
          }
     }
   if (cw->ec->new_client)
     {
        /* ignore until client idler first run */
        cw->ec->changes.visible = !cw->ec->hidden;
        cw->ec->visible = 1;
        EC_CHANGED(cw->ec);
        return;
     }
   /* ensure that some kind of frame calc has occurred if there's a frame */
   if (e_pixmap_is_x(cw->ec->pixmap) && cw->frame_object &&
       (cw->ec->h == cw->ec->client.h) && (cw->ec->w == cw->ec->client.w))
     ERR("Frame calc hasn't happened");
   /* force resize in case it hasn't happened yet, or just to update size */
   evas_object_resize(cw->smart_obj, cw->ec->w, cw->ec->h);
   if ((cw->w < 1) || (cw->h < 1))
     {
        /* if resize didn't go through, try again */
        cw->ec->visible = cw->ec->changes.visible = 1;
        EC_CHANGED(cw->ec);
        return;
     }
   /* re-set geometry */
   if (cw->ec->placed)
     evas_object_move(cw->smart_obj, cw->ec->x, cw->ec->y);

   /* if pixmap not available, clear pixmap since we're going to fetch it again */
   if (!e_pixmap_size_get(cw->ec->pixmap, &w, &h))
     e_pixmap_clear(cw->ec->pixmap);

   if (cw->real_hid && w && h)
     {
        DBG("  [%p] real hid - fix", cw->ec);
        cw->real_hid = 0;
        /* force comp theming in case it didn't happen already */
        e_comp_object_frame_theme_set(cw->smart_obj, E_COMP_OBJECT_FRAME_RESHADOW);
     }

   /* only do the show if show is allowed */
   if (!cw->real_hid)
     {
        if (cw->ec->internal) //internal clients render when they feel like it
          e_comp_object_damage(cw->smart_obj, 0, 0, cw->w, cw->h);
        evas_object_show(cw->smart_obj);
     }
}

static void
_e_comp_intercept_show(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Comp_Object *cw = data;
   E_Client *ec = cw->ec;

   if (ec->ignored) return;
   if (cw->effect_obj)
     {
        //INF("SHOW2 %p", ec);
        _e_comp_intercept_show_helper(cw);
        return;
     }
   //INF("SHOW %p", ec);
   if (ec->input_only)
     {
        cw->effect_obj = evas_object_rectangle_add(e_comp->evas);
        evas_object_color_set(cw->effect_obj, 0, 0, 0, 0);
        evas_object_smart_member_add(cw->effect_obj, cw->smart_obj);
     }
   else
     {
        _e_comp_object_setup(cw);
        cw->obj = evas_object_image_filled_add(e_comp->evas);
        evas_object_image_border_center_fill_set(cw->obj, EVAS_BORDER_FILL_SOLID);
        e_util_size_debug_set(cw->obj, 1);
        evas_object_image_pixels_get_callback_set(cw->obj, _e_comp_object_pixels_get, cw);
        evas_object_image_smooth_scale_set(cw->obj, e_comp_config_get()->smooth_windows);
        evas_object_name_set(cw->obj, "cw->obj");
        evas_object_image_colorspace_set(cw->obj, EVAS_COLORSPACE_ARGB8888);
        _e_comp_object_alpha_set(cw);

        if (cw->frame_object)
          {
             evas_object_event_callback_add(cw->obj, EVAS_CALLBACK_MOUSE_IN, _e_comp_object_ssd_mouse_in, cw);
             evas_object_event_callback_add(cw->obj, EVAS_CALLBACK_MOUSE_OUT, _e_comp_object_ssd_mouse_out, cw);
          }

#ifdef BORDER_ZOOMAPS
        e_comp_object_zoomap_set(o, 1);
#else
        cw->zoomap_disabled = 1;
#endif
        cw->redirected = 1;
        evas_object_color_set(cw->clip, ec->netwm.opacity, ec->netwm.opacity, ec->netwm.opacity, ec->netwm.opacity);
     }

   _e_comp_intercept_show_helper(cw);
}

static void
_e_comp_intercept_focus(void *data, Evas_Object *obj, Eina_Bool focus)
{
   E_Comp_Object *cw = data;
   E_Client *ec = cw->ec;

   if (focus)
     {
        ec = e_client_stack_active_adjust(ec);
        if (!ec) return;
        obj = ec->frame;
        cw = evas_object_data_get(obj, "comp_obj");
     }
   /* note: this is here as it seems there are enough apps that do not even
    * expect us to emulate a look of focus but not actually set x input
    * focus as we do - so simply abort any focus set on such windows */
   /* be strict about accepting focus hint */
   if (e_client_has_xwindow(ec))
     {
        /* be strict about accepting focus hint */
        if ((!ec->icccm.accepts_focus) &&
            (!ec->icccm.take_focus)) return;
     }
   if (focus && ec->lock_focus_out) return;
   if (e_object_is_del(E_OBJECT(ec)) && focus)
     {
        ERR("CAN'T FOCUS DELETED CLIENT!");
        return;
     }

   /* filter focus setting based on current state */
   if (focus)
     {
        if (ec->focused)
          {
             evas_object_focus_set(obj, focus);
             return;
          }
        if ((ec->iconic) && (!ec->deskshow))
          {
             /* don't focus an iconified window. that's silly! */
             e_client_uniconify(ec);
             if (e_client_focus_track_enabled())
               e_client_focus_latest_set(ec);
             return;
          }
        if (!ec->visible)
          {
             return;
          }
        if ((!ec->sticky) && (ec->desk) && (!ec->desk->visible))
          {
             if (ec->desk->animate_count) return;
             e_desk_show(ec->desk);
             if (!ec->desk->visible) return;
          }
     }

   if (focus)
     {
        /* check for dialog children that steal focus */
        if ((ec->modal) && (ec->modal != ec) &&
            (ec->modal->visible) && (!e_object_is_del(E_OBJECT(ec->modal))))
          {
             evas_object_focus_set(ec->modal->frame, focus);
             return;
          }
        else if ((ec->leader) && (ec->leader->modal) &&
                 (ec->leader->modal != ec) && ec->leader->modal->visible &&
                 (!e_object_is_del(E_OBJECT(ec->leader->modal))))
          {
             evas_object_focus_set(ec->leader->modal->frame, focus);
             return;
          }
        if (!cw->visible)
          {
             /* not yet visible, wait till the next time... */
             ec->want_focus = !ec->hidden;
             if (ec->want_focus)
               EC_CHANGED(ec);
             return;
          }
        e_client_focused_set(ec);
     }
   else
     {
        if (e_client_focused_get() == ec) {
          e_client_focused_set(NULL);
        }
     }
   evas_object_focus_set(obj, focus);
}

////////////////////////////////////////////////////

static void
_e_comp_object_frame_recalc(E_Comp_Object *cw)
{
   int w, h, ox, oy, ow, oh;

   if (cw->frame_object)
     {
        if (cw->obj) edje_object_part_unswallow(cw->frame_object, cw->obj);
        evas_object_geometry_get(cw->frame_object, NULL, NULL, &w, &h);
        /* set a fixed size, force edje calc, check size difference */
        evas_object_resize(cw->frame_object, MAX(w, 50), MAX(h, 50));
        edje_object_message_signal_process(cw->frame_object);
        edje_object_calc_force(cw->frame_object);
        edje_object_part_geometry_get(cw->frame_object, "e.swallow.client", &ox, &oy, &ow, &oh);
        cw->client_inset.l = ox;
        cw->client_inset.r = MAX(w, 50) - (ox + ow);
        cw->client_inset.t = oy;
        cw->client_inset.b = MAX(h, 50) - (oy + oh);
        if (cw->obj) edje_object_part_swallow(cw->frame_object, "e.swallow.client", cw->obj);
        evas_object_resize(cw->frame_object, w, h);
        if (!cw->input_objs)
          evas_object_pass_events_set(cw->obj, 0);
     }
   else
     {
        cw->client_inset.l = 0;
        cw->client_inset.r = 0;
        cw->client_inset.t = 0;
        cw->client_inset.b = 0;
     }
   cw->client_inset.calc = !!cw->frame_object;
}

static void
_e_comp_smart_cb_frame_recalc(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;
   int w = 0, h = 0, pw = 0, ph = 0;

   /* - get current size
    * - calc new size
    * - readjust for new frame size
    */

   w = cw->ec->w, h = cw->ec->h;
   e_comp_object_frame_wh_unadjust(obj, w, h, &pw, &ph);

   _e_comp_object_frame_recalc(cw);

   if (!cw->ec->fullscreen)
     e_comp_object_frame_wh_adjust(obj, cw->ec->client.w, cw->ec->client.h, &w, &h);

   evas_object_smart_callback_call(cw->smart_obj, "frame_recalc_done", &cw->client_inset);
   if (cw->ec->shading || cw->ec->shaded) return;
   if (cw->ec->fullscreen)
     evas_object_resize(cw->ec->frame, cw->ec->zone->w, cw->ec->zone->h);
   else if (cw->ec->new_client)
     {
        if ((cw->ec->w < 1) || (cw->ec->h < 1)) return;
        e_comp_object_frame_wh_adjust(obj, pw, ph, &w, &h);
        evas_object_resize(cw->ec->frame, w, h);
     }
   else if ((w != cw->ec->w) || (h != cw->ec->h))
     evas_object_resize(cw->ec->frame, w, h);
}

static Eina_Bool
_e_comp_object_shade_animator(void *data)
{
   E_Comp_Object *cw = data;
   Eina_Bool move = EINA_FALSE;
   int x, y, w, h;
   double dt, val;
   double dur;

   dt = ecore_loop_time_get() - cw->shade.start;
   dur = cw->ec->client.h / e_config->border_shade_speed;
   val = dt / dur;

   if (val < 0.0)
     val = 0.0;
   else if (val > 1.0)
     val = 1.0;

   if (e_config->border_shade_transition == E_TRANSITION_SINUSOIDAL)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_SINUSOIDAL, 0.0, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_DECELERATE)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_DECELERATE, 0.0, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_ACCELERATE)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_ACCELERATE, 0.0, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_LINEAR)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_LINEAR, 0.0, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_ACCELERATE_LOTS)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_ACCELERATE_FACTOR, 1.7, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_DECELERATE_LOTS)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_DECELERATE_FACTOR, 1.7, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_SINUSOIDAL_LOTS)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_SINUSOIDAL_FACTOR, 1.7, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_BOUNCE)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_BOUNCE, 1.2, 3.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else if (e_config->border_shade_transition == E_TRANSITION_BOUNCE_LOTS)
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_BOUNCE, 1.2, 5.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }
   else
     {
        cw->shade.val =
          ecore_animator_pos_map(val, ECORE_POS_MAP_LINEAR, 0.0, 0.0);
        if (!cw->ec->shaded) cw->shade.val = 1.0 - cw->shade.val;
     }

   /* due to M_PI's inaccuracy, cos(M_PI/2) != 0.0, so we need this */
   if (cw->shade.val < 0.001) cw->shade.val = 0.0;
   else if (cw->shade.val > .999)
     cw->shade.val = 1.0;

   x = cw->ec->x, y = cw->ec->y, w = cw->ec->w, h = cw->ec->h;
   if (cw->shade.dir == E_DIRECTION_UP)
     h = cw->client_inset.t + cw->ec->client.h * cw->shade.val;
   else if (cw->shade.dir == E_DIRECTION_DOWN)
     {
        h = cw->client_inset.t + cw->ec->client.h * cw->shade.val;
        y = cw->shade.y + cw->ec->client.h * (1 - cw->shade.val);
        move = EINA_TRUE;
     }
   else if (cw->shade.dir == E_DIRECTION_LEFT)
     w = cw->client_inset.t + cw->ec->client.w * cw->shade.val;
   else if (cw->shade.dir == E_DIRECTION_RIGHT)
     {
        w = cw->client_inset.t + cw->ec->client.w * cw->shade.val;
        x = cw->shade.x + cw->ec->client.w * (1 - cw->shade.val);
        move = EINA_TRUE;
     }

   if (move) evas_object_move(cw->smart_obj, x, y);
   evas_object_resize(cw->smart_obj, w, h);

   /* we're done */
   if (EINA_DBL_EQ(val, 1))
     {
        cw->shade.anim = NULL;

        evas_object_smart_callback_call(cw->smart_obj, "shade_done", NULL);
        if (cw->ec->shaded)
          e_comp_object_signal_emit(cw->smart_obj, "e,state,shaded", "e");
        else
          e_comp_object_signal_emit(cw->smart_obj, "e,state,unshaded", "e");
        edje_object_message_signal_process(cw->frame_object);
        _e_comp_smart_cb_frame_recalc(cw, cw->smart_obj, NULL);
     }
   return cw->ec->shading;
}

static void
_e_comp_smart_cb_shaded(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   E_FREE_FUNC(cw->shade.anim, ecore_animator_del);

   e_comp_object_signal_emit(cw->smart_obj, "e,state,shaded", "e");
   cw->shade.start = -100;
   cw->shade.dir = (E_Direction)event_info;
   _e_comp_object_shade_animator(cw);
}

static void
_e_comp_smart_cb_shading(void *data, Evas_Object *obj, void *event_info)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   if (cw->shade.anim && EINA_DBL_EQ(cw->shade.val, 0.0))
     {
        cw->ec->shaded = 0;
        _e_comp_smart_cb_shaded(data, obj, event_info);
        return;
     }
   E_FREE_FUNC(cw->shade.anim, ecore_animator_del);

   cw->shade.x = cw->x;
   cw->shade.y = cw->y;
   e_comp_object_signal_emit(cw->smart_obj, "e,state,shading", "e");
   cw->shade.start = ecore_loop_time_get();
   cw->shade.dir = (E_Direction)event_info;
   cw->shade.anim = ecore_animator_add(_e_comp_object_shade_animator, cw);
}

static void
_e_comp_smart_cb_unshaded(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   E_FREE_FUNC(cw->shade.anim, ecore_animator_del);

   cw->shade.dir = (E_Direction)event_info;
   if (cw->shade.dir == E_DIRECTION_UP ||
       cw->shade.dir == E_DIRECTION_LEFT)
     {
        cw->shade.x = cw->x;
        cw->shade.y = cw->y;
     }
   else
     {
        cw->shade.x = cw->x - cw->w;
        cw->shade.y = cw->y - cw->h;
     }
   e_comp_object_signal_emit(cw->smart_obj, "e,state,unshaded", "e");
   cw->shade.start = -100;
   _e_comp_object_shade_animator(cw);
}

static void
_e_comp_smart_cb_unshading(void *data, Evas_Object *obj, void *event_info)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   if (cw->shade.anim && EINA_DBL_EQ(cw->shade.val, 0.0))
     {
        cw->ec->shaded = 1;
        _e_comp_smart_cb_unshaded(data, obj, event_info);
        return;
     }
   E_FREE_FUNC(cw->shade.anim, ecore_animator_del);

   cw->shade.dir = (E_Direction)event_info;
   e_comp_object_signal_emit(cw->smart_obj, "e,state,unshading", "e");
   cw->shade.start = ecore_loop_time_get();
   cw->shade.anim = ecore_animator_add(_e_comp_object_shade_animator, cw);
}

static void
_e_comp_smart_cb_maximize(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   _e_comp_object_shadow_setup(cw);
   if (cw->frame_object)
     {
        _e_comp_object_shadow(cw);
        e_comp_object_signal_emit(cw->smart_obj, "e,action,maximize", "e");
        _e_comp_object_frame_recalc(cw);
        evas_object_smart_callback_call(cw->smart_obj, "frame_recalc_done", &cw->client_inset);
     }
}

static void
_e_comp_smart_cb_fullscreen(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if (_e_comp_object_shadow_setup(cw))
     e_comp_object_damage(cw->smart_obj, 0, 0, cw->ec->w, cw->ec->h);
   if (cw->frame_object)
     {
        _e_comp_object_shadow(cw);
        e_comp_object_signal_emit(cw->smart_obj, "e,action,maximize,fullscreen", "e");
        _e_comp_object_frame_recalc(cw);
        evas_object_smart_callback_call(cw->smart_obj, "frame_recalc_done", &cw->client_inset);
     }
}

static void
_e_comp_smart_cb_unmaximize(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if (cw->frame_object)
     {
        _e_comp_object_shadow(cw);
        e_comp_object_signal_emit(obj, "e,action,unmaximize", "e");
        _e_comp_object_frame_recalc(cw);
        evas_object_smart_callback_call(cw->smart_obj, "frame_recalc_done", &cw->client_inset);
     }
}

static void
_e_comp_smart_cb_unfullscreen(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if (_e_comp_object_shadow_setup(cw))
     {
        EC_CHANGED(cw->ec);
        cw->ec->changes.size = 1;
     }
   if (cw->frame_object)
     {
        _e_comp_object_shadow(cw);
        e_comp_object_signal_emit(obj, "e,action,unmaximize,unfullscreen", "e");
        _e_comp_object_frame_recalc(cw);
        evas_object_smart_callback_call(cw->smart_obj, "frame_recalc_done", &cw->client_inset);
     }
}

static void
_e_comp_smart_cb_sticky(void *data EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_comp_object_signal_emit(obj, "e,state,sticky", "e");
}

static void
_e_comp_smart_cb_unsticky(void *data EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_comp_object_signal_emit(obj, "e,state,unsticky", "e");
}

static void
_e_comp_smart_cb_unhung(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   e_comp_object_signal_emit(cw->smart_obj, "e,state,unhung", "e");
}

static void
_e_comp_smart_cb_hung(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if (!cw->ec) return; //NYI
   e_comp_object_signal_emit(cw->smart_obj, "e,state,hung", "e");
}

static void
_e_comp_smart_focus_in(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_comp_object_signal_emit(obj, "e,state,focused", "e");
}

static void
_e_comp_smart_focus_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Comp_Object *cw = data;

   if ((!cw->deleted) && (!e_object_is_del(E_OBJECT(cw->ec))))
     e_comp_object_signal_emit(obj, "e,state,unfocused", "e");
}

static void
_e_comp_smart_add(Evas_Object *obj)
{
   E_Comp_Object *cw;

   cw = E_NEW(E_Comp_Object, 1);
   cw->smart_obj = obj;
   cw->x = cw->y = cw->w = cw->h = -1;
   evas_object_smart_data_set(obj, cw);
   cw->opacity = 255.0;
   evas_object_data_set(obj, "comp_obj", cw);
   evas_object_move(obj, -1, -1);
   /* intercept ALL the callbacks! */
   evas_object_intercept_stack_above_callback_add(obj, _e_comp_intercept_stack_above, cw);
   evas_object_intercept_stack_below_callback_add(obj, _e_comp_intercept_stack_below, cw);
   evas_object_intercept_raise_callback_add(obj, _e_comp_intercept_raise, cw);
   evas_object_intercept_lower_callback_add(obj, _e_comp_intercept_lower, cw);
   evas_object_intercept_layer_set_callback_add(obj, _e_comp_intercept_layer_set, cw);
   evas_object_intercept_move_callback_add(obj, _e_comp_intercept_move, cw);
   evas_object_intercept_resize_callback_add(obj, _e_comp_intercept_resize, cw);
   evas_object_intercept_show_callback_add(obj, _e_comp_intercept_show, cw);
   evas_object_intercept_hide_callback_add(obj, _e_comp_intercept_hide, cw);
   evas_object_intercept_focus_set_callback_add(obj, _e_comp_intercept_focus, cw);

   evas_object_smart_callback_add(obj, "shading", _e_comp_smart_cb_shading, cw);
   evas_object_smart_callback_add(obj, "shaded", _e_comp_smart_cb_shaded, cw);
   evas_object_smart_callback_add(obj, "unshading", _e_comp_smart_cb_unshading, cw);
   evas_object_smart_callback_add(obj, "unshaded", _e_comp_smart_cb_unshaded, cw);

   evas_object_smart_callback_add(obj, "maximize", _e_comp_smart_cb_maximize, cw);
   evas_object_smart_callback_add(obj, "fullscreen", _e_comp_smart_cb_fullscreen, cw);
   evas_object_smart_callback_add(obj, "unmaximize", _e_comp_smart_cb_unmaximize, cw);
   evas_object_smart_callback_add(obj, "unfullscreen", _e_comp_smart_cb_unfullscreen, cw);

   evas_object_smart_callback_add(obj, "stick", _e_comp_smart_cb_sticky, cw);
   evas_object_smart_callback_add(obj, "unstick", _e_comp_smart_cb_unsticky, cw);

   evas_object_smart_callback_add(obj, "hung", _e_comp_smart_cb_hung, cw);
   evas_object_smart_callback_add(obj, "unhung", _e_comp_smart_cb_unhung, cw);

   evas_object_smart_callback_add(obj, "frame_recalc", _e_comp_smart_cb_frame_recalc, cw);

   evas_object_event_callback_add(obj, EVAS_CALLBACK_FOCUS_IN, _e_comp_smart_focus_in, cw);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_FOCUS_OUT, _e_comp_smart_focus_out, cw);
}

static void
_e_comp_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(cw->clip, r, g, b, a);
   cw->ec->netwm.opacity = a;
   if (cw->ec->remember && (cw->ec->remember->apply & E_REMEMBER_APPLY_OPACITY))
     e_remember_update(cw->ec);
   evas_object_smart_callback_call(obj, "color_set", NULL);
}


static void
_e_comp_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(cw->clip, clip);
}

static void
_e_comp_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(cw->clip);
}

static void
_e_comp_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   cw->visible = 0;
   cw->deleted |= cw->ec->delete_requested || e_object_is_del(E_OBJECT(cw->ec));
   evas_object_hide(cw->clip);
   evas_object_hide(cw->effect_obj);
   if (stopping) return;
   if (!cw->ec->input_only)
     {
        edje_object_freeze(cw->effect_obj);
        edje_object_freeze(cw->shobj);
        edje_object_play_set(cw->shobj, 0);
        if (cw->frame_object)
          edje_object_play_set(cw->frame_object, 0);
     }
   /* ensure focus-out */
   if (cw->ec->focused) {
     e_client_revert_focus(cw->ec);
   }
   e_comp_render_queue(); //force nocomp recheck
   e_comp_shape_queue();
}

static void
_e_comp_smart_show(Evas_Object *obj)
{
   E_Client *tmp;
   Eina_List *l;

   INTERNAL_ENTRY;
   cw->defer_hide = 0;
   cw->visible = 1;
   if ((cw->w < 0) || (cw->h < 0))
     ERR("ACK!");

   //INF("SMART SHOW: %p EC(%dx%d) CW(%dx%d)", cw->ec, cw->ec->w, cw->ec->h, cw->w, cw->h);

   EINA_LIST_FOREACH(cw->ec->e.state.video_child, l, tmp)
     evas_object_show(tmp->frame);

   evas_object_show(cw->clip);
   if (!cw->ec->input_only)
     {
        edje_object_thaw(cw->effect_obj);
        edje_object_thaw(cw->shobj);
        edje_object_play_set(cw->shobj, 1);
        if (cw->frame_object)
          edje_object_play_set(cw->frame_object, 1);
     }
   evas_object_show(cw->effect_obj);
   if (cw->ec->internal_elm_win && (!evas_object_visible_get(cw->ec->internal_elm_win)))
     evas_object_show(cw->ec->internal_elm_win);
   e_comp_render_queue();
   if (cw->ec->input_only)
     {
        e_comp_shape_queue();
        return;
     }
   if (cw->ec->iconic && (!cw->ec->new_client))
     e_comp_object_signal_emit(cw->smart_obj, "e,action,uniconify", "e");
   if (cw->updates_exist)
     e_comp_object_render_update_add(obj);
   if ((!cw->showing) && ((!cw->ec->iconic) || cw->ec->new_client || (!cw->animating)))
     {
        /* if cw->showing set, client was ec->hidden during show animation */
        e_comp_object_signal_emit(cw->smart_obj, "e,state,visible", "e");
        _e_comp_object_animating_begin(cw);
        cw->showing = 1;
        if (!_e_comp_object_effect_visibility_start(cw, 1)) return;
     }
   /* ensure some random effect doesn't lock the client offscreen */
   if (!cw->animating)
     {
        cw->showing = 0;
        e_comp_object_effect_set(obj, NULL);
        e_comp_shape_queue();
     }
}

static void
_e_comp_object_client_del(void *d, void *obj EINA_UNUSED)
{
   E_Comp_Object *cw = d;
   cw->deleted = 1;
   e_comp_object_render_update_del(cw->smart_obj);
   _e_comp_object_layers_remove(cw);
}

static void
_e_comp_input_objs_free(E_Comp_Object *cw)
{
   Evas_Object *rect;
   if (!cw->input_objs) return;
   while ((rect = eina_array_pop(cw->input_objs)))
     {
        evas_object_smart_member_del(rect);
        evas_object_freeze_events_set(rect, 1);
        evas_object_del(rect);
     }
}

static void
_e_comp_smart_del(Evas_Object *obj)
{
   Eina_List *l;
   Evas_Object *o;

   INTERNAL_ENTRY;

   E_FREE_FUNC(cw->updates, eina_tiler_free);
   E_FREE_FUNC(cw->pending_updates, eina_tiler_free);
   free(cw->ns);

   EINA_LIST_FREE(cw->obj_mirror, o)
     {
        evas_object_image_data_set(o, NULL);
        evas_object_freeze_events_set(o, 1);
        evas_object_event_callback_del_full(o, EVAS_CALLBACK_DEL, _e_comp_object_cb_mirror_del, cw);
        evas_object_event_callback_del_full(o, EVAS_CALLBACK_SHOW, _e_comp_object_cb_mirror_show, cw);
        evas_object_event_callback_del_full(o, EVAS_CALLBACK_HIDE, _e_comp_object_cb_mirror_hide, cw);
        evas_object_del(o);
     }
   EINA_LIST_FREE(cw->obj_agent, o)
     {
        evas_object_freeze_events_set(o, 1);
        evas_object_event_callback_del_full(o, EVAS_CALLBACK_DEL, _e_comp_object_cb_agent_del, cw);
        evas_object_del(o);
     }
   if (!cw->deleted)
     _e_comp_object_layers_remove(cw);
   l = evas_object_data_get(obj, "comp_object-to_del");
   E_FREE_LIST(l, evas_object_del);
   evas_object_del(cw->clip);
   evas_object_del(cw->effect_obj);
   evas_object_del(cw->shobj);
   evas_object_del(cw->frame_icon);
   evas_object_del(cw->frame_volume);
   evas_object_del(cw->frame_object);
   evas_object_del(cw->zoomobj);
   _e_comp_input_objs_free(cw);
   eina_array_free(cw->input_objs);
   eina_tiler_free(cw->input_area);
   evas_object_del(cw->obj);
   e_comp_shape_queue();
   eina_stringshare_del(cw->frame_theme);
   eina_stringshare_del(cw->frame_name);
   if (cw->animating)
     {
        cw->animating = 0;
        e_comp->animating--;
        UNREFD(cw->ec, 2);
        e_object_unref(E_OBJECT(cw->ec));
     }
//   UNREFD(cw->ec, 9);
//   e_object_unref(E_OBJECT(cw->ec));
   free(cw);
}

static void
_e_comp_object_input_rect_update(E_Comp_Object *cw)
{
   int x, y, w, h;
   Eina_Iterator *it;
   Eina_Rectangle *rect;
   Evas_Object *o;
   int i = 0;

   if (!cw->input_area) return;
   it = eina_tiler_iterator_new(cw->input_area);
   EINA_ITERATOR_FOREACH(it, rect)
     {
        o = eina_array_data_get(cw->input_objs, i++);
        x = rect->x, y = rect->y, w = rect->w, h = rect->h;

        E_RECTS_CLIP_TO_RECT(x, y, w, h, 0, 0,
                             cw->ec->client.w, cw->ec->client.h);
        evas_object_geometry_set
          (o, cw->x + x + (!!cw->frame_object * cw->client_inset.l),
              cw->y + y + (!!cw->frame_object * cw->client_inset.t),
              w, h);
     }
   eina_iterator_free(it);
}

static void
_e_comp_smart_move(Evas_Object *obj, int x, int y)
{
   Eina_List *l;
   Evas_Object *o;

   INTERNAL_ENTRY;

   cw->x = x, cw->y = y;
   cw->agent_updating = 1;
   EINA_LIST_FOREACH(cw->obj_agent, l, o)
     evas_object_move(o, cw->ec->x, cw->ec->x);
   cw->agent_updating = 0;
   if (cw->clip) evas_object_move(cw->clip, 0, 0);
   if (cw->effect_obj) evas_object_move(cw->effect_obj, x, y);
   if (cw->input_area)
     _e_comp_object_input_rect_update(cw);
   /* this gets called once during setup to init coords offscreen and guarantee first move */
   if (e_comp && cw->visible)
     {
        e_comp_shape_queue();
     }
}

static void
_e_comp_smart_resize(Evas_Object *obj, int w, int h)
{
   Eina_Bool first = EINA_FALSE;
   Eina_List *l;
   Evas_Object *o;

   INTERNAL_ENTRY;

   //INF("RSZ(%p): %dx%d -> %dx%d", cw->ec, cw->w, cw->h, w, h);
   if (!cw->effect_obj) ERR("ACK!");
   first = ((cw->w < 1) || (cw->h < 1));
   cw->w = w, cw->h = h;
   if ((!cw->ec->shading) && (!cw->ec->shaded))
     {
        int ww = 0, hh = 0, pw = 0, ph = 0;

        if (cw->frame_object)
          e_comp_object_frame_wh_unadjust(obj, w, h, &ww, &hh);
        else
          {
             ww = w;
             hh = h;
          }
        /* verify pixmap:object size */
        if (e_pixmap_size_get(cw->ec->pixmap, &pw, &ph) && (!cw->ec->override))
          {
             //INF("CW RSZ: %dx%d PIX(%dx%d)", w, h, pw, ph);
             //if (cw->obj)
               //{
                  //evas_object_size_hint_max_set(cw->obj, pw, ph);
                  //evas_object_size_hint_min_set(cw->obj, pw, ph);
               //}
             if ((ww != pw) || (hh != ph))
               ERR("CW RSZ: %dx%d || PX: %dx%d", ww, hh, pw, ph);
          }
        evas_object_resize(cw->effect_obj, w, h);
        if (cw->zoomobj) e_zoomap_child_resize(cw->zoomobj, pw, ph);
        if (cw->input_area)
          _e_comp_object_input_rect_update(cw);
        /* resize render update tiler */
        if (!first)
          {
             if (cw->updates)
               {
                  int tw, th;

                  eina_tiler_area_size_get(cw->updates, &tw, &th);
                  if ((tw != pw) || (th != ph))
                    {
                       RENDER_DEBUG("DAMAGE UNFULL: %p", cw->ec);
                       cw->updates_full = 0;
                       eina_tiler_clear(cw->updates);
                    }
               }
          }
        else
          {
             RENDER_DEBUG("DAMAGE RESIZE(%p): %dx%d", cw->ec, cw->ec->client.w, cw->ec->client.h);
             if (cw->updates) eina_tiler_area_size_set(cw->updates, cw->ec->client.w, cw->ec->client.h);
          }
     }
   else
     {
        evas_object_resize(cw->effect_obj, w, h);
     }
   cw->agent_updating = 1;
   EINA_LIST_FOREACH(cw->obj_agent, l, o)
     evas_object_resize(o, cw->ec->w, cw->ec->h);
   cw->agent_updating = 0;
   if (!cw->visible) return;
   e_comp_render_queue();
   if (!cw->animating)
     {
        e_comp_shape_queue();
     }
}

static void
_e_comp_object_move_end(void *d EINA_UNUSED, E_Client *ec)
{
   E_Comp_Object *cw = evas_object_smart_data_get(ec->frame);
   unsigned int i;
   Evas_Object *rect;
   Eina_Array_Iterator it;

   if (!cw) return;
   if (!cw->input_objs) return;

   EINA_ARRAY_ITER_NEXT(cw->input_objs, i, rect, it)
     evas_object_pointer_mode_set(rect, EVAS_OBJECT_POINTER_MODE_AUTOGRAB);
}

static void
_e_comp_object_move_begin(void *d EINA_UNUSED, E_Client *ec)
{
   E_Comp_Object *cw = evas_object_smart_data_get(ec->frame);
   unsigned int i;
   Evas_Object *rect;
   Eina_Array_Iterator it;

   if (!cw) return;
   if (!cw->input_objs) return;

   EINA_ARRAY_ITER_NEXT(cw->input_objs, i, rect, it)
     evas_object_pointer_mode_set(rect, EVAS_OBJECT_POINTER_MODE_NOGRAB);
}

static void
_e_comp_smart_init(void)
{
   const char *env;
   if (_e_comp_smart) return;

   env = getenv("E_RENDER_DEBUG");
   if (eina_streq(env, "focus"))
     render_debug_enabled = -1;
   else if (env)
     render_debug_enabled = 1;
   e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN, _e_comp_object_move_begin, NULL);
   e_client_hook_add(E_CLIENT_HOOK_MOVE_END, _e_comp_object_move_end, NULL);
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_comp_smart_add,
         _e_comp_smart_del,
         _e_comp_smart_move,
         _e_comp_smart_resize,
         _e_comp_smart_show,
         _e_comp_smart_hide,
         _e_comp_smart_color_set,
         _e_comp_smart_clip_set,
         _e_comp_smart_clip_unset,
         NULL,
         NULL,
         NULL,

         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_comp_smart = evas_smart_class_new(&sc);
   }
}

E_API void
e_comp_object_zoomap_set(Evas_Object *obj, Eina_Bool enabled)
{
   API_ENTRY;

   enabled = !enabled;
   if (cw->zoomap_disabled == enabled) return;
   if (enabled)
     {
        cw->zoomobj = e_zoomap_add(e_comp->evas);
        e_zoomap_smooth_set(cw->zoomobj, e_comp_config_get()->smooth_windows);
        e_zoomap_child_set(cw->zoomobj, cw->ec ? cw->frame_object : cw->obj);
        edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->zoomobj);
        e_zoomap_child_edje_solid_setup(cw->zoomobj);
        if (cw->ec->override)
          evas_object_name_set(cw->zoomobj, "cw->zoomobj::WINDOW");
        else if (!cw->ec->input_only)
          evas_object_name_set(cw->zoomobj, "cw->zoomobj::CLIENT");
     }
   else
     {
        edje_object_part_unswallow(cw->shobj, cw->zoomobj);
        E_FREE_FUNC(cw->zoomobj, evas_object_del);
        edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->ec ? cw->frame_object : cw->obj);
     }
   cw->zoomap_disabled = enabled;
}

E_API Eina_Bool
e_comp_object_mirror_visibility_check(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;
   return !!cw->force_visible;
}
/////////////////////////////////////////////////////////

static void
_e_comp_object_util_del(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Eina_List *l;
   Eina_Bool comp_object;

   comp_object = !!evas_object_data_get(obj, "comp_object");
   if (comp_object)
     {
        Evas_Object *o;

        o = edje_object_part_swallow_get(obj, "e.swallow.content");
        evas_object_del(o);
        e_comp_render_queue();
        e_comp_shape_queue();
     }
   l = evas_object_data_get(obj, "comp_object-to_del");
   E_FREE_LIST(l, evas_object_del);
}

static void
_e_comp_object_util_restack(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   if (e_comp_util_object_is_above_nocomp(obj) &&
       (!evas_object_data_get(obj, "comp_override")))
     {
        evas_object_data_set(obj, "comp_override", (void*)1);
        e_comp_override_add();
     }
}

static void
_e_comp_object_util_show(void *data EINA_UNUSED, Evas_Object *obj)
{
   Eina_Bool ref = EINA_TRUE;
   if (evas_object_visible_get(obj))
     {
        void *d;

        d = evas_object_data_del(obj, "comp_hiding");
        if (d)
          /* currently trying to hide */
          ref = EINA_FALSE;
        else
          /* already visible */
          return;
     }
   else
     {
        e_comp_shape_queue();
     }

   evas_object_show(obj);
   if (ref && (!stopping))
     {
        evas_object_ref(obj);
        evas_object_data_set(obj, "comp_ref", (void*)1);
     }
   edje_object_signal_emit(obj, "e,state,visible", "e");
   edje_object_message_signal_process(obj);
   evas_object_data_set(obj, "comp_showing", (void*)1);
   if (e_comp_util_object_is_above_nocomp(obj))
     {
        evas_object_data_set(obj, "comp_override", (void*)1);
        e_comp_override_add();
     }
}

static void
_e_comp_object_util_hide(void *data EINA_UNUSED, Evas_Object *obj)
{
   if (!evas_object_visible_get(obj)) return;
   /* already hiding */
   if (evas_object_data_get(obj, "comp_hiding"))
     {
        evas_object_data_del(obj, "comp_hiding");
        evas_object_hide(obj);
        e_comp_shape_queue();
        if (evas_object_data_del(obj, "comp_ref"))
          evas_object_unref(obj);
        return;
     }
   if ((!stopping) && (!evas_object_data_del(obj, "comp_showing")))
     {
        evas_object_ref(obj);
        evas_object_data_set(obj, "comp_ref", (void*)1);
     }
   edje_object_signal_emit(obj, "e,state,hidden", "e");
   edje_object_message_signal_process(obj);
   evas_object_data_set(obj, "comp_hiding", (void*)1);

   if (evas_object_data_del(obj, "comp_override"))
     e_comp_override_timed_pop();
}

static void
_e_comp_object_util_done_defer(void *data, Evas_Object *obj, const char *emission, const char *source EINA_UNUSED)
{
   if (!e_util_strcmp(emission, "e,action,hide,done"))
     {
        if (!evas_object_data_del(obj, "comp_hiding")) return;
        evas_object_intercept_hide_callback_del(obj, _e_comp_object_util_hide);
        evas_object_hide(obj);
        e_comp_shape_queue();
        evas_object_intercept_hide_callback_add(obj, _e_comp_object_util_hide, data);
     }
   else
     evas_object_data_del(obj, "comp_showing");
   if (evas_object_data_del(obj, "comp_ref"))
     evas_object_unref(obj);
}

static void
_e_comp_object_util_moveresize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   if (data)
     {
        int w, h;

        evas_object_geometry_get(obj, NULL, NULL, &w, &h);
        e_zoomap_child_resize(data, w, h);
     }
   if (evas_object_visible_get(obj))
     {
        e_comp_shape_queue();
     }
}

E_API void
e_comp_object_util_type_set(Evas_Object *obj, E_Comp_Object_Type type)
{
   Evas_Object *content;
   const char *name = NULL;
   Eina_List *l, *list = NULL;
   E_Comp_Config *conf = e_comp_config_get();
   Eina_Bool skip = EINA_FALSE, shadow = EINA_FALSE;
   E_Comp_Match *m;
   const char *grp;
   char buf[1024];
   int ok = 0;

   EINA_SAFETY_ON_NULL_RETURN(obj);

   edje_object_file_get(obj, NULL, &grp);
   switch (type)
     {
      case E_COMP_OBJECT_TYPE_MENU:
        list = conf->match.menus;
        shadow = EINA_TRUE;
        break;
      case E_COMP_OBJECT_TYPE_POPUP:
        list = conf->match.popups;
        break;
      default:
        list = conf->match.objects;
     }
   content = edje_object_part_swallow_get(obj, "e.swallow.content");
   if (content)
     {
        if (eina_streq(evas_object_type_get(content), "e_zoomap"))
          name = evas_object_name_get(e_zoomap_child_get(content));
        else
          name = evas_object_name_get(content);
     }
   if (name && (!skip))
     skip = (!strncmp(name, "noshadow", 8));
   if (skip)
     evas_object_data_set(obj, "comp_object_skip", (void*)1);
   else
     evas_object_data_del(obj, "comp_object_skip");
   if (list && (!skip))
     {
        EINA_LIST_FOREACH(list, l, m)
          {
             if (((m->name) && (!name)) ||
                 ((name) && (m->name) && (!e_util_glob_match(name, m->name))))
               continue;
             if (!m->shadow_style) continue;
             snprintf(buf, sizeof(buf), "e/comp/frame/%s", m->shadow_style);
             if (eina_streq(buf, grp)) return;
             ok = e_theme_edje_object_set(obj, "base/theme/comp", buf);
             if (ok)
               {
                  shadow = !m->no_shadow;
                  break;
               }
          }
     }
   else
     skip = EINA_TRUE;
   if (evas_object_data_get(obj, "noshadow"))
     shadow = EINA_FALSE;
   while (!ok)
     {
        if (skip)
          {
             if (eina_streq("e/comp/frame/none", grp)) return;
             ok = e_theme_edje_object_set(obj, "base/theme/comp", "e/comp/frame/none");
          }
        if (ok) break;
        if (conf->shadow_style)
          {
             snprintf(buf, sizeof(buf), "e/comp/frame/%s", conf->shadow_style);
             if (eina_streq(buf, grp)) return;
             ok = e_theme_edje_object_set(obj, "base/theme/comp", buf);
             if (ok) break;
          }
        if (eina_streq("e/comp/frame/default", grp)) return;
        ok = e_theme_edje_object_set(obj, "base/theme/comp", "e/comp/frame/default");
        break;
     }
   if (shadow && (e_util_strcmp(evas_object_type_get(obj), "edje") || (!edje_object_data_get(obj, "noshadow"))))
     edje_object_signal_emit(obj, "e,state,shadow,on", "e");
   else
     edje_object_signal_emit(obj, "e,state,shadow,off", "e");
   if (evas_object_visible_get(obj))
     edje_object_signal_emit(obj, "e,state,visible", "e");
   if (content)
     edje_object_part_swallow(obj, "e.swallow.content", content);
}

E_API Evas_Object *
e_comp_object_util_add(Evas_Object *obj, E_Comp_Object_Type type)
{
   Evas_Object *o, *z = NULL;
   E_Comp_Config *conf = e_comp_config_get();
   int x, y, w, h;
   Eina_Bool vis;

   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
   vis = evas_object_visible_get(obj);
   o = edje_object_add(e_comp->evas);
   evas_object_data_set(o, "comp_object", (void*)1);
   if (evas_object_data_get(obj, "noshadow"))
     evas_object_data_set(o, "noshadow", o);
   e_comp_object_util_type_set(o, type);

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   evas_object_geometry_set(o, x, y, w, h);
   evas_object_pass_events_set(o, evas_object_pass_events_get(obj));

   z = e_zoomap_add(e_comp->evas);
   evas_object_show(z);
   evas_object_geometry_set(z, x, y, w, h);
   e_zoomap_child_edje_solid_setup(z);
   e_zoomap_smooth_set(z, conf->smooth_windows);
   e_zoomap_child_set(z, obj);
   edje_object_signal_callback_add(o, "e,action,*,done", "e", _e_comp_object_util_done_defer, z);

   evas_object_intercept_show_callback_add(o, _e_comp_object_util_show, z);
   evas_object_intercept_hide_callback_add(o, _e_comp_object_util_hide, z);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOVE, _e_comp_object_util_moveresize, z);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _e_comp_object_util_del, z);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, _e_comp_object_util_moveresize, z);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESTACK, _e_comp_object_util_restack, z);

   e_comp_object_signal_emit(o, "e,state,hidden", "e");

   edje_object_part_swallow(o, "e.swallow.content", z ?: obj);

   _e_comp_object_event_add(o);

   if (vis)
     evas_object_show(o);

   return o;
}

E_API Evas_Object *
e_comp_object_util_get(Evas_Object *obj)
{
   Evas_Object *z;
   SOFT_ENTRY(NULL);
   if (cw) return NULL;
   z = edje_object_part_swallow_get(obj, "e.swallow.content");
   EINA_SAFETY_ON_NULL_RETURN_VAL(z, NULL);
   return e_zoomap_child_get(z);
}

/* utility functions for deleting objects when their "owner" is deleted */
E_API void
e_comp_object_util_del_list_append(Evas_Object *obj, Evas_Object *to_del)
{
   Eina_List *l;

   EINA_SAFETY_ON_NULL_RETURN(to_del);
   l = evas_object_data_get(obj, "comp_object-to_del");
   evas_object_data_set(obj, "comp_object-to_del", eina_list_append(l, to_del));
   evas_object_event_callback_del(obj, EVAS_CALLBACK_DEL, _e_comp_object_util_del);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _e_comp_object_util_del, NULL);
}

E_API void
e_comp_object_util_del_list_remove(Evas_Object *obj, Evas_Object *to_del)
{
   Eina_List *l;

   SOFT_ENTRY();
   EINA_SAFETY_ON_NULL_RETURN(to_del);
   l = evas_object_data_get(obj, "comp_object-to_del");
   if (l)
     evas_object_data_set(obj, "comp_object-to_del", eina_list_remove(l, to_del));
}

/////////////////////////////////////////////////////////

E_API Evas_Object *
e_comp_object_client_add(E_Client *ec)
{
   Evas_Object *o;
   E_Comp_Object *cw;

   EINA_SAFETY_ON_NULL_RETURN_VAL(ec, NULL);
   if (ec->frame) return NULL;
   _e_comp_smart_init();
   o = evas_object_smart_add(e_comp->evas, _e_comp_smart);
   cw = evas_object_smart_data_get(o);
   if (!cw)
     {
        evas_object_del(o);
        return NULL;
     }
   evas_object_data_set(o, "E_Client", ec);
//   REFD(ec, 9);
//   e_object_ref(E_OBJECT(ec));
   cw->ec = ec;
   ec->frame = o;
   evas_object_data_set(o, "comp_object", (void*)1);
   e_object_delfn_add(E_OBJECT(ec), _e_comp_object_client_del, cw);

   _e_comp_object_event_add(o);

   return o;
}

/* utility functions for getting client inset */
E_API void
e_comp_object_frame_xy_adjust(Evas_Object *obj, int x, int y, int *ax, int *ay)
{
   API_ENTRY;
   if (!cw->client_inset.calc)
     {
        if (ax) *ax = x;
        if (ay) *ay = y;
        return;
     }
   if (ax) *ax = x - cw->client_inset.l;
   if (ay) *ay = y - cw->client_inset.t;
}

E_API void
e_comp_object_frame_xy_unadjust(Evas_Object *obj, int x, int y, int *ax, int *ay)
{
   API_ENTRY;
   if (!cw->client_inset.calc)
     {
        if (ax) *ax = x;
        if (ay) *ay = y;
        return;
     }
   if (ax) *ax = x + cw->client_inset.l;
   if (ay) *ay = y + cw->client_inset.t;
}

E_API void
e_comp_object_frame_wh_adjust(Evas_Object *obj, int w, int h, int *aw, int *ah)
{
   API_ENTRY;
   if (!cw->client_inset.calc)
     {
        if (aw) *aw = w;
        if (ah) *ah = h;
        return;
     }
   if (aw) *aw = w + cw->client_inset.l + cw->client_inset.r;
   if (ah) *ah = h + cw->client_inset.t + cw->client_inset.b;
}

E_API void
e_comp_object_frame_wh_unadjust(Evas_Object *obj, int w, int h, int *aw, int *ah)
{
   API_ENTRY;
   if (!cw->client_inset.calc)
     {
        if (aw) *aw = w;
        if (ah) *ah = h;
        return;
     }
   if (aw) *aw = w - cw->client_inset.l - cw->client_inset.r;
   if (ah) *ah = h - cw->client_inset.t - cw->client_inset.b;
}

E_API E_Client *
e_comp_object_client_get(Evas_Object *obj)
{
   Evas_Object *o;

   SOFT_ENTRY(NULL);
   /* FIXME: remove this when eo is used */
   o = evas_object_data_get(obj, "comp_smart_obj");
   if (o)
     return e_comp_object_client_get(o);
   return cw ? cw->ec : NULL;
}

E_API void
e_comp_object_frame_extends_get(Evas_Object *obj, int *x, int *y, int *w, int *h)
{
   API_ENTRY;
   if (cw->frame_extends)
     edje_object_parts_extends_calc(cw->frame_object, x, y, w, h);
   else
     {
        if (x) *x = 0;
        if (y) *y = 0;
        if (w) *w  = cw->ec->w;
        if (h) *h  = cw->ec->h;
     }
}

E_API E_Zone *
e_comp_object_util_zone_get(Evas_Object *obj)
{
   E_Zone *zone = NULL;

   SOFT_ENTRY(NULL);
   if (cw)
     zone = cw->ec->zone;
   if (!zone)
     {
        int x, y, w, h;

        if (e_win_client_get(obj))
          return e_win_client_get(obj)->zone;
        evas_object_geometry_get(obj, &x, &y, &w, &h);
        zone = e_comp_zone_xy_get(x, y);
        if (zone) return zone;
        zone = e_comp_zone_xy_get(x + w, y + h);
        if (zone) return zone;
        zone = e_comp_zone_xy_get(x + w, y);
        if (zone) return zone;
        zone = e_comp_zone_xy_get(x, y + h);
     }
   return zone;
}

E_API void
e_comp_object_util_center_on_zone(Evas_Object *obj, E_Zone *zone)
{
   int x, y, w, h, ow, oh;

   SOFT_ENTRY();

   EINA_SAFETY_ON_NULL_RETURN(zone);
   e_zone_useful_geometry_get(zone, &x, &y, &w, &h);
   if (cw)
     ow = cw->ec->w, oh = cw->ec->h;
   else
     evas_object_geometry_get(obj, NULL, NULL, &ow, &oh);
   x = x + (w - ow) / 2;
   y = y + (h - oh) / 2;
   evas_object_move(obj, x, y);
}

E_API void
e_comp_object_util_center(Evas_Object *obj)
{
   E_Zone *zone;

   zone = e_comp_object_util_zone_get(obj);

   e_comp_object_util_center_on_zone(obj, zone);
}

E_API void
e_comp_object_util_center_on(Evas_Object *obj, Evas_Object *on)
{
   int x, y, w, h, ow, oh;
   E_Comp_Object *cw2 = NULL;
   const char *type;

   SOFT_ENTRY();
   EINA_SAFETY_ON_NULL_RETURN(on);

   type = evas_object_type_get(on);
   if ((type) && (!strcmp(type, "e_comp_object")))
     cw2 = evas_object_smart_data_get(on);
   if (cw2 && eina_streq(evas_object_type_get(on), SMART_NAME))
     x = cw2->ec->x, y = cw2->ec->y, w = cw2->ec->w, h = cw2->ec->h;
   else
     evas_object_geometry_get(on, &x, &y, &w, &h);
   if (cw)
     ow = cw->ec->w, oh = cw->ec->h;
   else
     evas_object_geometry_get(obj, NULL, NULL, &ow, &oh);
   evas_object_move(obj, x + (w / 2) - (ow / 2), y + (h / 2) - (oh / 2));
}

E_API void
e_comp_object_util_fullscreen(Evas_Object *obj)
{
   SOFT_ENTRY();

   if (cw)
     e_client_fullscreen(cw->ec, E_FULLSCREEN_RESIZE);
   else
     {
        evas_object_move(obj, 0, 0);
        evas_object_resize(obj, e_comp->w, e_comp->h);
     }
}

E_API void
e_comp_object_util_center_pos_get(Evas_Object *obj, int *x, int *y)
{
   E_Zone *zone;
   int zx, zy, zw, zh;
   int ow, oh;
   SOFT_ENTRY();

   if (cw)
     ow = cw->w, oh = cw->h;
   else
     evas_object_geometry_get(obj, NULL, NULL, &ow, &oh);
   zone = e_comp_object_util_zone_get(obj);
   e_zone_useful_geometry_get(zone, &zx, &zy, &zw, &zh);
   if (x) *x = zx + (zw - ow) / 2;
   if (y) *y = zy + (zh - oh) / 2;
}

E_API void
e_comp_object_input_area_set(Evas_Object *obj, const Eina_Tiler *area)
{
   Eina_Rectangle *rect;
   Eina_Iterator *it;

   API_ENTRY;

   if ((!area) && (!cw->input_area)) return;
   if (area && cw->input_area && eina_tiler_equal(area, cw->input_area)) return;
   _e_comp_input_objs_free(cw);
   if (cw->obj) evas_object_pass_events_set(cw->obj, 1);
   if ((!area) || eina_tiler_empty(area))
     {
        E_FREE_FUNC(cw->input_area, eina_tiler_free);
        E_FREE_FUNC(cw->input_objs, eina_array_free);
        return;
     }
   if (cw->input_area)
     eina_tiler_clear(cw->input_area);
   else
     {
        cw->input_area = eina_tiler_new(99999, 99999);
        eina_tiler_tile_size_set(cw->input_area, 1, 1);
     }
   if (!cw->input_objs)
     cw->input_objs = eina_array_new(1);
   it = eina_tiler_iterator_new(area);
   EINA_ITERATOR_FOREACH(it, rect)
     {
        Evas_Object *o = evas_object_rectangle_add(e_comp->evas);
        //e_util_size_debug_set(o, 1);
        evas_object_name_set(o, "cw->input_rect");
        evas_object_color_set(o, 0, 0, 0, 0);
        evas_object_clip_set(o, cw->clip);
        evas_object_smart_member_add(o, obj);
        evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_IN, _e_comp_object_ssd_mouse_in, cw);
        evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_OUT, _e_comp_object_ssd_mouse_out, cw);
        if (cw->ec->moving)
          evas_object_pointer_mode_set(o, EVAS_OBJECT_POINTER_MODE_NOGRAB);
        evas_object_show(o);
        eina_array_push(cw->input_objs, o);
        eina_tiler_rect_add(cw->input_area, rect);
     }
   eina_iterator_free(it);
   _e_comp_object_input_rect_update(cw);
}

E_API Eina_Bool
e_comp_object_coords_inside_input_area(Evas_Object *obj, int x, int y)
{
   API_ENTRY EINA_FALSE;

   if (cw->input_area)
     {
        unsigned int i;
        Evas_Object *rect;
        Eina_Array_Iterator it;

        EINA_ARRAY_ITER_NEXT(cw->input_objs, i, rect, it)
          if (evas_object_pointer_coords_inside_get(rect, x, y)) return EINA_TRUE;

        return EINA_FALSE;
     }
   if (evas_object_pass_events_get(obj)) return EINA_FALSE;
   return E_INSIDE(x, y, cw->ec->x, cw->ec->y, cw->ec->w, cw->ec->h);
}

E_API void
e_comp_object_frame_geometry_get(Evas_Object *obj, int *l, int *r, int *t, int *b)
{
   API_ENTRY;
   if (l) *l = cw->client_inset.l;
   if (r) *r = cw->client_inset.r;
   if (t) *t = cw->client_inset.t;
   if (b) *b = cw->client_inset.b;
}

/* set geometry for CSD */
E_API void
e_comp_object_frame_geometry_set(Evas_Object *obj, int l, int r, int t, int b)
{
   Eina_Bool calc;

   API_ENTRY;
   if (cw->frame_object)
     ERR("CSD call on win without CSD! %i %i %i %i", l, r, t, b);
   if ((cw->client_inset.l == l) && (cw->client_inset.r == r) &&
       (cw->client_inset.t == t) && (cw->client_inset.b == b)) return;
   calc = cw->client_inset.calc;
   cw->client_inset.calc = l || r || t || b;
   eina_stringshare_replace(&cw->frame_theme, "borderless");
   if (cw->client_inset.calc)
     {
        cw->ec->w += (l + r) - (cw->client_inset.l + cw->client_inset.r);
        cw->ec->h += (t + b) - (cw->client_inset.t + cw->client_inset.b);
        if (cw->ec->maximized || cw->ec->fullscreen)
          cw->ec->saved.frame = 0;
     }
   else if (cw->ec->maximized || cw->ec->fullscreen)
     {
        cw->ec->saved.x += l - cw->client_inset.l;
        cw->ec->saved.y += t - cw->client_inset.t;
        if (!e_client_has_xwindow(cw->ec))
          {
             cw->ec->saved.w -= ((l + r) - (cw->client_inset.l + cw->client_inset.r));
             cw->ec->saved.h -= ((t + b) - (cw->client_inset.t + cw->client_inset.b));
          }
     }
   if (!cw->ec->new_client)
     {
        if (calc && cw->client_inset.calc)
          {
             E_Zone *zone = e_comp_object_util_zone_get(obj);

             if (cw->ec->x != zone->x)
               cw->ec->x -= l - cw->client_inset.l;
             if (cw->ec->y != zone->y)
               cw->ec->y -= t - cw->client_inset.t;
          }
        cw->ec->changes.pos = cw->ec->changes.size = 1;
        EC_CHANGED(cw->ec);
     }
   cw->client_inset.l = l;
   cw->client_inset.r = r;
   cw->client_inset.t = t;
   cw->client_inset.b = b;
   if (!cw->shobj) return;
   if (cw->client_inset.calc)
     e_comp_object_signal_emit(obj, "e,state,focus,disabled", "e");
   else
     e_comp_object_signal_emit(obj, "e,state,focus,enabled", "e");
}

E_API Eina_Bool
e_comp_object_frame_allowed(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;
   return (!e_client_util_ignored_get(cw->ec)) && (!cw->ec->mwm.borderless) &&
     (cw->frame_object || (!cw->client_inset.calc));
}

E_API void
e_comp_object_frame_icon_geometry_get(Evas_Object *obj, int *x, int *y, int *w, int *h)
{
   API_ENTRY;

   if (x) *x = 0;
   if (y) *y = 0;
   if (w) *w = 0;
   if (h) *h = 0;
   if (!cw->frame_icon) return;
   evas_object_geometry_get(cw->frame_icon, x, y, w, h);
}

E_API Eina_Bool
e_comp_object_frame_title_set(Evas_Object *obj, const char *name)
{
   API_ENTRY EINA_FALSE;
   if (!e_util_strcmp(cw->frame_name, name)) return EINA_FALSE;
   eina_stringshare_replace(&cw->frame_name, name);
   if (cw->frame_object)
     edje_object_part_text_set(cw->frame_object, "e.text.title", cw->frame_name);
   return EINA_TRUE;
}

E_API Eina_Bool
e_comp_object_frame_exists(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;
   return !!cw->frame_object;
}

E_API Eina_Bool
e_comp_object_frame_icon_update(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;

   E_FREE_FUNC(cw->frame_icon, evas_object_del);
   if (!cw->frame_object) return EINA_FALSE;
   if (!edje_object_part_exists(cw->frame_object, "e.swallow.icon"))
     return EINA_TRUE;
   cw->frame_icon = e_client_icon_add(cw->ec, e_comp->evas);
   if (!cw->frame_icon) return EINA_TRUE;
   if (!edje_object_part_swallow(cw->frame_object, "e.swallow.icon", cw->frame_icon))
     E_FREE_FUNC(cw->frame_icon, evas_object_del);
   else
     evas_object_show(cw->frame_icon);
   return EINA_TRUE;
}

static void
_e_comp_object_volume_update(Evas_Object *obj)
{
   API_ENTRY;

   if (cw->ec->focused)
     e_client_volume_object_emit(cw->ec, "e,state,focused", "e");
   else
     e_client_volume_object_emit(cw->ec, "e,state,unfocused", "e");
   if (cw->ec->urgent)
     e_client_volume_object_emit(cw->ec, "e,state,urgent", "e");
   else
     e_client_volume_object_emit(cw->ec, "e,state,not_urgent", "e");
   if (cw->ec->shaded)
     e_client_volume_object_emit(cw->ec, "e,state,shaded", "e");
   if (cw->ec->sticky)
     e_client_volume_object_emit(cw->ec, "e,state,sticky", "e");
   if (cw->ec->hung)
     e_client_volume_object_emit(cw->ec, "e,state,hung", "e");
   /* visibility must always be enabled for re_manage clients to prevent
    * pop-in animations every time the user sees a persistent client again;
    * applying visibility for iconic clients prevents the client from getting
    * stuck as hidden
    */
   if (cw->visible || cw->ec->iconic || cw->ec->re_manage)
     {
        if ((cw->w > 0) && (cw->h > 0))
          e_client_volume_object_emit(cw->ec, "e,state,visible", "e");
     }
   else
     e_client_volume_object_emit(cw->ec, "e,state,hidden", "e");
   if (e_comp_object_frame_allowed(cw->smart_obj))
     e_client_volume_object_emit(cw->ec, "e,state,focus,enabled", "e");
   else
     e_client_volume_object_emit(cw->ec, "e,state,focus,disabled", "e");
}

E_API void
e_comp_object_frame_volume_update(Evas_Object *obj)
{
   API_ENTRY;

   if (!cw->frame_object) return;
   if (edje_object_part_exists(cw->frame_object, "e.swallow.volume"))
     {
        if (cw->ec->sinks)
          {
             if (!cw->frame_volume)
               {
                  cw->frame_volume = e_client_volume_object_add(cw->ec, e_comp->evas);
                  edje_object_part_swallow(cw->frame_object,
                                           "e.swallow.volume", cw->frame_volume);
                  _e_comp_object_volume_update(obj);
                  evas_object_show(cw->frame_volume);
               }
          }
        else
          {
             E_FREE_FUNC(cw->frame_volume, evas_object_del);
          }
     }
}

E_API Eina_Bool
e_comp_object_frame_theme_set(Evas_Object *obj, const char *name)
{
   Evas_Object *o, *pbg;
   char buf[4096];
   int ok;
   Eina_Stringshare *theme;

   API_ENTRY EINA_FALSE;

   if (!e_util_strcmp(cw->frame_theme, name))
    return edje_object_part_swallow(cw->shobj, "e.swallow.content", cw->frame_object ?: cw->obj);
   if (!e_util_strcmp(name, "COMP_RESHADOW"))
     return _e_comp_object_shadow_setup(cw);
   if (eina_streq(name, "borderless") && (!cw->frame_object)) return EINA_TRUE;
   pbg = cw->frame_object;
   theme = eina_stringshare_add(name);

   if (cw->frame_object)
     {
        int w, h;

        w = cw->ec->w, h = cw->ec->h;
        e_comp_object_frame_wh_unadjust(obj, w, h, &cw->ec->w, &cw->ec->h);
        if ((cw->ec->w != w) || (cw->ec->h != h))
          {
             cw->ec->changes.size = 1;
             EC_CHANGED(cw->ec);
          }
        E_FREE_FUNC(cw->frame_object, evas_object_del);
        if (!name) goto reshadow;
     }
   o = edje_object_add(e_comp->evas);
   snprintf(buf, sizeof(buf), "e/widgets/border/%s/border", name);
   ok = e_theme_edje_object_set(o, "base/theme/border", buf);
   if ((!ok) && (!e_util_strcmp(name, "borderless")))
     {
        cw->frame_object = NULL;
        E_FREE_FUNC(cw->frame_icon, evas_object_del);
        E_FREE_FUNC(cw->frame_volume, evas_object_del);
        evas_object_del(o);
        eina_stringshare_del(cw->frame_theme);
        cw->frame_theme = theme;
        goto reshadow;
     }
   if (!ok)
     {
        if (theme != e_config->theme_default_border_style)
          {
             snprintf(buf, sizeof(buf), "e/widgets/border/%s/border", e_config->theme_default_border_style);
             ok = e_theme_edje_object_set(o, "base/theme/border", buf);
          }
        if (!ok)
          {
             ok = e_theme_edje_object_set(o, "base/theme/border",
                                          "e/widgets/border/default/border");
             if (ok && (theme == e_config->theme_default_border_style))
               {
                  /* Reset default border style to default */
                  eina_stringshare_replace(&e_config->theme_default_border_style, "default");
                  e_config_save_queue();
               }
          }
     }

   if (ok)
     {
        cw->frame_object = cw->ec->frame_object = o;
        edje_object_signal_emit(o, "e,version,22", "e");
        eina_stringshare_del(cw->frame_theme);
        cw->frame_theme = theme;
        evas_object_name_set(o, "cw->frame_object");

        if (cw->frame_name)
          edje_object_part_text_set(o, "e.text.title", cw->frame_name);

        if (pbg)
          {
             if (cw->frame_icon)
               {
                  if (!edje_object_part_swallow(cw->frame_object,
                                                "e.swallow.icon",
                                                cw->frame_icon))
                    E_FREE_FUNC(cw->frame_icon, evas_object_del);
               }
             if (cw->frame_volume)
               {
                  if (!edje_object_part_swallow(cw->frame_object,
                                                "e.swallow.volume",
                                                cw->frame_volume))
                    E_FREE_FUNC(cw->frame_volume, evas_object_del);
               }
          }
        else
          {
             cw->ec->changes.icon = 1;
             EC_CHANGED(cw->ec);
          }

        if (cw->ec->volume_control_enabled)
          {
             e_comp_object_frame_volume_update(obj);
          }
     }
   else
     {
        ERR("USER IS USING A BAD THEME! ABORT!!!!");
        evas_object_del(o);
        E_FREE_FUNC(cw->frame_icon, evas_object_del);
        E_FREE_FUNC(cw->frame_volume, evas_object_del);
     }
reshadow:
   if (cw->shobj)
     _e_comp_object_shadow_setup(cw);
   do
     {
        _e_comp_smart_cb_frame_recalc(cw, cw->smart_obj, NULL);
        if ((cw->x == -1) && (cw->y == -1) && cw->ec->new_client &&
            (!cw->ec->placed) && (!cw->ec->re_manage))
          {
             cw->ec->x = MAX(cw->ec->zone->x, cw->ec->client.x - cw->client_inset.l);
             cw->ec->y = MAX(cw->ec->zone->y, cw->ec->client.y - cw->client_inset.t);
          }
        /* this guarantees that we won't get blocked by the NOP check in the interceptor */
        cw->y = cw->x = -99999;
        if (pbg)
          evas_object_move(obj, cw->ec->x, cw->ec->y);
        else if (cw->ec->placed || (!cw->ec->new_client))
          {
             /* if no previous frame:
              * - reapply client_inset
              * - clamp to zone
              */
             int x, y;

             if (cw->ec->changes.size)
               {
                  x = cw->ec->x;
                  y = cw->ec->y;
               }
             else
               {
                  x = cw->ec->client.x, y = cw->ec->client.y;
                  x = MAX(cw->ec->zone->x, cw->ec->client.x - cw->client_inset.l);
                  y = MAX(cw->ec->zone->y, cw->ec->client.y - cw->client_inset.t);
               }
             evas_object_move(obj, x, y);
          }
     } while (0);

   if (cw->ec->maximized)
     {
        cw->ec->changes.need_maximize = 1;
        EC_CHANGED(cw->ec);
     }
   evas_object_smart_callback_call(cw->smart_obj, "frame_changed", NULL);
   if (cw->frame_object)
     {
        cw->frame_extends = !!edje_object_data_get(cw->frame_object, "frame_extends");
        edje_object_signal_callback_add(cw->frame_object, "*", "*",
                                        _e_comp_object_cb_signal_bind, cw);
     }
   else
     cw->frame_extends = 0;
   if (pbg && (!cw->frame_object))
     {
        evas_object_event_callback_del(cw->obj, EVAS_CALLBACK_MOUSE_IN, _e_comp_object_ssd_mouse_in);
        evas_object_event_callback_del(cw->obj, EVAS_CALLBACK_MOUSE_OUT, _e_comp_object_ssd_mouse_out);
     }
   else if (cw->obj)
     {
        evas_object_event_callback_add(cw->obj, EVAS_CALLBACK_MOUSE_IN, _e_comp_object_ssd_mouse_in, cw);
        evas_object_event_callback_add(cw->obj, EVAS_CALLBACK_MOUSE_OUT, _e_comp_object_ssd_mouse_out, cw);
     }
   evas_object_del(pbg);
   return EINA_TRUE;
}

E_API void
e_comp_object_signal_emit(Evas_Object *obj, const char *sig, const char *src)
{
   E_Comp_Object_Mover *prov;

   API_ENTRY;
   //INF("EMIT %p: %s %s", cw->ec, sig, src);
   edje_object_signal_emit(cw->shobj, sig, src);
   e_client_volume_object_emit(cw->ec, sig, src);
   if (cw->frame_object) edje_object_signal_emit(cw->frame_object, sig, src);
   if (cw->frame_icon && e_icon_edje_get(cw->frame_icon))
     edje_object_signal_emit(e_icon_edje_get(cw->frame_icon), sig, src);
   /* start with highest priority callback first */
   EINA_INLIST_REVERSE_FOREACH(_e_comp_object_movers, prov)
     {
        if (!e_util_glob_match(sig, prov->sig)) continue;
        if (prov->func(prov->data, obj, sig)) break;
     }
}

E_API void
e_comp_object_signal_callback_add(Evas_Object *obj, const char *sig, const char *src, Edje_Signal_Cb cb, const void *data)
{
   /* FIXME: at some point I guess this should use eo to inherit
    * -EDJE_OBJ_SUB_ID_SIGNAL_CALLBACK_ADD
    * -EDJE_OBJ_SUB_ID_SIGNAL_CALLBACK_DEL
    */
   API_ENTRY;
   edje_object_signal_callback_add(cw->shobj, sig, src, cb, (void*)data);
}

E_API void
e_comp_object_signal_callback_del(Evas_Object *obj, const char *sig, const char *src, Edje_Signal_Cb cb)
{
   API_ENTRY;
   edje_object_signal_callback_del(cw->shobj, sig, src, cb);
}

E_API void
e_comp_object_signal_callback_del_full(Evas_Object *obj, const char *sig, const char *src, Edje_Signal_Cb cb, const void *data)
{
   API_ENTRY;
   edje_object_signal_callback_del_full(cw->shobj, sig, src, cb, (void*)data);
}

E_API void
e_comp_object_damage(Evas_Object *obj, int x, int y, int w, int h)
{
   int tw, th;
   Eina_Rectangle rect;
   API_ENTRY;

   if (cw->ec->input_only) return;
   if (cw->nocomp) return;
   _e_comp_object_updates_init(cw);
   EINA_SAFETY_ON_NULL_RETURN(cw->updates);
   rect.x = x, rect.y = y;
   rect.w = w, rect.h = h;
   evas_object_smart_callback_call(obj, "damage", &rect);
   if (e_comp->nocomp)
     {
        cw->nocomp_need_update = EINA_TRUE;
        return;
     }
   /* ignore overdraw */
   if (cw->updates_full)
     {
        RENDER_DEBUG("IGNORED %p: %d,%d %dx%d", cw->ec, x, y, w, h);
        if (!e_object_is_del(E_OBJECT(cw->ec)))
          e_comp_object_render_update_add(obj);
        return;
     }
   /* clip rect to client surface */
   RENDER_DEBUG("DAMAGE(%d,%d %dx%d) CLIP(%dx%d)", x, y, w, h, cw->ec->client.w, cw->ec->client.h);
   E_RECTS_CLIP_TO_RECT(x, y, w, h, 0, 0, cw->ec->client.w, cw->ec->client.h);
   /* if rect is the total size of the client after clip, clear the updates
    * since this is guaranteed to be the whole region anyway
    */
   eina_tiler_area_size_get(cw->updates, &tw, &th);
   if ((w > tw) || (h > th))
     {
        RENDER_DEBUG("DAMAGE RESIZE %p: %dx%d", cw->ec, cw->ec->client.w, cw->ec->client.h);
        eina_tiler_clear(cw->updates);
        eina_tiler_area_size_set(cw->updates, cw->ec->client.w, cw->ec->client.h);
        x = 0, y = 0;
        tw = cw->ec->client.w, th = cw->ec->client.h;
     }
   if ((!x) && (!y) && (w == tw) && (h == th))
     {
        eina_tiler_clear(cw->updates);
        RENDER_DEBUG("DAMAGE FULL: %p", cw->ec);
        cw->updates_full = 1;
        cw->update_count = 0;
     }
   cw->update_count++;
   if (cw->update_count > UPDATE_MAX)
     {
        /* this is going to get really dumb, so just update the whole thing */
        eina_tiler_clear(cw->updates);
        cw->update_count = cw->updates_full = 1;
        eina_tiler_rect_add(cw->updates, &(Eina_Rectangle){0, 0, tw, th});
        RENDER_DEBUG("DAMAGE MAX: %dx%d", tw, th);
     }
   else
     {
        eina_tiler_rect_add(cw->updates, &(Eina_Rectangle){x, y, w, h});
        RENDER_DEBUG("DAMAGE: %d,%d %dx%d", x, y, w, h);
     }
   cw->updates_exist = 1;
   if (!e_object_is_del(E_OBJECT(cw->ec)))
     e_comp_object_render_update_add(obj);
}

E_API Eina_Bool
e_comp_object_damage_exists(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;
   return cw->updates_exist || cw->updates_full;
}

E_API void
e_comp_object_render_update_add(Evas_Object *obj)
{
   API_ENTRY;

   if (cw->ec->input_only || (!cw->updates) || (!cw->redirected)) return;
   if (e_object_is_del(E_OBJECT(cw->ec)))
     ERR("CAN'T RENDER A DELETED CLIENT!");
   if (!e_pixmap_usable_get(cw->ec->pixmap)) return;
   //if (e_client_util_resizing_get(cw->ec) && (e_pixmap_type_get(cw->ec->pixmap) == E_PIXMAP_TYPE_WL))
     //INF("WL RENDER UPDATE");
   if (!cw->update)
     {
        cw->update = 1;
        e_comp->updates = eina_list_append(e_comp->updates, cw->ec);
     }
   e_comp_client_frame_add(obj);
   e_comp_render_queue();
}

E_API void
e_comp_object_render_update_del(Evas_Object *obj)
{
   API_ENTRY;

   if (cw->ec->input_only || (!cw->updates)) return;
   if (!cw->update) return;
   cw->update = 0;
   /* this gets called during comp animating to clear the update flag */
   if (e_comp->grabbed) return;
   e_comp->updates = eina_list_remove(e_comp->updates, cw->ec);
}

E_API void
e_comp_object_shape_apply(Evas_Object *obj)
{
   Eina_List *l;
   Evas_Object *o;
   unsigned int i, *pix, *p;
   int w, h, px, py;

   API_ENTRY;
   if (!cw->ec) return; //NYI
   if (cw->ec->shaped)
     {
        if ((cw->ec->shape_rects_num >= 1) &&
            (!_e_comp_shaped_check(cw->ec->client.w, cw->ec->client.h, cw->ec->shape_rects, cw->ec->shape_rects_num)))
           return;
     }
   if (cw->native)
     {
        ERR("BUGGER: shape with native surface? cw=%p", cw);
        return;
     }
   evas_object_image_size_get(cw->obj, &w, &h);
   if ((w < 1) || (h < 1)) return;

   //INF("SHAPE RENDER %p", cw->ec);

   p = pix = evas_object_image_data_get(cw->obj, 1);
   if (!pix)
     {
        evas_object_image_data_set(cw->obj, pix);
        _e_comp_object_alpha_set(cw);
        return;
     }
   if ((cw->ec->shaped) && (cw->ec->shape_rects))
     {
        unsigned char *spix, *sp;

        spix = calloc(w * h, sizeof(unsigned char));
        RENDER_DEBUG("SHAPE [%p] rects %i", cw->ec, cw->ec->shape_rects_num);
        for (i = 0; i < cw->ec->shape_rects_num; i++)
          {
             int rx, ry, rw, rh;

             rx = cw->ec->shape_rects[i].x; ry = cw->ec->shape_rects[i].y;
             rw = cw->ec->shape_rects[i].w; rh = cw->ec->shape_rects[i].h;
             E_RECTS_CLIP_TO_RECT(rx, ry, rw, rh, 0, 0, w, h);
             sp = spix + (w * ry) + rx;
             for (py = 0; py < rh; py++)
               {
                  for (px = 0; px < rw; px++)
                    {
                       *sp = 0xff; sp++;
                    }
                  sp += w - rw;
               }
          }
        sp = spix;
        for (py = 0; py < h; py++)
          {
             for (px = 0; px < w; px++)
               {
                  unsigned int mask, imask;

                  mask = ((unsigned int)(*sp)) << 24;
                  imask = mask >> 8;
                  imask |= imask >> 8;
                  imask |= imask >> 8;
                  *p = mask | (*p & imask);
                  //if (*sp) *p = 0xff000000 | *p;
                  //else *p = 0x00000000;
                  sp++;
                  p++;
               }
          }
        free(spix);
     }
   else
      {
         for (py = 0; py < h; py++)
           {
              for (px = 0; px < w; px++)
                *p |= 0xff000000;
           }
      }
   evas_object_image_data_set(cw->obj, pix);
   _e_comp_object_alpha_set(cw);
   evas_object_image_data_update_add(cw->obj, 0, 0, w, h);
   EINA_LIST_FOREACH(cw->obj_mirror, l, o)
     {
        evas_object_image_data_set(o, pix);
        evas_object_image_data_update_add(o, 0, 0, w, h);
        evas_object_image_alpha_set(o, 1);
     }
// don't need to fix alpha chanel as blending
// should be totally off here regardless of
// alpha channel content
}

/* helper function to simplify toggling of redirection for display servers which support it */
E_API void
e_comp_object_redirected_set(Evas_Object *obj, Eina_Bool set)
{
   API_ENTRY;

   set = !!set;
   if (cw->redirected == set) return;
   cw->redirected = set;
   if (set)
     {
        if (cw->updates_exist)
          e_comp_object_render_update_add(obj);
        else
          e_comp_object_damage(obj, 0, 0, cw->w, cw->h);
        evas_object_smart_callback_call(obj, "redirected", NULL);
     }
   else
     {
        Eina_List *l;
        Evas_Object *o;

        if (cw->ec->pixmap)
          e_pixmap_clear(cw->ec->pixmap);
        if (cw->native)
          evas_object_image_native_surface_set(cw->obj, NULL);
        evas_object_image_size_set(cw->obj, 1, 1);
        evas_object_image_data_set(cw->obj, NULL);
        EINA_LIST_FOREACH(cw->obj_mirror, l, o)
          {
             evas_object_image_size_set(o, 1, 1);
             evas_object_image_data_set(o, NULL);
             if (cw->native)
               evas_object_image_native_surface_set(o, NULL);
          }
        cw->native = 0;
        e_comp_object_render_update_del(obj);
        evas_object_smart_callback_call(obj, "unredirected", NULL);
     }
}

E_API void
e_comp_object_native_surface_set(Evas_Object *obj, Eina_Bool set)
{
   Evas_Native_Surface ns;
   Eina_List *l;
   Evas_Object *o;

   API_ENTRY;
   EINA_SAFETY_ON_NULL_RETURN(cw->ec);
   if (cw->ec->input_only) return;
   set = !!set;

   if (set)
     {
        switch (e_comp->comp_type)
          {
           case E_PIXMAP_TYPE_X:
             /* native requires gl enabled, texture from pixmap enabled, and a non-shaped client */
             set = e_comp->gl &&
                   e_comp_config_get()->texture_from_pixmap &&
                   !cw->ec->shaped;
             break;
           case E_PIXMAP_TYPE_WL:
             set = !e_pixmap_is_pixels(cw->ec->pixmap);
             break;
           default:
             set = 0;
          }
        if (set)
          set = (!!cw->ns) || e_pixmap_native_surface_init(cw->ec->pixmap, &ns);
     }
   if ((!set) && (!cw->native)) return;

   cw->native = set;
   evas_object_image_native_surface_set(cw->obj, set && (!cw->blanked) ? (cw->ns ?: &ns) : NULL);
   _e_comp_object_alpha_set(cw);
   EINA_LIST_FOREACH(cw->obj_mirror, l, o)
     {
        evas_object_image_native_surface_set(o, set ? (cw->ns ?: &ns) : NULL);
        evas_object_image_alpha_set(o, !!cw->ns ? 1 : cw->ec->argb);
     }
}

E_API void
e_comp_object_native_surface_override(Evas_Object *obj, Evas_Native_Surface *ns)
{
   API_ENTRY;
   if (cw->ec->input_only) return;
   E_FREE(cw->ns);
   if (ns)
     cw->ns = (void *)eina_memdup((unsigned char *)ns, sizeof(Evas_Native_Surface), 0);
   _e_comp_object_alpha_set(cw);
   if (cw->native)
     e_comp_object_native_surface_set(obj, cw->native);
   e_comp_object_damage(obj, 0, 0, cw->w, cw->h);
}

E_API void
e_comp_object_blank(Evas_Object *obj, Eina_Bool set)
{
   API_ENTRY;

   set = !!set;

   if (cw->blanked == set) return;
   cw->blanked = set;
   _e_comp_object_alpha_set(cw);
   if (set)
     {
        evas_object_image_native_surface_set(cw->obj, NULL);
        evas_object_image_data_set(cw->obj, NULL);
        return;
     }
   if (cw->native)
     e_comp_object_native_surface_set(obj, 1);
   e_comp_object_damage(obj, 0, 0, cw->w, cw->h);
}

/* mark an object as dirty and setup damages */
E_API void
e_comp_object_dirty(Evas_Object *obj)
{
   Eina_Iterator *it;
   Eina_Rectangle *rect;
   Eina_List *ll;
   Evas_Object *o;
   int w, h;
   Eina_Bool dirty, visible, alpha;
   int bx, by, bxx, byy;

   API_ENTRY;
   /* only actually dirty if pixmap is available */
   dirty = e_pixmap_size_get(cw->ec->pixmap, &w, &h);
   visible = cw->visible;
   if (!dirty) w = h = 1;
   evas_object_image_pixels_dirty_set(cw->obj, cw->blanked ? 0 : dirty);
   if (!dirty)
     evas_object_image_data_set(cw->obj, e_pixmap_image_data_get(cw->ec->pixmap));
   evas_object_image_size_set(cw->obj, w, h);

   RENDER_DEBUG("SIZE [%p]: %dx%d", cw->ec, w, h);
   if (cw->pending_updates)
     eina_tiler_area_size_set(cw->pending_updates, w, h);

   alpha = evas_object_image_alpha_get(cw->obj);
   EINA_LIST_FOREACH(cw->obj_mirror, ll, o)
     {
        //evas_object_image_border_set(o, bx, by, bxx, byy);
        //evas_object_image_border_center_fill_set(o, EVAS_BORDER_FILL_SOLID);
        if (!dirty)
          evas_object_image_data_set(o, NULL);
        evas_object_image_size_set(o, w, h);
        evas_object_image_pixels_dirty_set(o, dirty);
        evas_object_image_alpha_set(o, alpha);
        visible |= evas_object_visible_get(o);
     }
   if (!dirty)
     {
        if (!e_object_is_del(E_OBJECT(cw->ec)))
          ERR("ERROR FETCHING PIXMAP FOR %p", cw->ec);
        return;
     }

   e_pixmap_image_opaque_get(cw->ec->pixmap, &bx, &by, &bxx, &byy);
   if (bxx && byy)
     {
        bxx = w - (bx + bxx), byy = h - (by + byy);
        // XXX: FIXME: - keep at least ONE border > 0 to allow cutouts to work
        // evas doesn't have a good fix for this right now.... also
        // we still BLEND the center bit anyway as we dont switch to non
        // blend ... so ugh. evas issue tho.
        if (byy < 1) byy = 1;
        evas_object_image_border_set(cw->obj, bx, bxx, by, byy);
     }
   else
     {
        bx = by = bxx = byy = 0;
        evas_object_image_border_set(cw->obj, bx, bxx, by, byy);
        if (cw->client_inset.calc && (!cw->frame_object)) //CSD
          {
             bx = -cw->client_inset.l + 4, by = -cw->client_inset.t + 4;
             bxx = -cw->client_inset.r, byy = -cw->client_inset.b;
          }
     }
   if ((cw->border.bx != bx) || (cw->border.by != by) ||
       (cw->border.bxx != bxx) || (cw->border.byy != byy) ||
       (cw->border.w != w) || (cw->border.h != h))
   {
      Edje_Message_Int_Set *msg;
      Edje_Message_Int msg2;
      Eina_Bool id = (bx || by || bxx || byy);

      cw->border.bx = bx;
      cw->border.by = by;
      cw->border.bxx = bxx;
      cw->border.byy = byy;
      cw->border.w = w;
      cw->border.h = h;
      msg = alloca(sizeof(Edje_Message_Int_Set) + (sizeof(int) * 3));
      msg->count = 4;
      msg->val[0] = bx;
      msg->val[1] = by;
      msg->val[2] = bxx;
      msg->val[3] = byy;
      edje_object_message_send(cw->shobj, EDJE_MESSAGE_INT_SET, 1, msg);
      msg2.val = id;
      edje_object_message_send(cw->shobj, EDJE_MESSAGE_INT, 0, &msg2);
   }

   e_comp_object_native_surface_set(obj, 1);
   it = eina_tiler_iterator_new(cw->updates);
   EINA_ITERATOR_FOREACH(it, rect)
     {
        RENDER_DEBUG("UPDATE ADD [%p]: %d %d %dx%d", cw->ec, rect->x, rect->y, rect->w, rect->h);
        evas_object_image_data_update_add(cw->obj, rect->x, rect->y, rect->w, rect->h);
        EINA_LIST_FOREACH(cw->obj_mirror, ll, o)
          evas_object_image_data_update_add(o, rect->x, rect->y, rect->w, rect->h);
        if (cw->pending_updates)
          eina_tiler_rect_add(cw->pending_updates, rect);
     }
   eina_iterator_free(it);
   if (cw->pending_updates)
     eina_tiler_clear(cw->updates);
   else
     {
        cw->pending_updates = cw->updates;
        cw->updates = eina_tiler_new(w, h);
        eina_tiler_tile_size_set(cw->updates, 1, 1);
     }
   cw->update_count = cw->updates_full = cw->updates_exist = 0;
   evas_object_smart_callback_call(obj, "dirty", NULL);
   if (cw->real_hid || cw->visible || (!visible) || (!cw->pending_updates) || cw->native) return;
   /* force render if main object is hidden but mirrors are visible */
   RENDER_DEBUG("FORCING RENDER %p", cw->ec);
   e_comp_object_render(obj);
}

E_API Eina_Bool
e_comp_object_render(Evas_Object *obj)
{
   Eina_Iterator *it = NULL;
   Eina_Rectangle *r;
   Eina_List *l;
   Evas_Object *o;
   int stride, pw, ph;
   unsigned int *pix, *srcpix;
   Eina_Bool ret = EINA_FALSE;

   API_ENTRY EINA_FALSE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(cw->ec, EINA_FALSE);
   if (cw->ec->input_only) return EINA_TRUE;
   e_comp_object_render_update_del(obj);
   if (!e_pixmap_size_get(cw->ec->pixmap, &pw, &ph)) return EINA_FALSE;
   //if (e_pixmap_type_get(cw->ec->pixmap) == E_PIXMAP_TYPE_WL)
     //INF("WL RENDER!");

   if (!cw->pending_updates)
     {
        WRN("RENDER [%p]: NO RECTS!", cw->ec);
        evas_object_image_data_set(cw->obj, NULL);
        EINA_LIST_FOREACH(cw->obj_mirror, l, o)
          evas_object_image_data_set(o, NULL);
        return EINA_FALSE;
     }

   evas_object_image_pixels_dirty_set(cw->obj, EINA_FALSE);

   RENDER_DEBUG("RENDER SIZE: %dx%d", pw, ph);

   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        pix = e_pixmap_image_data_get(cw->ec->pixmap);
        ret = EINA_TRUE;
        goto end;
     }

   it = eina_tiler_iterator_new(cw->pending_updates);
   if (e_pixmap_image_is_argb(cw->ec->pixmap))
     {
        pix = e_pixmap_image_data_get(cw->ec->pixmap);
        EINA_ITERATOR_FOREACH(it, r)
          {
             E_RECTS_CLIP_TO_RECT(r->x, r->y, r->w, r->h, 0, 0, pw, ph);
             /* get pixmap data from rect region on display server into memory */
             ret = e_pixmap_image_draw(cw->ec->pixmap, r);
             if (!ret)
               {
                  WRN("UPDATE [%p]: %i %i %ix%i FAIL(%u)!!!!!!!!!!!!!!!!!", cw->ec, r->x, r->y, r->w, r->h, cw->failures);
                  if (++cw->failures < FAILURE_MAX)
                    e_comp_object_damage(obj, 0, 0, pw, ph);
                  else
                    {
                       DELD(cw->ec, 2);
                       e_object_del(E_OBJECT(cw->ec));
                       return EINA_FALSE;
                    }
                  break;
               }
             RENDER_DEBUG("UPDATE [%p] %i %i %ix%i", cw->ec, r->x, r->y, r->w, r->h);
          }
        if (!it) pix = NULL;
        goto end;
     }

   pix = evas_object_image_data_get(cw->obj, EINA_TRUE);
   stride = evas_object_image_stride_get(cw->obj);
   srcpix = e_pixmap_image_data_get(cw->ec->pixmap);
   EINA_ITERATOR_FOREACH(it, r)
     {
        E_RECTS_CLIP_TO_RECT(r->x, r->y, r->w, r->h, 0, 0, pw, ph);
        ret = e_pixmap_image_draw(cw->ec->pixmap, r);
        if (!ret)
          {
             WRN("UPDATE [%p]: %i %i %ix%i FAIL(%u)!!!!!!!!!!!!!!!!!", cw->ec, r->x, r->y, r->w, r->h, cw->failures);
             if (++cw->failures < FAILURE_MAX)
               e_comp_object_damage(obj, 0, 0, pw, ph);
             else
               {
                  DELD(cw->ec, 3);
                  e_object_del(E_OBJECT(cw->ec));
                  return EINA_FALSE;
               }
             break;
          }
        e_pixmap_image_data_argb_convert(cw->ec->pixmap, pix, srcpix, r, stride);
        RENDER_DEBUG("UPDATE [%p]: %d %d %dx%d -- pix = %p", cw->ec, r->x, r->y, r->w, r->h, pix);
     }
   if (!it) pix = NULL;
end:
   eina_iterator_free(it);
   evas_object_image_data_set(cw->obj, cw->blanked ? NULL : pix);
   _e_comp_object_alpha_set(cw);

   E_FREE_FUNC(cw->pending_updates, eina_tiler_free);
   if (ret)
     e_comp_client_post_update_add(cw->ec);
   return ret;
}

E_API Evas_Object *
e_comp_object_agent_add(Evas_Object *obj)
{
   Evas_Object *o;

   API_ENTRY NULL;

   o = evas_object_rectangle_add(e_comp->evas);
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_pass_events_set(o, 1);
   evas_object_geometry_set(o, cw->ec->x, cw->ec->y, cw->ec->w, cw->ec->h);

   cw->obj_agent = eina_list_append(cw->obj_agent, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, _e_comp_object_cb_agent_resize, cw);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOVE, _e_comp_object_cb_agent_move, cw);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _e_comp_object_cb_agent_del, cw);
   return o;
}

static void
_e_comp_object_frame_mirror_resize_cb(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED)
{
   E_Client *ec;
   Evas_Coord w, h;

   ec = evas_object_data_get(obj, "ec");
   if (!ec) return;
   if (!evas_object_data_get(obj, "do_shadow")) return;
   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   if ((w == ec->w) && (h == ec->h))
     {
        if (!evas_object_data_get(obj, "shadow_off"))
          {
             edje_object_signal_emit(obj, "e,state,shadow,off", "e");
             evas_object_data_del(obj, "shadow_on");
             evas_object_data_set(obj, "shadow_off", obj);
          }
     }
   else
     {
        if (!evas_object_data_get(obj, "shadow_on"))
          {
             edje_object_signal_emit(obj, "e,state,shadow,on", "e");
             evas_object_data_del(obj, "shadow_off");
             evas_object_data_set(obj, "shadow_on", obj);
          }
     }
}

static void
_e_comp_object_frame_mirror_src_frame_resize_cb(void *data, Evas_Object *obj, void *info EINA_UNUSED)
{
   Evas_Object *o = data;
   Evas_Object *o_zoomap;

   API_ENTRY;

   o_zoomap = evas_object_data_get(o, "zoomap");
   if (!o_zoomap) return;
   e_zoomap_child_resize(o_zoomap, cw->ec->w, cw->ec->h);
}

static void
_e_comp_object_frame_mirror_src_frame_shadow_changed_cb(void *data, Evas_Object *obj, void *info EINA_UNUSED)
{
   Evas_Object *o, *o_frame, *o_client, *o_zoomap, *o_sh = data;
   const char *file = NULL, *group = NULL;

   API_ENTRY;

   o_frame = evas_object_data_get(o_sh, "frame");
   o_client = evas_object_data_get(o_sh, "client");
   o_zoomap = evas_object_data_get(o_sh, "zoomap");

   edje_object_file_get(cw->shobj, &file, &group);
   edje_object_file_set(o_sh, file, group);
   evas_object_data_del(o_sh, "shadow_off");
   evas_object_data_del(o_sh, "shadow_on");
   if (e_client_util_shadow_state_get(cw->ec))
     {
        evas_object_data_set(o_sh, "do_shadow", o_sh);
        edje_object_signal_emit(o_sh, "e,state,shadow,on", "e");
     }
   else
     {
        evas_object_data_del(o_sh, "do_shadow");
        edje_object_signal_emit(o_sh, "e,state,shadow,off", "e");
     }
   edje_object_signal_emit(o_sh, "e,state,visible", "e");

   edje_object_file_get(cw->frame_object, &file, &group);
   if ((file) && (group))
     {
        if (!o_zoomap)
          {
             o = o_zoomap = e_zoomap_add(e_comp->evas);
             evas_object_data_set(o_sh, "zoomap", o);
             e_zoomap_smooth_set(o, e_comp_config_get()->smooth_windows);
             edje_object_part_swallow(o_sh, "e.swallow.content", o);
             evas_object_show(o);
          }

        if (!o_frame)
          {
             o = o_frame = edje_object_add(e_comp->evas);
             evas_object_data_set(o_sh, "frame", o);
             edje_object_file_set(o, file, group);
             e_zoomap_child_set(o_zoomap, o);
             e_zoomap_solid_set(o, EINA_FALSE);
             evas_object_show(o);
          }
        edje_object_file_set(o_frame, file, group);
        edje_object_signal_emit(o_frame, "e,version,22", "e");
        edje_object_signal_emit(o_frame, "e,state,focused", "e");
        edje_object_signal_emit(o_frame, "e,state,visible", "e");
        edje_object_part_text_set(o_frame, "e.text.title", cw->frame_name);
     }
   else
     {
        o = evas_object_data_get(o_sh, "frame");
        if (o) evas_object_del(o);
        evas_object_data_del(o_sh, "frame");

        o = evas_object_data_get(o_sh, "zoomap");
        if (o) evas_object_del(o);
        evas_object_data_del(o_sh, "zoomap");
     }

   if (o_frame) edje_object_part_swallow(o_frame, "e.swallow.client",  o_client);
   else         edje_object_part_swallow(o_sh,    "e.swallow.content", o_client);
   evas_object_show(o_client);
}

static void
_e_comp_object_frame_mirror_del_cb(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED)
{
   Evas_Object *o;

   o = evas_object_data_get(obj, "comp_obj");
   if (o)
     {
        evas_object_smart_callback_del_full(o, "frame_changed", _e_comp_object_frame_mirror_src_frame_shadow_changed_cb, obj);
        evas_object_smart_callback_del_full(o, "shadow_change", _e_comp_object_frame_mirror_src_frame_shadow_changed_cb, obj);
        evas_object_smart_callback_del_full(o, "client_resize", _e_comp_object_frame_mirror_src_frame_resize_cb, obj);
     }

   o = evas_object_data_get(obj, "client");
   if (o) evas_object_del(o);
   evas_object_data_del(obj, "client");

   o = evas_object_data_get(obj, "icon");
   if (o) evas_object_del(o);
   evas_object_data_del(obj, "icon");

   o = evas_object_data_get(obj, "frame");
   if (o) evas_object_del(o);
   evas_object_data_del(obj, "frame");

   o = evas_object_data_get(obj, "zoomap");
   if (o) evas_object_del(o);
   evas_object_data_del(obj, "zoomap");
}

E_API Evas_Object *
e_comp_object_util_frame_mirror_add(Evas_Object *obj)
{
   Evas_Object *o, *o_frame = NULL, *o_client, *o_icon, *o_zoomap = NULL, *o_sh;
   const char *file = NULL, *group = NULL;

   API_ENTRY NULL;

   o = o_sh = edje_object_add(e_comp->evas);
   evas_object_data_set(o, "ec", cw->ec);
   edje_object_file_get(cw->shobj, &file, &group);
   edje_object_file_set(o, file, group);
   if (e_client_util_shadow_state_get(cw->ec))
     {
        evas_object_data_set(o, "do_shadow", o_sh);
        edje_object_signal_emit(o, "e,state,shadow,on", "e");
     }
   else
     edje_object_signal_emit(o, "e,state,shadow,off", "e");
   edje_object_signal_emit(o, "e,state,visible", "e");

   edje_object_file_get(cw->frame_object, &file, &group);

   if ((file) && (group))
     {
        o = o_zoomap = e_zoomap_add(e_comp->evas);
        evas_object_data_set(o_sh, "zoomap", o);
        e_zoomap_smooth_set(o, e_comp_config_get()->smooth_windows);
        edje_object_part_swallow(o_sh, "e.swallow.content", o);
        evas_object_show(o);

        o = o_frame = edje_object_add(e_comp->evas);
        evas_object_data_set(o_sh, "frame", o);
        edje_object_file_set(o, file, group);
        edje_object_signal_emit(o, "e,version,22", "e");
        edje_object_signal_emit(o, "e,state,focused", "e");
        edje_object_signal_emit(o, "e,state,visible", "e");
        if (cw->ec->shaded)
          edje_object_signal_emit(o, "e,state,shaded", "e");
        edje_object_part_text_set(o, "e.text.title", cw->frame_name);
        e_zoomap_child_set(o_zoomap, o);
        e_zoomap_solid_set(o, EINA_FALSE);
        evas_object_show(o);
     }

   o = o_client = e_comp_object_util_mirror_add(obj);
   evas_object_data_set(o_sh, "client", o);
   if (o_frame) edje_object_part_swallow(o_frame, "e.swallow.client",  o);
   else         edje_object_part_swallow(o_sh,    "e.swallow.content", o);
   evas_object_show(o);

   if (o_frame)
     {
        o = o_icon = e_client_icon_add(cw->ec, e_comp->evas);
        evas_object_data_set(o_sh, "icon", o);
        edje_object_part_swallow(o_frame, "e.swallow.icon", o);
        evas_object_show(o);
     }

   evas_object_event_callback_add(o_sh, EVAS_CALLBACK_DEL, _e_comp_object_frame_mirror_del_cb, NULL);
   evas_object_event_callback_add(o_sh, EVAS_CALLBACK_RESIZE, _e_comp_object_frame_mirror_resize_cb, NULL);
   evas_object_data_set(o_sh, "comp_obj", obj);
   evas_object_smart_callback_add(obj, "frame_changed", _e_comp_object_frame_mirror_src_frame_shadow_changed_cb, o_sh);
   evas_object_smart_callback_add(obj, "shadow_change", _e_comp_object_frame_mirror_src_frame_shadow_changed_cb, o_sh);
   evas_object_smart_callback_add(obj, "client_resize", _e_comp_object_frame_mirror_src_frame_resize_cb, o_sh);
   if (o_zoomap)
     e_zoomap_child_resize(o_zoomap, cw->ec->w, cw->ec->h);
   return o_sh;
}

/* create a duplicate of an evas object */
E_API Evas_Object *
e_comp_object_util_mirror_add(Evas_Object *obj)
{
   Evas_Object *o;
   int w, h;
   unsigned int *pix = NULL;

   SOFT_ENTRY(NULL);

   if (!cw)
     cw = evas_object_data_get(obj, "comp_mirror");
   if (!cw)
     {
        o = evas_object_image_filled_add(evas_object_evas_get(obj));
        evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
        evas_object_image_smooth_scale_set(o, e_comp_config_get()->smooth_windows);
        evas_object_image_alpha_set(o, 1);
        evas_object_image_source_set(o, obj);
        return o;
     }
   if ((!cw->ec) || (!e_pixmap_size_get(cw->ec->pixmap, &w, &h))) return NULL;
   o = evas_object_image_filled_add(evas_object_evas_get(obj));
   evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
   evas_object_image_smooth_scale_set(o, e_comp_config_get()->smooth_windows);
   cw->obj_mirror = eina_list_append(cw->obj_mirror, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _e_comp_object_cb_mirror_del, cw);
   evas_object_event_callback_add(o, EVAS_CALLBACK_SHOW, _e_comp_object_cb_mirror_show, cw);
   evas_object_event_callback_add(o, EVAS_CALLBACK_HIDE, _e_comp_object_cb_mirror_hide, cw);
   evas_object_data_set(o, "E_Client", cw->ec);
   evas_object_data_set(o, "comp_mirror", cw);

   evas_object_image_size_set(o, w, h);
   evas_object_image_alpha_set(o, evas_object_image_alpha_get(cw->obj));

   if (cw->ec->shaped)
     {
        if (e_pixmap_image_data_get(cw->ec->pixmap))
          pix = evas_object_image_data_get(cw->obj, 0);
     }
   else
     {
        if (cw->native)
          {
             if (cw->ns)
               evas_object_image_native_surface_set(o, cw->ns);
             else
               {
                  Evas_Native_Surface ns;

                  e_pixmap_native_surface_init(cw->ec->pixmap, &ns);
                  evas_object_image_native_surface_set(o, &ns);
               }
          }
        else
          pix = e_pixmap_image_data_get(cw->ec->pixmap);
     }
   if (pix)
     {
      Eina_Bool dirty;
      //int bx, by, bxx, byy;

      dirty = evas_object_image_pixels_dirty_get(cw->obj);
      evas_object_image_pixels_dirty_set(o, dirty);
      //e_pixmap_image_opaque_get(cw->ec->pixmap, &bx, &by, &bxx, &byy);
      //if (bxx && byy)
        //bxx = cw->ec->client.w - (bx + bxx), byy = cw->ec->client.h - (by + byy);
      //else
        //bx = by = bxx = byy = 0;
      //evas_object_image_border_set(o, bx, by, bxx, byy);
      //evas_object_image_border_center_fill_set(o, EVAS_BORDER_FILL_SOLID);
      evas_object_image_data_set(o, pix);
      if (cw->ec->shaped)
        evas_object_image_data_set(cw->obj, pix);
      if (dirty)
        evas_object_image_data_update_add(o, 0, 0, w, h);
   }
   evas_object_image_pixels_get_callback_set(o, _e_comp_object_mirror_pixels_get, cw);
   return o;
}

//////////////////////////////////////////////////////

E_API Eina_Bool
e_comp_object_effect_allowed_get(Evas_Object *obj)
{
   API_ENTRY EINA_FALSE;

   if (!cw->shobj) return EINA_FALSE;
   return EINA_TRUE;
}

/* setup an api effect for a client */
E_API Eina_Bool
e_comp_object_effect_set(Evas_Object *obj, const char *effect)
{
   char buf[4096];
   Eina_Stringshare *grp;

   API_ENTRY EINA_FALSE;
   if (!cw->shobj) return EINA_FALSE; //input window

   if (!effect) effect = "none";
   snprintf(buf, sizeof(buf), "e/comp/effects/%s", effect);
   edje_object_file_get(cw->effect_obj, NULL, &grp);
   cw->effect_set = !eina_streq(effect, "none");
   if (!e_util_strcmp(buf, grp)) return cw->effect_set;
   if (!e_theme_edje_object_set(cw->effect_obj, "base/theme/comp", buf))
     {
        snprintf(buf, sizeof(buf), "e/comp/effects/auto/%s", effect);
        if (!e_theme_edje_object_set(cw->effect_obj, "base/theme/comp", buf))
          if (!e_theme_edje_object_set(cw->effect_obj, "base/theme/comp", "e/comp/effects/none"))
            {
               if (cw->effect_running)
                 {
                    if (!e_comp_object_effect_stop(obj, evas_object_data_get(cw->effect_obj, "_e_comp.end_cb")))
                      return EINA_FALSE;
                 }
               cw->effect_set = EINA_FALSE;
               return cw->effect_set;
            }
     }
   if (cw->effect_running)
     {
        if (!e_comp_object_effect_stop(obj, evas_object_data_get(cw->effect_obj, "_e_comp.end_cb")))
          return EINA_FALSE;
     }
   edje_object_part_swallow(cw->effect_obj, "e.swallow.content", cw->shobj);
   if (cw->effect_clip)
     {
        evas_object_clip_unset(cw->clip);
        cw->effect_clip = 0;
     }
   cw->effect_clip_able = !edje_object_data_get(cw->effect_obj, "noclip");
   return cw->effect_set;
}

/* set params for embryo scripts in effect */
E_API void
e_comp_object_effect_params_set(Evas_Object *obj, int id, int *params, unsigned int count)
{
   Edje_Message_Int_Set *msg;
   unsigned int x;

   API_ENTRY;
   EINA_SAFETY_ON_NULL_RETURN(params);
   EINA_SAFETY_ON_FALSE_RETURN(count);
   if (!cw->effect_set) return;

   msg = alloca(sizeof(Edje_Message_Int_Set) + ((count - 1) * sizeof(int)));
   msg->count = (int)count;
   for (x = 0; x < count; x++)
      msg->val[x] = params[x];
   edje_object_message_send(cw->effect_obj, EDJE_MESSAGE_INT_SET, id, msg);
   edje_object_message_signal_process(cw->effect_obj);
}

static void
_e_comp_object_effect_end_cb(void *data, Evas_Object *obj, const char *emission, const char *source)
{
   Edje_Signal_Cb end_cb;
   void *end_data;
   E_Comp_Object *cw = data;

   edje_object_signal_callback_del_full(obj, "e,action,done", "e", _e_comp_object_effect_end_cb, NULL);
   cw->effect_running = 0;
   if (!_e_comp_object_animating_end(cw)) return;
   e_comp_shape_queue();
   end_cb = evas_object_data_get(obj, "_e_comp.end_cb");
   if (!end_cb) return;
   end_data = evas_object_data_get(obj, "_e_comp.end_data");
   end_cb(end_data, cw->smart_obj, emission, source);

}

/* clip effect to client's zone */
E_API void
e_comp_object_effect_clip(Evas_Object *obj)
{
   API_ENTRY;
   if (!cw->ec->zone) return;
   if (cw->effect_clip) e_comp_object_effect_unclip(cw->smart_obj);
   if (!cw->effect_clip_able) return;
   evas_object_clip_set(cw->smart_obj, cw->ec->zone->bg_clip_object);
   cw->effect_clip = 1;
}

/* unclip effect from client's zone */
E_API void
e_comp_object_effect_unclip(Evas_Object *obj)
{
   API_ENTRY;
   if (!cw->effect_clip) return;
   evas_object_clip_unset(cw->smart_obj);
   cw->effect_clip = 0;
}

/* start effect, running end_cb after */
E_API Eina_Bool
e_comp_object_effect_start(Evas_Object *obj, Edje_Signal_Cb end_cb, const void *end_data)
{
   API_ENTRY EINA_FALSE;
   EINA_SAFETY_ON_NULL_RETURN_VAL(cw->ec, EINA_FALSE); //NYI
   if (!cw->effect_set) return EINA_FALSE;
   e_comp_object_effect_clip(obj);
   edje_object_signal_callback_del(cw->effect_obj, "e,action,done", "e", _e_comp_object_effect_end_cb);

   edje_object_signal_callback_add(cw->effect_obj, "e,action,done", "e", _e_comp_object_effect_end_cb, cw);
   evas_object_data_set(cw->effect_obj, "_e_comp.end_cb", end_cb);
   evas_object_data_set(cw->effect_obj, "_e_comp.end_data", end_data);

   edje_object_signal_emit(cw->effect_obj, "e,action,go", "e");
   _e_comp_object_animating_begin(cw);
   cw->effect_running = 1;
   return EINA_TRUE;
}

/* stop a currently-running effect immediately */
E_API Eina_Bool
e_comp_object_effect_stop(Evas_Object *obj, Edje_Signal_Cb end_cb)
{
   API_ENTRY EINA_FALSE;
   if (evas_object_data_get(cw->effect_obj, "_e_comp.end_cb") != end_cb) return EINA_TRUE;
   e_comp_object_effect_unclip(obj);
   if (cw->effect_clip)
     {
        evas_object_clip_unset(cw->effect_obj);
        cw->effect_clip = 0;
     }
   edje_object_signal_emit(cw->effect_obj, "e,action,stop", "e");
   edje_object_signal_callback_del_full(cw->effect_obj, "e,action,done", "e", _e_comp_object_effect_end_cb, cw);
   cw->effect_running = 0;
   return _e_comp_object_animating_end(cw);
}

static int
_e_comp_object_effect_mover_sort_cb(E_Comp_Object_Mover *a, E_Comp_Object_Mover *b)
{
   return a->pri - b->pri;
}

/* add a function to trigger based on signal emissions for the purpose of modifying effects */
E_API E_Comp_Object_Mover *
e_comp_object_effect_mover_add(int pri, const char *sig, E_Comp_Object_Mover_Cb provider, const void *data)
{
   E_Comp_Object_Mover *prov;

   prov = E_NEW(E_Comp_Object_Mover, 1);
   EINA_SAFETY_ON_NULL_RETURN_VAL(prov, NULL);
   prov->func = provider;
   prov->data = (void*)data;
   prov->pri = pri;
   prov->sig = sig;
   _e_comp_object_movers = eina_inlist_sorted_insert(_e_comp_object_movers, EINA_INLIST_GET(prov),
     (Eina_Compare_Cb)_e_comp_object_effect_mover_sort_cb);
   return prov;
}

E_API void
e_comp_object_effect_mover_del(E_Comp_Object_Mover *prov)
{
   EINA_SAFETY_ON_NULL_RETURN(prov);
   _e_comp_object_movers = eina_inlist_remove(_e_comp_object_movers, EINA_INLIST_GET(prov));
   free(prov);
}
////////////////////////////////////

static void
_e_comp_object_autoclose_cleanup(Eina_Bool already_del)
{
   if (e_comp->autoclose.obj)
     {
        e_comp_ungrab_input(0, 1);
        if (e_comp->autoclose.del_cb)
          e_comp->autoclose.del_cb(e_comp->autoclose.data, e_comp->autoclose.obj);
        else if (!already_del)
          {
             evas_object_hide(e_comp->autoclose.obj);
             E_FREE_FUNC(e_comp->autoclose.obj, evas_object_del);
          }
        E_FREE_FUNC(e_comp->autoclose.rect, evas_object_del);
     }
   e_comp->autoclose.obj = NULL;
   e_comp->autoclose.data = NULL;
   e_comp->autoclose.del_cb = NULL;
   e_comp->autoclose.key_cb = NULL;
   E_FREE_FUNC(e_comp->autoclose.key_handler, ecore_event_handler_del);
   e_comp_shape_queue();
}

static Eina_Bool
_e_comp_object_autoclose_key_down_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Key *ev = event;
   Eina_Bool del = EINA_TRUE;

   /* returning false in key_cb means delete the object */
   if (e_comp->autoclose.key_cb)
     del = !e_comp->autoclose.key_cb(e_comp->autoclose.data, ev);
   if (del) _e_comp_object_autoclose_cleanup(0);
   return ECORE_CALLBACK_DONE;
}

static void
_e_comp_object_autoclose_mouse_up_cb(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_comp_object_autoclose_cleanup(0);
}

static void
_e_comp_object_autoclose_setup(Evas_Object *obj)
{
   if (!e_comp->autoclose.rect)
     {
        /* create rect just below autoclose object to catch mouse events */
        e_comp->autoclose.rect = evas_object_rectangle_add(e_comp->evas);
        evas_object_move(e_comp->autoclose.rect, 0, 0);
        evas_object_resize(e_comp->autoclose.rect, e_comp->w, e_comp->h);
        evas_object_show(e_comp->autoclose.rect);
        evas_object_name_set(e_comp->autoclose.rect, "e_comp->autoclose.rect");
        evas_object_color_set(e_comp->autoclose.rect, 0, 0, 0, 0);
        evas_object_event_callback_add(e_comp->autoclose.rect, EVAS_CALLBACK_MOUSE_UP, _e_comp_object_autoclose_mouse_up_cb, e_comp);
        e_comp_grab_input(0, 1);
     }
   evas_object_layer_set(e_comp->autoclose.rect, evas_object_layer_get(obj) - 1);
   evas_object_focus_set(obj, 1);
   e_comp_shape_queue();
   if (!e_comp->autoclose.key_handler)
     e_comp->autoclose.key_handler = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, _e_comp_object_autoclose_key_down_cb, e_comp);
}

static void
_e_comp_object_autoclose_show(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   _e_comp_object_autoclose_setup(obj);
   evas_object_event_callback_del(obj, EVAS_CALLBACK_SHOW, _e_comp_object_autoclose_show);
}

static void
_e_comp_object_autoclose_del(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   evas_object_event_callback_del(obj, EVAS_CALLBACK_SHOW, _e_comp_object_autoclose_show);
   _e_comp_object_autoclose_cleanup(1);
   if (e_client_focused_get()) return;
   if (e_config->focus_policy != E_FOCUS_MOUSE)
     e_client_refocus();
}


E_API Eina_Bool
e_comp_object_util_autoclose_on_escape(void *d EINA_UNUSED, Ecore_Event_Key *ev)
{
   return !!strcmp(ev->key, "Escape");
}

E_API void
e_comp_object_util_autoclose(Evas_Object *obj, E_Comp_Object_Autoclose_Cb del_cb, E_Comp_Object_Key_Cb cb, const void *data)
{
   if (e_comp->autoclose.obj)
     {
        if (e_comp->autoclose.obj == obj) return;
        evas_object_event_callback_del_full(e_comp->autoclose.obj, EVAS_CALLBACK_DEL, _e_comp_object_autoclose_del, e_comp);
        _e_comp_object_autoclose_cleanup(0);
     }
   if (!obj) return;
   e_comp->autoclose.obj = obj;
   e_comp->autoclose.del_cb = del_cb;
   e_comp->autoclose.key_cb = cb;
   e_comp->autoclose.data = (void*)data;
   if (evas_object_visible_get(obj))
     _e_comp_object_autoclose_setup(obj);
   else
     evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _e_comp_object_autoclose_show, e_comp);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _e_comp_object_autoclose_del, e_comp);
}

E_API Evas_Object *
e_comp_object_frame_icon_get(Evas_Object *obj)
{
   API_ENTRY NULL;
   return cw->frame_icon;
}

E_API Evas_Object *
e_comp_object_frame_volume_get(Evas_Object *obj)
{
   API_ENTRY NULL;
   return cw->frame_volume;
}
#define E_COMP_WL
#include "e.h"

/* handle include for printing uint64_t */
#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#define COMPOSITOR_VERSION 4

E_API int E_EVENT_WAYLAND_GLOBAL_ADD = -1;

#ifndef EGL_HEIGHT
# define EGL_HEIGHT			0x3056
#endif
#ifndef EGL_WIDTH
# define EGL_WIDTH			0x3057
#endif

/* Resource Data Mapping: (wl_resource_get_user_data)
 *
 * wl_surface == e_pixmap
 * wl_region == eina_tiler
 * wl_subsurface == e_client
 *
 */

static void _e_comp_wl_subsurface_parent_commit(E_Client *ec, Eina_Bool parent_synchronized);

EINTERN Eina_Bool e_comp_wl_extensions_init(void);

/* local variables */
/* static Eina_Hash *clients_win_hash = NULL; */
static Eina_List *handlers = NULL;
static double _last_event_time = 0.0;

static int64_t surface_id = 0;

static Eina_List *grab_clients;
static Eina_List *grab_cbs;

/* local functions */

static Eina_Bool
_parent_client_contains_pointer(E_Client *ec)
{
   Eina_List *l;
   E_Client *c, *top = ec;

   while (top->parent) top = top->parent;

   if (top == ec) return EINA_FALSE;

   if (top->mouse.in) return EINA_TRUE;

   EINA_LIST_FOREACH(top->comp_data->sub.list, l, c)
     if (c->mouse.in) return EINA_TRUE;

   EINA_LIST_FOREACH(top->transients, l, c)
     if ((ec != c) && c->mouse.in) return EINA_TRUE;

   return EINA_FALSE;
}

static struct wl_resource *
_output_resource_find(Eina_List *reslist, struct wl_resource *surface)
{
   Eina_List *l;
   struct wl_client *client;
   struct wl_resource *res;

   client = wl_resource_get_client(surface);
   EINA_LIST_FOREACH(reslist, l, res)
     if (wl_resource_get_client(res) == client) return res;

   return NULL;
}

static void
_e_comp_wl_surface_outputs_update(E_Client *ec)
{
   Eina_List *l;
   E_Zone *zone;
   int32_t obits = 0;

   if (ec->visible)
     EINA_LIST_FOREACH(e_comp->zones, l, zone)
       if (E_INTERSECTS(zone->x, zone->y, zone->w, zone->h,
                        ec->x, ec->y, ec->w, ec->h)) obits |= 1 << zone->id;

   if (obits != ec->comp_data->on_outputs)
     {
        int32_t leave = (obits ^ ec->comp_data->on_outputs) & ec->comp_data->on_outputs;
        int32_t enter = (obits ^ ec->comp_data->on_outputs) & obits;

        ec->comp_data->on_outputs = obits;
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          {
             struct wl_resource *s, *res;
             E_Comp_Wl_Output *wlo;

             s = ec->comp_data->surface;
             wlo = zone->output;
             if (!wlo) continue;

             res = _output_resource_find(wlo->resources, s);
             if (!res) continue;

             if (leave & (1 << zone->id)) wl_surface_send_leave(s, res);
             if (enter & (1 << zone->id)) wl_surface_send_enter(s, res);
          }
     }
}

static void
_e_comp_wl_configure_send(E_Client *ec, Eina_Bool edges)
{
   int w = 0, h = 0;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->changes.size || e_pixmap_usable_get(ec->pixmap))
     {
        if (e_comp_object_frame_exists(ec->frame))
          w = ec->client.w, h = ec->client.h;
        else
          w = ec->w, h = ec->h;
     }
   ec->comp_data->shell.configure_send(ec->comp_data->shell.surface,
                                       edges * e_comp_wl->resize.edges,
                                       w, h);
}

/* static void */
/* _e_comp_wl_log_cb_print(const char *format, va_list args) */
/* { */
/*    EINA_LOG_DOM_INFO(e_log_dom, format, args); */
/* } */

static void
_e_comp_wl_modules_load(void)
{
   const char **m, *mods[] =
   {
#ifdef USE_MODULE_WL_DESKTOP_SHELL
      "wl_desktop_shell",
#endif
#ifdef USE_MODULE_XWAYLAND
      "xwayland",
#endif
      NULL
   };

   for (m = mods; *m; m++)
     {
        E_Module *mod = e_module_find(*m);

        if (!mod)
          mod = e_module_new(*m);

        if (mod)
          e_module_enable(mod);
     }
}

static void
_e_comp_wl_evas_cb_show(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec = data;

   if (e_object_is_del(E_OBJECT(ec))) return;

   if (!ec->override) e_hints_window_visible_set(ec);
}

static void
_e_comp_wl_evas_cb_hide(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec = data;

   if (!e_object_is_del(E_OBJECT(ec))) return;
   if (!e_object_ref_get(E_OBJECT(ec))) return;

   e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
   e_comp_object_dirty(ec->frame);
   if (e_comp_object_render(ec->frame))
     e_comp_client_post_update_add(ec);
   e_object_unref(E_OBJECT(ec));
}

static void
_e_comp_wl_mouse_in(E_Client *ec, Evas_Event_Mouse_In *ev)
{
   struct wl_resource *res;
   struct wl_client *wc;
   Eina_List *l;
   uint32_t serial;

   if (e_object_is_del(E_OBJECT(ec))) return;

   if (!ec->comp_data->surface) return;

   e_comp_wl->ptr.ec = ec;
   if (e_comp_wl->drag)
     {
        e_comp_wl_data_device_send_enter(ec);
        return;
     }
   if (!eina_list_count(e_comp_wl->ptr.resources)) return;
   wc = wl_resource_get_client(ec->comp_data->surface);
   serial = wl_display_next_serial(e_comp_wl->wl.disp);
   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(res);

        if (!e_comp_wl_input_pointer_check(res)) continue;
        if (wl_resource_get_client(res) != wc) continue;
        ptr->entered = 1;
        wl_pointer_send_enter(res, serial, ec->comp_data->surface,
                              wl_fixed_from_int(ev->canvas.x - ec->client.x),
                              wl_fixed_from_int(ev->canvas.y - ec->client.y));
        e_comp_wl_input_pointer_cursor_update(ptr);
     }
}

static void
_e_comp_wl_evas_cb_mouse_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   if (e_comp_object_frame_exists(obj)) return;
   _e_comp_wl_mouse_in(data, event_info);
}

static void
_e_comp_wl_cb_ssd_mouse_in(void *data, Evas_Object *obj, void *event_info)
{
   E_Client *ec = data;

   if (!e_comp_object_frame_exists(obj)) return;
   if (!e_object_is_del(data))
     ec->comp_data->ssd_mouse_in = 1;
   _e_comp_wl_mouse_in(data, event_info);
}

static void
_e_comp_wl_mouse_out(E_Client *ec)
{
   struct wl_resource *res;
   struct wl_client *wc;
   Eina_List *l;
   uint32_t serial;

   if ((ec == e_client_action_get()) && e_grabinput_mouse_win_get()) return;
   /* FIXME? this is a hack to just reset the cursor whenever we mouse out. not sure if accurate */
   {
      e_pointer_object_set(e_comp->pointer, NULL, 0, 0);
      evas_object_show(e_comp->pointer->o_ptr);
   }
   if (e_comp_wl->ptr.ec == ec)
     e_comp_wl->ptr.ec = NULL;
   if (!ec->comp_data) return;

   if (!ec->comp_data->surface) return;
   if (e_comp_wl->drag)
     {
        e_comp_wl_data_device_send_leave(ec);
        return;
     }
   if (!eina_list_count(e_comp_wl->ptr.resources)) return;

   wc = wl_resource_get_client(ec->comp_data->surface);
   serial = wl_display_next_serial(e_comp_wl->wl.disp);
   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(res);
        if (!e_comp_wl_input_pointer_check(res)) continue;
        if (wl_resource_get_client(res) != wc) continue;
        ptr->entered = 0;
        wl_pointer_send_leave(res, serial, ec->comp_data->surface);
     }
}

static void
_e_comp_wl_evas_cb_mouse_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (e_comp_object_frame_exists(obj)) return;
   _e_comp_wl_mouse_out(data);
}

static void
_e_comp_wl_cb_ssd_mouse_out(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!e_comp_object_frame_exists(obj)) return;
   if (!e_object_is_del(data))
     ec->comp_data->ssd_mouse_in = 0;
   _e_comp_wl_mouse_out(data);
}

static void
_e_comp_wl_send_mouse_move(E_Client *ec, int x, int y, unsigned int timestamp)
{
   struct wl_resource *res;
   struct wl_client *wc;
   Eina_List *l;
   E_Client *tec, *tec_in = NULL;
   Eina_Bool found = EINA_FALSE;
   Evas_Event_Mouse_In ev;

   /* begin hacks:
    *
    * this should be handled by setting NOGRAB pointer mode on comp object internals,
    * ie. 03a4ecbdb0972f8267c07ea14d752fcee69bd9fa
    * but doing so breaks dnd eventing.
    *
    * instead, walk the transients list, assuming (correctly) that they are listed from
    * bottom to top, and send mouse events to whichever surface the pointer is in
    */
   ev.canvas.x = x;
   ev.canvas.y = y;
   EINA_LIST_FOREACH(ec->transients, l, tec)
     {
        if (e_client_util_ignored_get(tec) || (!evas_object_visible_get(tec->frame)))
          continue;
        if (tec->mouse.in) tec_in = tec;
        if (E_INSIDE(x, y, tec->x, tec->y, tec->w, tec->h))
          {
             if (tec_in && (tec != tec_in))
               {
                  e_client_mouse_out(tec_in, x, y);
                  _e_comp_wl_mouse_out(tec_in);
                  tec_in = NULL;
               }
             e_client_mouse_in(tec, x, y);
             _e_comp_wl_mouse_in(tec, &ev);
             e_client_mouse_move(tec, &(Evas_Point){x, y});
             _e_comp_wl_send_mouse_move(tec, x, y, timestamp);
             found = EINA_TRUE;
          }
        else if (tec->mouse.in)
          {
             e_client_mouse_out(tec, x, y);
             _e_comp_wl_mouse_out(tec);
          }
     }
   if (found) return;
   /* if a transient previously had mouse.in, re-set mouse.in to the parent */
   if (tec_in)
     {
        e_client_mouse_in(ec, x, y);
        _e_comp_wl_mouse_in(ec, &ev);
        e_client_mouse_move(ec, &(Evas_Point){x, y});
     }
   /* end hacks */
   wc = wl_resource_get_client(ec->comp_data->surface);
   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        if (!e_comp_wl_input_pointer_check(res)) continue;
        if (wl_resource_get_client(res) != wc) continue;
        wl_pointer_send_motion(res, timestamp,
                               wl_fixed_from_int(x - ec->client.x),
                               wl_fixed_from_int(y - ec->client.y));
     }
}

static Eina_Bool
_e_comp_wl_evas_cb_mouse_internal_check(E_Client *ec, int x, int y)
{
   if (!e_comp_object_frame_exists(ec->frame)) return EINA_TRUE;
   if (E_INSIDE(x, y, ec->client.x, ec->client.y, ec->client.w, ec->client.h)) return EINA_TRUE;
   return EINA_FALSE;
}

static void
_e_comp_wl_evas_cb_mouse_move(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   E_Client *ec = data;
   Evas_Event_Mouse_Move *ev = event;

   if (ec == e_client_action_get()) return;
   if (!ec->mouse.in) return;
   if (!_e_comp_wl_evas_cb_mouse_internal_check(ec, ev->cur.canvas.x, ev->cur.canvas.y))
     {
        e_client_mouse_move(ec, &(Evas_Point){ev->cur.canvas.x, ev->cur.canvas.y});
        return;
     }
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->ignored) return;
   if (!ec->comp_data->surface) return;

   if ((!e_comp_wl->drag_client) ||
       (!e_client_has_xwindow(e_comp_wl->drag_client)) ||
       e_comp_wl->drag_client->override)
     _e_comp_wl_send_mouse_move(ec, ev->cur.canvas.x, ev->cur.canvas.y, ev->timestamp);
}

static void
_e_comp_wl_evas_cb_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   E_Client *ec = data;
   Evas_Event_Mouse_Down *ev = event;

   if (!_e_comp_wl_evas_cb_mouse_internal_check(ec, ev->output.x, ev->output.y)) return;
   e_comp_wl_evas_handle_mouse_button(ec, ev->timestamp, ev->button,
                                      WL_POINTER_BUTTON_STATE_PRESSED);
}

static void
_e_comp_wl_evas_cb_mouse_up(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   E_Client *ec = data;
   Evas_Event_Mouse_Up *ev = event;

   if (!_e_comp_wl_evas_cb_mouse_internal_check(ec, ev->output.x, ev->output.y)) return;
   e_comp_wl_evas_handle_mouse_button(ec, ev->timestamp, ev->button,
                                      WL_POINTER_BUTTON_STATE_RELEASED);
}

static void
_e_comp_wl_evas_cb_mouse_wheel(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   E_Client *ec;
   Evas_Event_Mouse_Wheel *ev;
   struct wl_resource *res;
   struct wl_client *wc;
   Eina_List *l;
   uint32_t axis, dir;

   ev = event;
   if (!(ec = data)) return;
   if (ec == e_client_action_get()) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->ignored) return;
   if (!ec->mouse.in) return;

   if (ev->direction == 0)
     axis = WL_POINTER_AXIS_VERTICAL_SCROLL;
   else
     axis = WL_POINTER_AXIS_HORIZONTAL_SCROLL;

   if (ev->z < 0)
     dir = -wl_fixed_from_int(abs(10 * ev->z));
   else
     dir = wl_fixed_from_int(10 * ev->z);

   if (!ec->comp_data->surface) return;

   wc = wl_resource_get_client(ec->comp_data->surface);
   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        if (!e_comp_wl_input_pointer_check(res)) continue;
        if (wl_resource_get_client(res) != wc) continue;
        wl_pointer_send_axis(res, ev->timestamp, axis, dir);
     }
}

static void
_e_comp_wl_evas_cb_multi_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Eina_List *l;
   struct wl_client *wc;
   uint32_t serial;
   struct wl_resource *res;
   E_Client *ec = data;
   Evas_Event_Multi_Down *ev = event;
   wl_fixed_t x, y;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (!ec->comp_data->surface) return;
   if (!ec->mouse.in) return;

   wc = wl_resource_get_client(ec->comp_data->surface);
   serial = wl_display_next_serial(e_comp_wl->wl.disp);

   x = wl_fixed_from_int(ev->canvas.x - ec->client.x);
   y = wl_fixed_from_int(ev->canvas.y - ec->client.y);

   EINA_LIST_FOREACH(e_comp_wl->touch.resources, l, res)
     {
        if (wl_resource_get_client(res) != wc) continue;
        if (!e_comp_wl_input_touch_check(res)) continue;
        wl_touch_send_down(res, serial, ev->timestamp,
                           ec->comp_data->surface, ev->device, x, y);
     }
}

static void
_e_comp_wl_evas_cb_multi_up(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Eina_List *l;
   struct wl_client *wc;
   uint32_t serial;
   struct wl_resource *res;
   E_Client *ec = data;
   Evas_Event_Multi_Up *ev = event;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (!ec->comp_data->surface) return;
   if (!ec->mouse.in) return;

   wc = wl_resource_get_client(ec->comp_data->surface);
   serial = wl_display_next_serial(e_comp_wl->wl.disp);

   EINA_LIST_FOREACH(e_comp_wl->touch.resources, l, res)
     {
        if (wl_resource_get_client(res) != wc) continue;
        if (!e_comp_wl_input_touch_check(res)) continue;
        wl_touch_send_up(res, serial, ev->timestamp, ev->device);
     }
}

static void
_e_comp_wl_evas_cb_multi_move(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Eina_List *l;
   struct wl_client *wc;
   struct wl_resource *res;
   E_Client *ec = data;
   Evas_Event_Multi_Move *ev = event;
   wl_fixed_t x, y;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (!ec->comp_data->surface) return;
   if (!ec->mouse.in) return;

   wc = wl_resource_get_client(ec->comp_data->surface);

   x = wl_fixed_from_int(ev->cur.canvas.x - ec->client.x);
   y = wl_fixed_from_int(ev->cur.canvas.y - ec->client.y);

   EINA_LIST_FOREACH(e_comp_wl->touch.resources, l, res)
     {
        if (wl_resource_get_client(res) != wc) continue;
        if (!e_comp_wl_input_touch_check(res)) continue;
        wl_touch_send_motion(res, ev->timestamp, ev->device, x, y);
     }
}

static void
_e_comp_wl_client_priority_raise(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   e_pid_nice_priority_fg(ec->netwm.pid);
}

static void
_e_comp_wl_client_priority_normal(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   e_pid_nice_priority_bg(ec->netwm.pid);
}

static Eina_Bool
_e_comp_wl_evas_cb_focus_in_timer(E_Client *ec)
{
   if (e_object_is_del(E_OBJECT(ec))) return EINA_FALSE;

   ec->comp_data->on_focus_timer = NULL;

   if (!e_comp_wl->kbd.focused) return EINA_FALSE;
   e_comp_wl_input_keyboard_modifiers_update();
   return EINA_FALSE;
}

static Eina_Bool
_keyboard_resource_add(struct wl_resource *newres)
{
  Eina_List *l;
  struct wl_resource *res;

  EINA_LIST_FOREACH(e_comp_wl->kbd.focused, l, res)
    if (res == newres) return EINA_FALSE;

  e_comp_wl->kbd.focused = eina_list_append(e_comp_wl->kbd.focused, newres);
  return EINA_TRUE;
}

static void
_e_comp_wl_evas_cb_focus_in(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec, *focused;
   struct wl_resource *res;
   struct wl_client *wc;
   Eina_List *l;
   int added = 0;

   if (!(ec = data)) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->iconic) return;

   /* block spurious focus events */
   focused = e_client_focused_get();
   if ((focused) && (ec != focused)) return;

   /* raise client priority */
   _e_comp_wl_client_priority_raise(ec);

   wc = wl_resource_get_client(ec->comp_data->surface);
   if (ec->comp_data->is_xdg_surface)
     {
        /* We only send kbd focus to xdg top levels */
        while (ec->parent)
          {
             ec = ec->parent;

             /* If an xdg shell popup's parent already has focus we don't
              * need to do anything more.
              */
             if (ec->focused) return;
          }
     }

   EINA_LIST_FOREACH(e_comp_wl->kbd.resources, l, res)
     if (wl_resource_get_client(res) == wc)
       added |= _keyboard_resource_add(res);
   if (!e_comp_wl->kbd.focused) return;
   if (!added) return;
   e_comp_wl_input_keyboard_enter_send(ec);
   if (e_comp_util_kbd_grabbed()) return;
   e_comp_wl_data_device_keyboard_focus_set();
   ec->comp_data->on_focus_timer =
     ecore_timer_loop_add(0.8, (Ecore_Task_Cb)_e_comp_wl_evas_cb_focus_in_timer, ec);
}

static void
_e_comp_wl_keyboard_leave(E_Client *ec)
{
   struct wl_resource *res;
   Eina_List *l, *ll;
   uint32_t serial;

   if (!eina_list_count(e_comp_wl->kbd.resources)) return;
   if (!ec->comp_data) return;

   if (ec->comp_data->is_xdg_surface)
     {
        /* If we left an xdg popup to enter some other (sub)surface
         * of the same top level, we don't need to do anything.
         * We also test e_client_focused_get() because it will be NULL
         * on moves which trip up _parent_client_contains_pointer() */
        if (e_client_focused_get() &&
            _parent_client_contains_pointer(ec)) return;

        /* We only kbd focus top level xdg */
        while (ec->parent) ec = ec->parent;
     }

   serial = wl_display_next_serial(e_comp_wl->wl.disp);

   EINA_LIST_FOREACH_SAFE(e_comp_wl->kbd.focused, l, ll, res)
     {
        if (ec->comp_data->surface)
          wl_keyboard_send_leave(res, serial, ec->comp_data->surface);
        e_comp_wl->kbd.focused = eina_list_remove_list(e_comp_wl->kbd.focused, l);
     }
   if ((!e_comp_wl->wl.client_ec) || e_object_is_del(E_OBJECT(e_comp_wl->wl.client_ec))) return;
   if (e_client_focused_get()) return;
   e_comp_wl->kbd.focus = e_comp_wl->wl.client_ec->comp_data->surface;
   e_comp_wl_data_device_keyboard_focus_set();
}

static void
_e_comp_wl_evas_cb_focus_out(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec = data;

   /* lower client priority */
   if (!e_object_is_del(data))
     {
        _e_comp_wl_client_priority_normal(ec);
        E_FREE_FUNC(ec->comp_data->on_focus_timer, ecore_timer_del);
     }
   _e_comp_wl_keyboard_leave(ec);
}

static void
_e_comp_wl_evas_cb_restack(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *sec, *ec = data;
   Eina_List *l, *ll;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (e_client_has_xwindow(ec)) return;

   /* only restack to enforce shell spec if config would otherwise not force restack */
   if (!e_config->transient.raise)
     e_client_transients_restack(ec);

   if (!ec->comp_data->sub.list) return;
   EINA_LIST_FOREACH(ec->comp_data->sub.list, l, sec)
     evas_object_layer_set(sec->frame, evas_object_layer_get(ec->frame));
   sec = eina_list_last_data_get(ec->comp_data->sub.list);
   evas_object_stack_above(sec->frame, ec->frame);
   EINA_LIST_REVERSE_FOREACH_SAFE(ec->comp_data->sub.list, l, ll, sec)
     {
        E_Client *nsec = eina_list_data_get(ll);

        if (nsec)
          evas_object_stack_below(nsec->frame, sec->frame);
     }
}

static void
_e_comp_wl_evas_cb_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *sec, *ec = data;
   Eina_List *l;

   if (e_object_is_del(E_OBJECT(ec))) return;
   ec->comp_data->moved = 1;
   EINA_LIST_FOREACH(ec->comp_data->sub.list, l, sec)
     {
        if (!sec->comp_data->sub.data->position.set)
          evas_object_move(sec->frame, ec->client.x + sec->comp_data->sub.data->position.x,
                           ec->client.y + sec->comp_data->sub.data->position.y);
     }
   _e_comp_wl_surface_outputs_update(ec);
}

static void
_e_comp_wl_evas_cb_unmaximize_pre(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Client *ec = data;
   E_Maximize *max = event_info;

   if (ec->need_fullscreen || e_object_is_del(E_OBJECT(ec))) return;
   if (ec->comp_data->in_commit)
     ec->comp_data->maximizing = 1;
   else if (!e_client_has_xwindow(ec))
     {
        int w, h, ew = 0, eh = 0, *ecw, *ech;
        unsigned int pmax = ec->maximized;
        ec->comp_data->unmax = *max;
        if (ec->internal)
          ecw = &ec->client.w, ech = &ec->client.h;
        else
          ecw = &ec->w, ech = &ec->h;
        if ((!e_config->window_maximize_animate) || ec->maximize_anims_disabled)
          {
             e_client_unmaximize_geometry_get(ec, *max, NULL, NULL, &w, &h);
             if (ec->internal)
               e_comp_object_frame_wh_unadjust(ec->frame, w, h, &w, &h);
             ew = *ecw, eh = *ech;
             *ecw = w, *ech = h;
          }
        ec->maximized = 0;
        _e_comp_wl_configure_send(ec, 0);
        if ((!e_config->window_maximize_animate) || ec->maximize_anims_disabled)
          *ecw = ew, *ech = eh;
        ec->maximized = pmax;
        *max = 0;
     }
}

static void
_e_comp_wl_evas_cb_maximize_pre(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Client *ec = data;
   E_Maximize *max = event_info;

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (!ec->comp_data->buffer_commit)
     ec->maximize_anims_disabled = 1;
   if (ec->comp_data->in_commit)
     ec->comp_data->maximizing = 1;
   else if (!e_client_has_xwindow(ec))
     {
        int w, h, ew = 0, eh = 0, *ecw, *ech;
        unsigned int pmax = ec->maximized;
        ec->comp_data->max = *max;
        if (ec->internal)
          ecw = &ec->client.w, ech = &ec->client.h;
        else
          ecw = &ec->w, ech = &ec->h;
        if ((!e_config->window_maximize_animate) ||
            ec->maximize_anims_disabled || (!ec->comp_data->buffer_commit))
          {
             e_client_maximize_geometry_get(ec, *max, NULL, NULL, &w, &h);
             if (ec->internal)
               e_comp_object_frame_wh_unadjust(ec->frame, w, h, &w, &h);
             ew = *ecw, eh = *ech;
             *ecw = w, *ech = h;
          }
        ec->maximized = *max;
        _e_comp_wl_configure_send(ec, 0);
        if ((!e_config->window_maximize_animate) || ec->maximize_anims_disabled)
          *ecw = ew, *ech = eh;
        ec->maximized = pmax;
        *max = 0;
     }
}

static void
_e_comp_wl_evas_cb_resize(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   if ((ec->shading) || (ec->shaded)) return;
   if (!ec->comp_data->shell.configure_send) return;
   if (ec->comp_data->maximizing) return;
   if (e_client_util_resizing_get(ec) && e_comp_wl->resize.edges)
     {
        int x, y;

        x = ec->mouse.last_down[ec->moveinfo.down.button - 1].w;
        y = ec->mouse.last_down[ec->moveinfo.down.button - 1].h;
        if (e_comp_object_frame_exists(ec->frame))
          e_comp_object_frame_wh_unadjust(ec->frame, x, y, &x, &y);

        switch (ec->resize_mode)
          {
           case E_POINTER_RESIZE_TL:
           case E_POINTER_RESIZE_L:
           case E_POINTER_RESIZE_BL:
             x += ec->mouse.last_down[ec->moveinfo.down.button - 1].mx -
               ec->mouse.current.mx;
             break;
           case E_POINTER_RESIZE_TR:
           case E_POINTER_RESIZE_R:
           case E_POINTER_RESIZE_BR:
             x += ec->mouse.current.mx - ec->mouse.last_down[ec->moveinfo.down.button - 1].mx;
             break;
           default:
             break;;
          }
        switch (ec->resize_mode)
          {
           case E_POINTER_RESIZE_TL:
           case E_POINTER_RESIZE_T:
           case E_POINTER_RESIZE_TR:
             y += ec->mouse.last_down[ec->moveinfo.down.button - 1].my -
               ec->mouse.current.my;
             break;
           case E_POINTER_RESIZE_BL:
           case E_POINTER_RESIZE_B:
           case E_POINTER_RESIZE_BR:
             y += ec->mouse.current.my - ec->mouse.last_down[ec->moveinfo.down.button - 1].my;
             break;
           default:
             break;
          }
        x = E_CLAMP(x, 1, x);
        y = E_CLAMP(y, 1, y);
        ec->comp_data->shell.configure_send(ec->comp_data->shell.surface,
                                            e_comp_wl->resize.edges,
                                            x, y);
     }
   else
     _e_comp_wl_configure_send(ec, 1);
}

static void
_e_comp_wl_evas_cb_state_update(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec = data;

   if (e_object_is_del(E_OBJECT(ec))) return;

   /* check for wayland pixmap */

   if (ec->comp_data->shell.configure_send)
     _e_comp_wl_configure_send(ec, 0);
}

static void
_e_comp_wl_evas_cb_maximize_done(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (e_object_is_del(E_OBJECT(ec))) return;
   ec->comp_data->maximizing = 0;
   ec->maximize_anims_disabled = ec->comp_data->maximize_anims_disabled;
}

static void
_e_comp_wl_evas_cb_delete_request(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec = data;

   e_comp_ignore_win_del(E_PIXMAP_TYPE_WL, e_pixmap_window_get(ec->pixmap));

   if (!e_client_has_xwindow(ec))
     {
        if (ec->internal_elm_win)
          evas_object_del(ec->internal_elm_win);
        else
          e_object_del(E_OBJECT(ec));
     }
}

static void
_e_comp_wl_evas_cb_kill_request(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;

   e_comp_ignore_win_del(E_PIXMAP_TYPE_WL, e_pixmap_window_get(ec->pixmap));

   evas_object_pass_events_set(ec->frame, EINA_TRUE);
   if (ec->visible) evas_object_hide(ec->frame);
   if (ec->internal)
     {
        ec->ignored = 1;
        if (!e_object_is_del(E_OBJECT(ec)))
          ec->comp_data->mapped = EINA_FALSE;
     }
   else e_object_del(E_OBJECT(ec));
}

static void
_e_comp_wl_evas_cb_ping(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (!(ec->comp_data->shell.ping)) return;
   if (!(ec->comp_data->shell.surface)) return;

   ec->comp_data->shell.ping(ec->comp_data->shell.surface);
}

static void
_e_comp_wl_evas_cb_color_set(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   E_Client *ec;
   int a = 0;

   if (!(ec = data)) return;
   evas_object_color_get(obj, NULL, NULL, NULL, &a);
   if (ec->netwm.opacity == a) return;
   ec->netwm.opacity = a;
   ec->netwm.opacity_changed = EINA_TRUE;
}

static void
_e_comp_wl_buffer_cb_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Comp_Wl_Buffer *buffer;

   buffer = container_of(listener, E_Comp_Wl_Buffer, destroy_listener);
   wl_signal_emit(&buffer->destroy_signal, buffer);
   buffer->destroyed = EINA_TRUE;

   if (!buffer->busy)
     free(buffer);
}

static void
_e_comp_wl_client_evas_init(E_Client *ec)
{
   if (ec->comp_data->evas_init) return;
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_SHOW,
                                  EVAS_CALLBACK_PRIORITY_AFTER,
                                  _e_comp_wl_evas_cb_show, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_HIDE,
                                  EVAS_CALLBACK_PRIORITY_AFTER,
                                  _e_comp_wl_evas_cb_hide, ec);

   /* setup input callbacks */
   evas_object_smart_callback_add(ec->frame, "mouse_in",
     (Evas_Smart_Cb)_e_comp_wl_cb_ssd_mouse_in, ec);
   evas_object_smart_callback_add(ec->frame, "mouse_out",
     (Evas_Smart_Cb)_e_comp_wl_cb_ssd_mouse_out, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_IN,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           (Evas_Object_Event_Cb)_e_comp_wl_evas_cb_mouse_in, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_OUT,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           (Evas_Object_Event_Cb)_e_comp_wl_evas_cb_mouse_out, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_MOVE,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_mouse_move, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_DOWN,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_mouse_down, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_UP,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_mouse_up, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOUSE_WHEEL,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_mouse_wheel, ec);

   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MULTI_DOWN,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_multi_down, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MULTI_UP,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_multi_up, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MULTI_MOVE,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_multi_move, ec);


   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_FOCUS_IN,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_focus_in, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_FOCUS_OUT,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_focus_out, ec);

   if (!ec->override)
     {
        evas_object_smart_callback_add(ec->frame, "client_resize",
                                       _e_comp_wl_evas_cb_resize, ec);
        evas_object_smart_callback_add(ec->frame, "maximize_pre",
                                       _e_comp_wl_evas_cb_maximize_pre, ec);
        evas_object_smart_callback_add(ec->frame, "unmaximize_pre",
                                       _e_comp_wl_evas_cb_unmaximize_pre, ec);
        evas_object_smart_callback_add(ec->frame, "maximize_done",
                                       _e_comp_wl_evas_cb_maximize_done, ec);
        evas_object_smart_callback_add(ec->frame, "unmaximize_done",
                                       _e_comp_wl_evas_cb_maximize_done, ec);
        evas_object_smart_callback_add(ec->frame, "fullscreen",
                                       _e_comp_wl_evas_cb_state_update, ec);
     }
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_MOVE,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_move, ec);
   evas_object_event_callback_priority_add(ec->frame, EVAS_CALLBACK_RESTACK,
                                           EVAS_CALLBACK_PRIORITY_AFTER,
                                           _e_comp_wl_evas_cb_restack, ec);

   if (ec->internal_elm_win)
     {
        /* setup delete/kill callbacks */
        evas_object_smart_callback_add(ec->frame, "delete_request",
                                       _e_comp_wl_evas_cb_delete_request, ec);
        evas_object_smart_callback_add(ec->frame, "kill_request",
                                       _e_comp_wl_evas_cb_kill_request, ec);
     }

   /* setup ping callback */
   evas_object_smart_callback_add(ec->frame, "ping",
                                  _e_comp_wl_evas_cb_ping, ec);

   evas_object_smart_callback_add(ec->frame, "color_set",
                                  _e_comp_wl_evas_cb_color_set, ec);

   ec->comp_data->evas_init = EINA_TRUE;
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_ON, _e_comp_wl_screensaver_on, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_OFF, _e_comp_wl_screensaver_off, NULL);
   e_comp_wl_notidle();

   e_screensaver_attrs_set(e_screensaver_timeout_get(EINA_TRUE),
                           e_config->screensaver_blanking,
                           e_config->screensaver_expose);
// XXX: maybe later like x work on explicit suspend of compositor stuff?
//   e_desklock_show_hook_add(_e_comp_x_desklock_show);
//   e_desklock_hide_hook_add(_e_comp_x_desklock_hide);
}

static inline int
_rotation_get(int rotation)
{
   int transform;
   switch (rotation)
     {
      case 90:
        transform = WL_OUTPUT_TRANSFORM_90;
        break;
      case 180:
        transform = WL_OUTPUT_TRANSFORM_180;
        break;
      case 270:
        transform = WL_OUTPUT_TRANSFORM_270;
        break;
      case 0:
      default:
        transform = WL_OUTPUT_TRANSFORM_NORMAL;
        break;
     }
   return transform;
}

static Eina_Bool
_e_comp_wl_cb_randr_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   const Eina_List *l;
   E_Randr2_Screen *screen;

   if (e_xinerama_fake_screens_exist())
     {
        E_Screen *scr;
        EINA_LIST_FOREACH(e_xinerama_screens_get(), l, scr)
          {
             e_comp_wl_output_init(NULL, NULL, NULL,
               scr->x, scr->y, scr->w, scr->h,
               0, 0, 0, 0, _rotation_get(scr->rotation), scr->escreen);
          }
        return ECORE_CALLBACK_RENEW;
     }
   if (!e_randr2) return ECORE_CALLBACK_RENEW;
   EINA_LIST_FOREACH(e_randr2->screens, l, screen)
     {
        if (!screen->config.enabled)
          {
             e_comp_wl_output_remove(screen->id);
             continue;
          }

        if (!e_comp_wl_output_init(screen->id, screen->info.name,
                                   screen->info.screen,
                                   screen->config.geom.x, screen->config.geom.y,
                                   screen->config.geom.w, screen->config.geom.h,
                                   screen->info.size.w, screen->info.size.h,
                                   screen->config.mode.refresh, screen->info.subpixel,
                                   _rotation_get(screen->config.rotation), 0))
          ERR("Could not initialize screen %s", screen->info.name);
     }

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_wl_cb_comp_object_add(void *data EINA_UNUSED, int type EINA_UNUSED, E_Event_Comp_Object *ev)
{
   E_Client *ec;

   /* try to get the client from the object */
   if (!(ec = e_comp_object_client_get(ev->comp_object)))
     return ECORE_CALLBACK_RENEW;

   /* check for client being deleted */
   if (e_object_is_del(E_OBJECT(ec))) return ECORE_CALLBACK_RENEW;

   /* check for wayland pixmap */
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_WL)
     return ECORE_CALLBACK_RENEW;

   /* if we have not setup evas callbacks for this client, do it */
   if (!ec->comp_data->evas_init) _e_comp_wl_client_evas_init(ec);

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_wl_cb_mouse_move(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Move *ev)
{
   _last_event_time = ecore_loop_time_get();

   e_comp_wl->ptr.x = ev->x;
   e_comp_wl->ptr.y = ev->y;
   e_comp_canvas_notidle();
   if (e_comp_wl->selection.target &&
       (!e_client_has_xwindow(e_comp_wl->selection.target)) &&
       e_comp_wl->drag)
     {
        struct wl_resource *res;
        int x, y;

        res = e_comp_wl_data_find_for_client(wl_resource_get_client(e_comp_wl->selection.target->comp_data->surface));
        x = ev->x - e_comp_wl->selection.target->client.x;
        y = ev->y - e_comp_wl->selection.target->client.y;
        wl_data_device_send_motion(res, ev->timestamp, wl_fixed_from_int(x), wl_fixed_from_int(y));
     }
   if (e_comp_wl->drag &&
       e_comp_wl->drag_client &&
       e_client_has_xwindow(e_comp_wl->drag_client) &&
       (!e_comp_wl->drag_client->override))
     _e_comp_wl_send_mouse_move(e_comp_wl->drag_client, ev->x, ev->y, ev->timestamp);
   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_wl_surface_state_size_update(E_Client *ec, E_Comp_Wl_Surface_State *state)
{
   Eina_Rectangle *window;
   /* double scale = 0.0; */

   /* scale = e_comp_wl->output.scale; */
   /* switch (e_comp_wl->output.transform) */
   /*   { */
   /*    case WL_OUTPUT_TRANSFORM_90: */
   /*    case WL_OUTPUT_TRANSFORM_270: */
   /*    case WL_OUTPUT_TRANSFORM_FLIPPED_90: */
   /*    case WL_OUTPUT_TRANSFORM_FLIPPED_270: */
   /*      w = ec->comp_data->buffer_ref.buffer->h / scale; */
   /*      h = ec->comp_data->buffer_ref.buffer->w / scale; */
   /*      break; */
   /*    default: */
   /*      w = ec->comp_data->buffer_ref.buffer->w / scale; */
   /*      h = ec->comp_data->buffer_ref.buffer->h / scale; */
   /*      break; */
   /*   } */

   if (!e_pixmap_size_get(ec->pixmap, &state->bw, &state->bh)) return;
   if (e_client_has_xwindow(ec) || e_comp_object_frame_exists(ec->frame)) return;
   window = &ec->comp_data->shell.window;
   if (window->x || window->y || window->w || window->h)
     e_comp_object_frame_geometry_set(ec->frame, -window->x,
                                      (window->x + window->w) - state->bw,
                                      -window->y,
                                      (window->y + window->h) - state->bh);
   else
     e_comp_object_frame_geometry_set(ec->frame, 0, 0, 0, 0);
}

static void
_e_comp_wl_surface_state_cb_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Comp_Wl_Surface_State *state;

   state =
     container_of(listener, E_Comp_Wl_Surface_State, buffer_destroy_listener);
   state->buffer = NULL;
}

static void
_e_comp_wl_surface_state_init(E_Comp_Wl_Surface_State *state)
{
   state->new_attach = EINA_FALSE;
   state->buffer = NULL;
   state->buffer_destroy_listener.notify =
     _e_comp_wl_surface_state_cb_buffer_destroy;
   state->sx = state->sy = 0;

   state->input = NULL;

   state->opaque = NULL;
}

static void
_e_comp_wl_surface_state_finish(E_Comp_Wl_Surface_State *state)
{
   struct wl_resource *cb;
   Eina_Rectangle *dmg;
   Eina_List *free_list;

   /* The resource destroy callback will walk the state->frames list,
    * so move the list to a temporary first.
    */
   free_list = state->frames;
   state->frames = NULL;
   EINA_LIST_FREE(free_list, cb)
     wl_resource_destroy(cb);

   EINA_LIST_FREE(state->damages, dmg)
     eina_rectangle_free(dmg);

   if (state->opaque) eina_tiler_free(state->opaque);
   state->opaque = NULL;

   if (state->input) eina_tiler_free(state->input);
   state->input = NULL;

   if (state->buffer) wl_list_remove(&state->buffer_destroy_listener.link);
   state->buffer = NULL;
}

static void
_e_comp_wl_surface_state_buffer_set(E_Comp_Wl_Surface_State *state, E_Comp_Wl_Buffer *buffer)
{
   if (state->buffer == buffer) return;
   if (state->buffer)
     wl_list_remove(&state->buffer_destroy_listener.link);
   state->buffer = buffer;
   if (state->buffer)
     wl_signal_add(&state->buffer->destroy_signal,
                   &state->buffer_destroy_listener);
}

static void
_e_comp_wl_surface_state_attach(E_Client *ec, E_Comp_Wl_Surface_State *state)
{
   /* set usable early because shell module checks this */
   e_pixmap_usable_set(ec->pixmap, (state->buffer != NULL));
   e_pixmap_resource_set(ec->pixmap, state->buffer);
   e_pixmap_dirty(ec->pixmap);
   e_pixmap_refresh(ec->pixmap);
}

static void
_e_comp_wl_surface_state_commit(E_Client *ec, E_Comp_Wl_Surface_State *state)
{
   Eina_Bool first = EINA_FALSE;
   Eina_Rectangle *dmg;
   int x = 0, y = 0, w, h;

   first = !e_pixmap_usable_get(ec->pixmap);
#ifndef HAVE_WAYLAND_ONLY
   if (first && e_client_has_xwindow(ec))
     first = !e_pixmap_usable_get(e_comp_x_client_pixmap_get(ec));
#endif

   if (e_client_util_is_popup(ec) && (!ec->parent) && (!e_client_has_xwindow(ec)))
     {
        wl_resource_post_error(ec->comp_data->surface,
                               WL_DISPLAY_ERROR_INVALID_OBJECT,
                               "Popup requires a parent shell surface before commit");
        return;
     }

   ec->comp_data->in_commit = 1;
   if (ec->ignored && ec->comp_data->shell.surface)
     {
        EC_CHANGED(ec);
        ec->new_client = 1;
        e_comp->new_clients++;
        e_client_unignore(ec);
     }

   if (state->new_attach)
     {
        _e_comp_wl_surface_state_attach(ec, state);
        if (first && (!ec->comp_data->cursor) && (!e_client_util_is_popup(ec)))
          {
             if (!e_client_has_xwindow(ec))
               {
                  ec->take_focus = !starting || ec->internal_elm_win;
                  ec->want_focus = ec->override && !ec->parent;
                  if (ec->parent && (!ec->lock_user_location))
                    e_comp_object_util_center_on(ec->frame, ec->parent->frame);
               }
          }
     }

   _e_comp_wl_surface_state_buffer_set(state, NULL);


   if (ec->comp_data->shell.surface)
     {
        if (ec->comp_data->shell.set.min_size.w)
          ec->icccm.min_w = ec->comp_data->shell.set.min_size.w;
        ec->comp_data->shell.set.min_size.w = 0;
        if (ec->comp_data->shell.set.min_size.h)
          ec->icccm.min_h = ec->comp_data->shell.set.min_size.h;
        ec->comp_data->shell.set.min_size.h = 0;
        if (ec->comp_data->shell.set.max_size.w)
          ec->icccm.max_w = ec->comp_data->shell.set.max_size.w;
        ec->comp_data->shell.set.max_size.w = 0;
        if (ec->comp_data->shell.set.max_size.h)
          ec->icccm.max_h = ec->comp_data->shell.set.max_size.h;
        ec->comp_data->shell.set.max_size.h = 0;
        if (ec->icccm.min_w && ec->icccm.max_w && (ec->icccm.min_w > ec->icccm.max_w))
          wl_resource_post_error(ec->comp_data->shell.surface,
                               WL_DISPLAY_ERROR_INVALID_OBJECT,
                               "min surface width cannot be larger than max surface width");
        if (ec->icccm.min_h && ec->icccm.max_h && (ec->icccm.min_h > ec->icccm.max_h))
          wl_resource_post_error(ec->comp_data->shell.surface,
                               WL_DISPLAY_ERROR_INVALID_OBJECT,
                               "min surface height cannot be larger than max surface height");
        if (ec->comp_data->shell.set.fullscreen && (!ec->fullscreen))
          {
             E_Zone *zone = e_comp_zone_id_get(ec->comp_data->shell.set.fs_zone);

             if (zone && (zone != ec->zone))
               e_client_zone_set(ec, zone);
             e_client_fullscreen(ec, E_FULLSCREEN_RESIZE);
          }
        if (ec->comp_data->shell.set.unfullscreen)
           e_client_unfullscreen(ec);
        if (ec->comp_data->shell.set.maximize)
          {
             e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | ec->comp_data->max);
             ec->comp_data->max = 0;
          }
        if (ec->comp_data->shell.set.unmaximize)
          {
             e_client_unmaximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | ec->comp_data->unmax);
             ec->comp_data->unmax = 0;
          }
        if (ec->comp_data->shell.set.minimize)
          e_client_iconify(ec);
        ec->comp_data->shell.set.fullscreen =
        ec->comp_data->shell.set.unfullscreen =
        ec->comp_data->shell.set.maximize =
        ec->comp_data->shell.set.unmaximize =
        ec->comp_data->shell.set.minimize = 0;
     }
   _e_comp_wl_surface_state_size_update(ec, state);

   if (state->new_attach)
     {
        if (ec->changes.pos || ec->internal_elm_win)
          e_comp_object_frame_xy_unadjust(ec->frame, ec->x, ec->y, &x, &y);
        else
          {
             x = ec->client.x, y = ec->client.y;
             if (first)
               x -= ec->comp_data->shell.window.x, y -= ec->comp_data->shell.window.y;
          }

        if ((!ec->comp_data->buffer_commit) && e_client_has_xwindow(ec))
          /* use client geometry to avoid race condition from x11 configure request */
          x = ec->x, y = ec->y;
        else
          {
             ec->client.w = state->bw;
             ec->client.h = state->bh;
             if (!ec->changes.size)
               e_comp_object_frame_wh_adjust(ec->frame, ec->client.w, ec->client.h, &ec->w, &ec->h);
          }
        w = ec->client.w;
        h = ec->client.h;
        if (ec->comp_data->need_center)
          {
             Eina_List *l;
             E_Client *cec;
             EINA_LIST_FOREACH(ec->transients, l, cec)
               {
                  cec->placed = 0;
                  if (!cec->ignored) EC_CHANGED(cec);
               }
             ec->comp_data->need_center = 0;
          }
     }
   else
     w = state->bw, h = state->bh;
   if (!e_pixmap_usable_get(ec->pixmap))
     {
        if (ec->comp_data->mapped)
          {
             if ((ec->comp_data->shell.surface) && (ec->comp_data->shell.unmap))
               ec->comp_data->shell.unmap(ec->comp_data->shell.surface);
             else if (ec->comp_data->cursor || e_client_has_xwindow(ec) || ec->internal_elm_win ||
                      (ec->comp_data->sub.data && ec->comp_data->sub.data->parent->comp_data->mapped) ||
                      (ec == e_comp_wl->drag_client))
               {
                  ec->visible = EINA_FALSE;
                  evas_object_hide(ec->frame);
                  ec->comp_data->mapped = 0;
               }
          }
     }
   else
     {
        if (!ec->comp_data->mapped)
          {
             if ((ec->comp_data->shell.surface) && (ec->comp_data->shell.map))
               ec->comp_data->shell.map(ec->comp_data->shell.surface);
             else if ((ec == e_comp->pointer->client.ec) || e_client_has_xwindow(ec) || ec->internal_elm_win ||
                      (ec->comp_data->sub.data && ec->comp_data->sub.data->parent->comp_data->mapped) ||
                      (ec == e_comp_wl->drag_client))
               {
                  ec->visible = EINA_TRUE;
                  ec->ignored = 0;
                  evas_object_show(ec->frame);
                  ec->comp_data->mapped = 1;
                  if (e_client_has_xwindow(ec))
                    {
                       if (!e_client_util_desk_visible(ec, e_desk_current_get(ec->zone)))
                         evas_object_hide(ec->frame);
                    }
               }
          }
     }

   if (state->new_attach)
     {
        if ((ec->comp_data->shell.surface) && (ec->comp_data->shell.configure))
          ec->comp_data->shell.configure(ec->comp_data->shell.surface,
                                         x, y, state->bw, state->bh);
        else
          {
             if (ec->netwm.sync.wait)
               {
                  E_Client_Pending_Resize *pnd = NULL;

                  ec->netwm.sync.wait--;

                  /* skip pending for which we didn't get a reply */
                  while (ec->pending_resize)
                    {
                       pnd = eina_list_data_get(ec->pending_resize);
                       ec->pending_resize = eina_list_remove(ec->pending_resize, pnd);

                       if ((state->bw == pnd->w) && (state->bh == pnd->h))
                         break;

                       E_FREE(pnd);
                    }

                  if (pnd)
                    {
                       e_comp_object_frame_wh_adjust(ec->frame, pnd->w, pnd->h, &ec->w, &ec->h);
                       E_FREE(pnd);
                    }
                  ecore_evas_pointer_xy_get(e_comp->ee, &ec->mouse.current.mx, &ec->mouse.current.my);
                  ec->netwm.sync.send_time = ecore_loop_time_get();
               }
             if (e_comp_wl->drag && e_comp_wl->drag_client &&
                 (e_comp_wl->drag_client == ec))
               {
                  e_comp_wl->drag->dx -= state->sx;
                  e_comp_wl->drag->dy -= state->sy;
                  e_drag_move(e_comp_wl->drag,
                    e_comp_wl->drag->x + state->sx, e_comp_wl->drag->y + state->sy);
                  e_drag_resize(e_comp_wl->drag, state->bw, state->bh);
               }
             else
               e_client_util_move_resize_without_frame(ec, x, y, w, h);
          }

        if ((!ec->comp_data->sub.data) && (!ec->comp_data->buffer_commit))
          {
             if (e_client_has_xwindow(ec))
               ec->placed = 0;
             else if (!ec->placed)
               ec->placed = ec->netwm.type != E_WINDOW_TYPE_NORMAL;
             ec->want_focus |= ec->icccm.accepts_focus && (!ec->override);
          }
     }
   else if (ec->comp_data->need_xdg_configure && ec->comp_data->shell.surface && !ec->iconic)
     _e_comp_wl_configure_send(ec, 0);

   state->sx = 0;
   state->sy = 0;
   if (state->new_attach)
     ec->comp_data->buffer_commit = 1;
   state->new_attach = EINA_FALSE;

   /* insert state frame callbacks into comp_data->frames
    * NB: This clears state->frames list */
   ec->comp_data->frames = eina_list_merge(ec->comp_data->frames,
                                           state->frames);
   state->frames = NULL;

   /* put state damages into surface */
   if ((!e_comp->nocomp) && (ec->frame))
     {
        EINA_LIST_FREE(state->damages, dmg)
          {
             e_comp_object_damage(ec->frame, dmg->x, dmg->y, dmg->w, dmg->h);
             eina_rectangle_free(dmg);
          }
     }

   /* put state opaque into surface */
   if (state->opaque)
     {
        if (!eina_tiler_empty(state->opaque))
          {
             Eina_Rectangle *rect;
             Eina_Iterator *itr;

             /* This is seriously wrong and results in only the first
              * rectangle in the region being set, but in the usual
              * case there's only one rectangle.
              */
             itr = eina_tiler_iterator_new(state->opaque);
             EINA_ITERATOR_FOREACH(itr, rect)
               {
                  Eina_Rectangle r;

                  EINA_RECTANGLE_SET(&r, rect->x, rect->y, rect->w, rect->h);
                  E_RECTS_CLIP_TO_RECT(r.x, r.y, r.w, r.h, 0, 0, state->bw, state->bh);
                  e_pixmap_image_opaque_set(ec->pixmap, r.x, r.y, r.w, r.h);
                  break;
               }

             eina_iterator_free(itr);
             eina_tiler_free(state->opaque);
             state->opaque = NULL;
          }
        else
          e_pixmap_image_opaque_set(ec->pixmap, 0, 0, 0, 0);
     }

   /* put state input into surface */
   if (e_client_has_xwindow(ec))
     {
        Eina_Tiler *t = eina_tiler_new(ec->client.w, ec->client.h);
        eina_tiler_tile_size_set(t, 1, 1);
        if (ec->shaped_input)
          {
             Eina_Rectangle *r;
             unsigned int i;

             for (i = 0, r = ec->shape_input_rects; i < ec->shape_input_rects_num; i++, r++)
               eina_tiler_rect_add(t, r);
          }
        else
          eina_tiler_rect_add(t, &(Eina_Rectangle){0, 0, 65535, 65535});
        e_comp_object_input_area_set(ec->frame, t);
        eina_tiler_free(t);
     }
   else if (state->input)
     {
        e_comp_object_input_area_set(ec->frame, state->input);

        eina_tiler_free(state->input);
        state->input = NULL;
     }
   ec->comp_data->in_commit = 0;

   _e_comp_wl_surface_outputs_update(ec);
}

static void
_e_comp_wl_surface_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   E_Client *ec;
   DBG("Surface Cb Destroy: %d", wl_resource_get_id(resource));
   ec = wl_resource_get_user_data(resource);
   if (ec && (!e_object_is_del(E_OBJECT(ec)))) ec->comp_data->surface = NULL;
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_surface_cb_attach(struct wl_client *client, struct wl_resource *resource, struct wl_resource *buffer_resource, int32_t sx, int32_t sy)
{
   E_Client *ec;
   E_Comp_Wl_Buffer *buffer = NULL;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->comp_data->need_xdg_configure)
     {
        ec->comp_data->shell.buffer_attach_error(ec);
        return;
     }

   if (buffer_resource)
     {
        if (!(buffer = e_comp_wl_buffer_get(buffer_resource)))
          {
             ERR("Could not get buffer from resource");
             wl_client_post_no_memory(client);
             return;
          }
     }

   _e_comp_wl_surface_state_buffer_set(&ec->comp_data->pending, buffer);

   ec->comp_data->pending.sx = sx;
   ec->comp_data->pending.sy = sy;
   ec->comp_data->pending.new_attach = EINA_TRUE;
}

static void
_e_comp_wl_surface_cb_damage_buffer(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   E_Client *ec;
   Eina_Rectangle *dmg = NULL;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   if (!(dmg = eina_rectangle_new(x, y, w, h))) return;

   ec->comp_data->pending.damages =
     eina_list_append(ec->comp_data->pending.damages, dmg);
}

/*
 * Currently damage and damage_buffer are the same because we don't support
 * buffer_scale, transform, or viewport.  Once we support those we'll have
 * to make surface_cb_damage handle damage in surface co-ordinates.
 */
static void
_e_comp_wl_surface_cb_damage(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   _e_comp_wl_surface_cb_damage_buffer(client, resource, x, y, w, h);
}

static void
_e_comp_wl_frame_cb_destroy(struct wl_resource *resource)
{
   E_Client *ec;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   ec->comp_data->frames =
     eina_list_remove(ec->comp_data->frames, resource);

   ec->comp_data->pending.frames =
     eina_list_remove(ec->comp_data->pending.frames, resource);

   if (!ec->comp_data->sub.data) return;

   ec->comp_data->sub.data->cached.frames =
     eina_list_remove(ec->comp_data->sub.data->cached.frames, resource);
}

static void
_e_comp_wl_surface_cb_frame(struct wl_client *client, struct wl_resource *resource, uint32_t callback)
{
   E_Client *ec;
   struct wl_resource *res;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   /* create frame callback */
   if (!(res =
         wl_resource_create(client, &wl_callback_interface, 1, callback)))
     {
        wl_resource_post_no_memory(resource);
        return;
     }

   wl_resource_set_implementation(res, NULL, ec, _e_comp_wl_frame_cb_destroy);

   ec->comp_data->pending.frames =
     eina_list_append(ec->comp_data->pending.frames, res);
}

static void
_e_comp_wl_surface_cb_opaque_region_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region_resource)
{
   E_Client *ec;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   if (ec->comp_data->pending.opaque)
     eina_tiler_free(ec->comp_data->pending.opaque);
   ec->comp_data->pending.opaque = eina_tiler_new(65535, 65535);
   eina_tiler_tile_size_set(ec->comp_data->pending.opaque, 1, 1);
   if (region_resource)
     {
        Eina_Tiler *tmp;

        if (!(tmp = wl_resource_get_user_data(region_resource)))
          return;

        eina_tiler_union(ec->comp_data->pending.opaque, tmp);
     }
}

static void
_e_comp_wl_surface_cb_input_region_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region_resource)
{
   E_Client *ec;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->comp_data->cursor || (ec == e_comp_wl->drag_client)) return;

   if (ec->comp_data->pending.input)
     eina_tiler_free(ec->comp_data->pending.input);
   ec->comp_data->pending.input = eina_tiler_new(65535, 65535);
   eina_tiler_tile_size_set(ec->comp_data->pending.input, 1, 1);
   if (region_resource)
     {
        Eina_Tiler *tmp;

        if (!(tmp = wl_resource_get_user_data(region_resource)))
          return;

        eina_tiler_union(ec->comp_data->pending.input, tmp);
     }
   else
     {
        eina_tiler_rect_add(ec->comp_data->pending.input,
                            &(Eina_Rectangle){0, 0, 65535, 65535});
     }
}

static void
_e_comp_wl_surface_cb_commit(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   E_Client *ec, *subc;
   Eina_List *l;

   if (!(ec = wl_resource_get_user_data(resource))) return;
   if (e_object_is_del(E_OBJECT(ec))) return;

   if (e_comp_wl_subsurface_commit(ec)) return;

   e_comp_wl_surface_commit(ec);

   EINA_LIST_FOREACH(ec->comp_data->sub.list, l, subc)
     {
        if (ec != subc)
          _e_comp_wl_subsurface_parent_commit(subc, EINA_FALSE);
     }
}

static void
_e_comp_wl_surface_cb_buffer_transform_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, int32_t transform EINA_UNUSED)
{
   /* DBG("Surface Buffer Transform: %d", wl_resource_get_id(resource)); */
}

static void
_e_comp_wl_surface_cb_buffer_scale_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, int32_t scale EINA_UNUSED)
{
   /* DBG("Surface Buffer Scale: %d", wl_resource_get_id(resource)); */
}

static const struct wl_surface_interface _e_surface_interface =
{
   _e_comp_wl_surface_cb_destroy,
   _e_comp_wl_surface_cb_attach,
   _e_comp_wl_surface_cb_damage,
   _e_comp_wl_surface_cb_frame,
   _e_comp_wl_surface_cb_opaque_region_set,
   _e_comp_wl_surface_cb_input_region_set,
   _e_comp_wl_surface_cb_commit,
   _e_comp_wl_surface_cb_buffer_transform_set,
   _e_comp_wl_surface_cb_buffer_scale_set,
   _e_comp_wl_surface_cb_damage_buffer
};


static void
_e_comp_wl_surface_destroy(struct wl_resource *resource)
{
   E_Client *ec;

   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!e_object_is_del(E_OBJECT(ec)))
     {
        if (ec->comp_data->mapped)
          ec->comp_data->mapped = EINA_FALSE;
        evas_object_hide(ec->frame);
     }

   if (ec->internal_elm_win)
     {
        e_pixmap_alias(ec->pixmap, E_PIXMAP_TYPE_WL, wl_resource_get_id(resource));
        ec->ignored = 1;
     }
   else
     e_object_del(E_OBJECT(ec));
}

static void
_e_comp_wl_compositor_cb_surface_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   struct wl_resource *res;
   E_Client *wc, *ec = NULL;
   Eina_List *l;
   pid_t pid;

   DBG("Compositor Cb Surface Create: %d", id);

   /* try to create an internal surface */
   if (!(res = wl_resource_create(client, &wl_surface_interface,
                                  wl_resource_get_version(resource), id)))
     {
        ERR("Could not create compositor surface");
        wl_client_post_no_memory(client);
        return;
     }

   DBG("\tCreated Resource: %d", wl_resource_get_id(res));

   /* set implementation on resource */
   wl_resource_set_implementation(res, &_e_surface_interface, NULL,
                                  _e_comp_wl_surface_destroy);

   wl_client_get_credentials(client, &pid, NULL, NULL);
   if ((client != e_comp_wl->xwl_client) && (pid == getpid())) //internal!
     ec = e_pixmap_find_client(E_PIXMAP_TYPE_WL, (int64_t)id);
   if (ec)
     {
        if (e_object_is_del(E_OBJECT(ec))) return;
     }
   else
     {
        E_Pixmap *ep = NULL;

        /* try to create new pixmap */
        do
          {
             if (--surface_id >= 0) surface_id = -1;
             ep = e_pixmap_find(E_PIXMAP_TYPE_WL, surface_id);
          } while (ep);
        if (!(ep = e_pixmap_new(E_PIXMAP_TYPE_WL, surface_id)))
          {
             ERR("Could not create new pixmap");
             wl_resource_destroy(res);
             wl_client_post_no_memory(client);
             return;
          }
        DBG("\tUsing Pixmap: %p", ep);

        ec = e_client_new(ep, 0, 0);
     }
   if (ec->new_client)
     e_comp->new_clients--;
   ec->new_client = 0;
   if ((!ec->client.w) && (!ec->client.h))
     ec->client.w = ec->client.h = 1;
   ec->comp_data->surface = res;
   ec->netwm.pid = pid;
   if (!ec->internal_elm_win)
     {
        ec->icccm.min_w = ec->icccm.min_h =
        ec->icccm.max_w = ec->icccm.max_h = 0;
     }
   if (client != e_comp_wl->xwl_client)
     ec->internal = pid == getpid();

   if (ec->internal && (!e_comp_wl->wl.client_ec))
     {
        e_comp_wl->wl.client_ec = ec;
        if (!e_client_focused_get())
          {
             e_comp_wl->kbd.focus = e_comp_wl->wl.client_ec->comp_data->surface;
             e_comp_wl_data_device_keyboard_focus_set();
          }
     }
   ec->icccm.delete_request |= ec->internal;

   /* set reference to pixmap so we can fetch it later */
   DBG("\tUsing Client: %p", ec);
   wl_resource_set_user_data(res, ec);
#ifndef HAVE_WAYLAND_ONLY
   EINA_LIST_FOREACH(e_comp_wl->xwl_pending, l, wc)
     {
        if (!e_pixmap_is_x(wc->pixmap)) continue;
        if (wl_resource_get_id(res) !=
            ((E_Comp_X_Client_Data*)wc->comp_data)->surface_id) continue;
        // wc, ec is correct! first arg should be the x client, 2nd wl one
        e_comp_x_xwayland_client_setup(wc, ec);
        break;
     }
#endif
   /* emit surface create signal */
   wl_signal_emit(&e_comp_wl->signals.surface.create, res);

   e_object_ref(E_OBJECT(ec));
}

static void
_e_comp_wl_region_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   DBG("Region Destroy: %d", wl_resource_get_id(resource));
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_region_cb_add(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   Eina_Tiler *tiler;

   DBG("Region Add: %d", wl_resource_get_id(resource));
   DBG("\tGeom: %d %d %d %d", x, y, w, h);

   /* get the tiler from the resource */
   if ((tiler = wl_resource_get_user_data(resource)))
     eina_tiler_rect_add(tiler, &(Eina_Rectangle){x, y, w, h});
}

static void
_e_comp_wl_region_cb_subtract(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   Eina_Tiler *tiler;

   DBG("Region Subtract: %d", wl_resource_get_id(resource));
   DBG("\tGeom: %d %d %d %d", x, y, w, h);

   /* get the tiler from the resource */
   if ((tiler = wl_resource_get_user_data(resource)))
     eina_tiler_rect_del(tiler, &(Eina_Rectangle){x, y, w, h});
}

static const struct wl_region_interface _e_region_interface =
{
   _e_comp_wl_region_cb_destroy,
   _e_comp_wl_region_cb_add,
   _e_comp_wl_region_cb_subtract
};

static void
_e_comp_wl_compositor_cb_region_destroy(struct wl_resource *resource)
{
   Eina_Tiler *tiler;

   DBG("Compositor Region Destroy: %d", wl_resource_get_id(resource));

   /* try to get the tiler from the region resource */
   if ((tiler = wl_resource_get_user_data(resource)))
     eina_tiler_free(tiler);
}

static void
_e_comp_wl_compositor_cb_region_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Eina_Tiler *tiler;
   struct wl_resource *res;

   DBG("Region Create: %d", wl_resource_get_id(resource));

   /* try to create new tiler */
   if (!(tiler = eina_tiler_new(65535, 65535)))
     {
        ERR("Could not create Eina_Tiler");
        wl_resource_post_no_memory(resource);
        return;
     }

   /* set tiler size */
   eina_tiler_tile_size_set(tiler, 1, 1);

   if (!(res = wl_resource_create(client, &wl_region_interface, 1, id)))
     {
        ERR("\tFailed to create region resource");
        wl_resource_post_no_memory(resource);
        return;
     }

   wl_resource_set_implementation(res, &_e_region_interface, tiler,
                                  _e_comp_wl_compositor_cb_region_destroy);
}

static const struct wl_compositor_interface _e_comp_interface =
{
   _e_comp_wl_compositor_cb_surface_create,
   _e_comp_wl_compositor_cb_region_create
};

static void
_e_comp_wl_compositor_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!(res =
         wl_resource_create(client, &wl_compositor_interface,
                            version, id)))
     {
        ERR("Could not create compositor resource");
        wl_client_post_no_memory(client);
        return;
     }

   wl_resource_set_implementation(res, &_e_comp_interface, e_comp, NULL);
}

static void
_e_comp_wl_subsurface_destroy(struct wl_resource *resource)
{
   E_Client *ec;
   E_Comp_Wl_Subsurf_Data *sdata;

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!ec->comp_data) return;

   if (!(sdata = ec->comp_data->sub.data)) return;

   if (sdata->parent)
     {
        /* remove this client from parents sub list */
        sdata->parent->comp_data->sub.list =
          eina_list_remove(sdata->parent->comp_data->sub.list, ec);
     }

   _e_comp_wl_surface_state_finish(&sdata->cached);

   /* the client is getting deleted, which means the pixmap will be getting
    * freed. We need to unset the surface user data */
   /* wl_resource_set_user_data(ec->comp_data->surface, NULL); */

   E_FREE(sdata);

   ec->comp_data->sub.data = NULL;
}

static Eina_Bool
_e_comp_wl_subsurface_synchronized_get(E_Comp_Wl_Subsurf_Data *sdata)
{
   while (sdata)
     {
        if (sdata->synchronized) return EINA_TRUE;
        if (!sdata->parent) return EINA_FALSE;
        sdata = sdata->parent->comp_data->sub.data;
     }

   return EINA_FALSE;
}

static void
_e_comp_wl_subsurface_commit_to_cache(E_Client *ec)
{
   E_Comp_Client_Data *cdata;
   E_Comp_Wl_Subsurf_Data *sdata;

   if (!(cdata = ec->comp_data)) return;
   if (!(sdata = cdata->sub.data)) return;

   DBG("Subsurface Commit to Cache");

   /* move pending damage to cached */
   sdata->cached.damages = eina_list_merge(sdata->cached.damages,
                                           cdata->pending.damages);
   cdata->pending.damages = NULL;

   if (cdata->pending.new_attach)
     {
        sdata->cached.new_attach = EINA_TRUE;
        _e_comp_wl_surface_state_buffer_set(&sdata->cached,
                                            cdata->pending.buffer);
        e_pixmap_resource_set(ec->pixmap, cdata->pending.buffer);
        e_pixmap_dirty(ec->pixmap);
        e_pixmap_refresh(ec->pixmap);
     }

   sdata->cached.sx = cdata->pending.sx;
   sdata->cached.sy = cdata->pending.sy;
   /* sdata->cached.buffer = cdata->pending.buffer; */
   sdata->cached.new_attach = cdata->pending.new_attach;

   /* _e_comp_wl_surface_state_buffer_set(&cdata->pending, NULL); */
   /* cdata->pending.sx = 0; */
   /* cdata->pending.sy = 0; */
   /* cdata->pending.new_attach = EINA_FALSE; */

   sdata->cached.opaque = cdata->pending.opaque;
   cdata->pending.opaque = NULL;

   sdata->cached.input = cdata->pending.input;
   cdata->pending.input = NULL;

   sdata->cached.frames = eina_list_merge(sdata->cached.frames,
                                          cdata->pending.frames);
   cdata->pending.frames = NULL;
   sdata->cached.has_data = EINA_TRUE;
}

static void
_cb_early_frame_cb(void *data, const Efl_Event *event EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->comp_data->early_frame == 1)
     e_pixmap_image_clear(ec->pixmap, 1);

   /* If some client does a surface frame and never follows with
    * a render, it's just an idiot, so stop trying to help it with
    * timing.
    */
   if (ec->comp_data->early_frame++ > 15)
     {
       ec->comp_data->early_frame = 0;
       efl_event_callback_del(e_comp->evas, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK,
                              _cb_early_frame_cb, data);
     }
}

static void
_e_comp_wl_surface_early_frame(E_Client *ec)
{
   if (e_comp_object_damage_exists(ec->frame))
     {
        efl_event_callback_del(e_comp->evas, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK,
                               _cb_early_frame_cb, ec);
        ec->comp_data->early_frame = 0;
        return;
     }
   if (ec->on_post_updates) return;
   if (!ec->comp_data->early_frame)
       efl_event_callback_add(e_comp->evas, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK,
                              _cb_early_frame_cb, ec);
   ec->comp_data->early_frame = 1;
}

static void
_e_comp_wl_subsurface_commit_from_cache(E_Client *ec)
{
   E_Comp_Client_Data *cdata;
   E_Comp_Wl_Subsurf_Data *sdata;

   if (!(cdata = ec->comp_data)) return;
   if (!(sdata = cdata->sub.data)) return;

   DBG("Subsurface Commit from Cache");

   _e_comp_wl_surface_state_commit(ec, &sdata->cached);
   _e_comp_wl_surface_early_frame(ec);
}

static void
_e_comp_wl_subsurface_parent_commit(E_Client *ec, Eina_Bool parent_synchronized)
{
   E_Client *parent;
   E_Comp_Wl_Subsurf_Data *sdata;

   if (!(sdata = ec->comp_data->sub.data)) return;
   if (!(parent = sdata->parent)) return;

   if (sdata->position.set)
     {
        evas_object_move(ec->frame, parent->client.x + sdata->position.x,
                         parent->client.y + sdata->position.y);
        sdata->position.set = EINA_FALSE;
     }

   if ((parent_synchronized) || (sdata->synchronized))
     {
        E_Client *subc;
        Eina_List *l;

        if (sdata->cached.has_data)
          _e_comp_wl_subsurface_commit_from_cache(ec);

        EINA_LIST_FOREACH(ec->comp_data->sub.list, l, subc)
          {
             if (ec != subc)
               _e_comp_wl_subsurface_parent_commit(subc, EINA_TRUE);
          }
     }
}

static void
_e_comp_wl_subsurface_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_subsurface_cb_position_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y)
{
   E_Client *ec;
   E_Comp_Wl_Subsurf_Data *sdata;

   DBG("Subsurface Cb Position Set: %d", wl_resource_get_id(resource));

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!(sdata = ec->comp_data->sub.data)) return;

   sdata->position.x = x;
   sdata->position.y = y;
   sdata->position.set = EINA_TRUE;
}

static void
_e_comp_wl_subsurface_cb_place_above(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *sibling_resource)
{
   E_Client *ec, *ecs;
   E_Client *parent;

   DBG("Subsurface Cb Place Above: %d", wl_resource_get_id(resource));

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!ec->comp_data->sub.data) return;

   /* try to get the client from the sibling resource */
   if (!(ecs = wl_resource_get_user_data(sibling_resource))) return;

   if (!ecs->comp_data->sub.data) return;

   if (!(parent = ec->comp_data->sub.data->parent)) return;

   parent->comp_data->sub.list =
     eina_list_remove(parent->comp_data->sub.list, ec);

   parent->comp_data->sub.list =
     eina_list_append_relative(parent->comp_data->sub.list, ec, ecs);

   parent->comp_data->sub.restack_target = parent;
}

static void
_e_comp_wl_subsurface_cb_place_below(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *sibling_resource)
{
   E_Client *ec, *ecs;
   E_Client *parent;

   DBG("Subsurface Cb Place Below: %d", wl_resource_get_id(resource));

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!ec->comp_data->sub.data) return;

   /* try to get the client from the sibling resource */
   if (!(ecs = wl_resource_get_user_data(sibling_resource))) return;

   if (!ecs->comp_data->sub.data) return;

   if (!(parent = ec->comp_data->sub.data->parent)) return;

   parent->comp_data->sub.list =
     eina_list_remove(parent->comp_data->sub.list, ec);

   parent->comp_data->sub.list =
     eina_list_prepend_relative(parent->comp_data->sub.list, ec, ecs);

   parent->comp_data->sub.restack_target = parent;
}

static void
_e_comp_wl_subsurface_cb_sync_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   E_Client *ec;
   E_Comp_Wl_Subsurf_Data *sdata;

   DBG("Subsurface Cb Sync Set: %d", wl_resource_get_id(resource));

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!(sdata = ec->comp_data->sub.data)) return;

   sdata->synchronized = EINA_TRUE;
}

static void
_e_comp_wl_subsurface_cb_desync_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   E_Client *ec;
   E_Comp_Wl_Subsurf_Data *sdata;

   DBG("Subsurface Cb Desync Set: %d", wl_resource_get_id(resource));

   /* try to get the client from resource data */
   if (!(ec = wl_resource_get_user_data(resource))) return;

   if (!(sdata = ec->comp_data->sub.data)) return;

   sdata->synchronized = EINA_FALSE;
}

static const struct wl_subsurface_interface _e_subsurface_interface =
{
   _e_comp_wl_subsurface_cb_destroy,
   _e_comp_wl_subsurface_cb_position_set,
   _e_comp_wl_subsurface_cb_place_above,
   _e_comp_wl_subsurface_cb_place_below,
   _e_comp_wl_subsurface_cb_sync_set,
   _e_comp_wl_subsurface_cb_desync_set
};

static Eina_Bool
_e_comp_wl_subsurface_create(E_Client *ec, E_Client *epc, uint32_t id, struct wl_resource *surface_resource)
{
   struct wl_client *client;
   struct wl_resource *res;
   E_Comp_Wl_Subsurf_Data *sdata;

   /* try to get the wayland client from the surface resource */
   if (!(client = wl_resource_get_client(surface_resource)))
     {
        ERR("Could not get client from resource %d",
            wl_resource_get_id(surface_resource));
        return EINA_FALSE;
     }

   /* try to allocate subsurface data */
   if (!(sdata = E_NEW(E_Comp_Wl_Subsurf_Data, 1)))
     {
        ERR("Could not allocate space for subsurface data");
        goto dat_err;
     }

   /* try to create the subsurface resource */
   if (!(res = wl_resource_create(client, &wl_subsurface_interface, 1, id)))
     {
        ERR("Failed to create subsurface resource");
        wl_resource_post_no_memory(surface_resource);
        goto res_err;
     }

   /* set resource implementation */
   wl_resource_set_implementation(res, &_e_subsurface_interface, ec,
                                  _e_comp_wl_subsurface_destroy);

   _e_comp_wl_surface_state_init(&sdata->cached);

   /* set subsurface data properties */
   sdata->resource = res;
   sdata->synchronized = EINA_TRUE;
   sdata->parent = epc;

   /* set subsurface client properties */
   ec->borderless = EINA_TRUE;
   ec->argb = EINA_TRUE;
   ec->lock_border = EINA_TRUE;
   ec->lock_focus_in = ec->lock_focus_out = EINA_TRUE;
   ec->netwm.state.skip_taskbar = EINA_TRUE;
   ec->netwm.state.skip_pager = EINA_TRUE;
   ec->no_shape_cut = EINA_TRUE;
   ec->border_size = 0;
   ec->layer_block = 1;

   if (epc)
     {
        if (epc->comp_data)
          {
             /* append this client to the parents subsurface list */
             epc->comp_data->sub.list =
               eina_list_append(epc->comp_data->sub.list, ec);
          }

        /* TODO: add callbacks ?? */
     }

   ec->comp_data->surface = surface_resource;
   ec->comp_data->sub.data = sdata;
   if (epc)
     {
        evas_object_layer_set(ec->frame, evas_object_layer_get(epc->frame));
        evas_object_stack_above(ec->frame, epc->frame);
     }

   return EINA_TRUE;

res_err:
   free(sdata);
dat_err:
   return EINA_FALSE;
}

static void
_e_comp_wl_subcompositor_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_subcompositor_cb_subsurface_get(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource, struct wl_resource *parent_resource)
{
   E_Client *ec, *epc = NULL;
   static const char where[] = "get_subsurface: wl_subsurface@";

   if (!(ec = wl_resource_get_user_data(surface_resource))) return;
   if (!(epc = wl_resource_get_user_data(parent_resource))) return;

   if (ec == epc)
     {
        wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
                               "%s%d: wl_surface@%d cannot be its own parent",
                               where, id, wl_resource_get_id(surface_resource));
        return;
     }

   if (e_object_is_del(E_OBJECT(ec))) return;
   if (e_object_is_del(E_OBJECT(epc))) return;

   /* check if this surface is already a sub-surface */
   if ((ec->comp_data) && (ec->comp_data->sub.data))
     {
        wl_resource_post_error(resource,
                               WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
                               "%s%d: wl_surface@%d is already a sub-surface",
                               where, id, wl_resource_get_id(surface_resource));
        return;
     }

   /* try to create a new subsurface */
   if (!_e_comp_wl_subsurface_create(ec, epc, id, surface_resource))
     ERR("Failed to create subsurface for surface %d",
         wl_resource_get_id(surface_resource));
}

static const struct wl_subcompositor_interface _e_subcomp_interface =
{
   _e_comp_wl_subcompositor_cb_destroy,
   _e_comp_wl_subcompositor_cb_subsurface_get
};

static void
_e_comp_wl_subcompositor_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!(res =
         wl_resource_create(client, &wl_subcompositor_interface,
                            version, id)))
     {
        ERR("Could not create subcompositor resource");
        wl_client_post_no_memory(client);
        return;
     }

   wl_resource_set_implementation(res, &_e_subcomp_interface, e_comp, NULL);

   /* TODO: add handlers for client iconify/uniconify */
}

static void
_e_comp_wl_client_cb_new(void *data EINA_UNUSED, E_Client *ec)
{
   int64_t win;

   /* make sure this is a wayland client */
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_WL) return;

   /* get window id from pixmap */
   win = e_pixmap_window_get(ec->pixmap);

   if (!(ec->comp_data = E_NEW(E_Comp_Client_Data, 1)))
     {
        ERR("Could not allocate new client data structure");
        return;
     }

   wl_signal_init(&ec->comp_data->destroy_signal);
   _e_comp_wl_surface_state_init(&ec->comp_data->pending);

   /* set initial client properties */
   ec->argb = EINA_TRUE;
   ec->no_shape_cut = EINA_TRUE;
   ec->redirected = ec->ignored = 1;
   ec->border_size = 0;

   /* NB: could not find a better place to do this, BUT for internal windows,
    * we need to set delete_request else the close buttons on the frames do
    * basically nothing */
   if (ec->internal_elm_win)
     {
        ec->icccm.delete_request = EINA_TRUE;
        ec->override = elm_win_override_get(e_win_evas_win_get(ecore_evas_get(ecore_event_window_match(win))));
     }

   /* set initial client data properties */
   ec->comp_data->mapped = EINA_FALSE;
   ec->comp_data->pending.input = eina_tiler_new(65535, 65535);
   eina_tiler_tile_size_set(ec->comp_data->pending.input, 1, 1);
   eina_tiler_rect_add(ec->comp_data->pending.input,
                            &(Eina_Rectangle){0, 0, 65535, 65535});

   /* add this client to the hash */
   /* eina_hash_add(clients_win_hash, &win, ec); */
}

static void
_e_comp_wl_client_cb_del(void *data EINA_UNUSED, E_Client *ec)
{
   /* Eina_Rectangle *dmg; */
   struct wl_resource *cb;
   E_Client *subc;
   Eina_List *free_list;

   /* make sure this is a wayland client */
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_WL) return;

   efl_event_callback_del(e_comp->evas, EFL_CANVAS_OBJECT_EVENT_ANIMATOR_TICK,
                          _cb_early_frame_cb, ec);
   ec->comp_data->early_frame = 0;

   if (ec == e_comp_wl->wl.client_ec)
     e_comp_wl->wl.client_ec = NULL;
   e_comp_wl_extension_pointer_unconstrain(ec);

   /* remove sub list */
   EINA_LIST_FREE(ec->comp_data->sub.list, subc)
     {
        if (!e_object_is_del(E_OBJECT(subc)))
          subc->comp_data->sub.data->parent = NULL;
     }

   if ((!e_client_has_xwindow(ec)) && (ec->parent) && (ec->parent->modal == ec))
     {
        ec->parent->lock_close = EINA_FALSE;
        ec->parent->modal = NULL;
     }
   if (e_comp_wl->selection.target == ec) e_comp_wl->selection.target = NULL;

   if ((ec == e_client_focused_get()) && ec->visible) _e_comp_wl_keyboard_leave(ec);

   wl_signal_emit(&ec->comp_data->destroy_signal, &ec->comp_data->surface);

   _e_comp_wl_surface_state_finish(&ec->comp_data->pending);
   E_FREE_FUNC(ec->comp_data->on_focus_timer, ecore_timer_del);
   if (ec->mouse.in)
     _e_comp_wl_mouse_out(ec);
   else if (ec->comp_data->cursor)
     {
        Eina_List *l;
        struct wl_resource *res;

        EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
          {
             E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(res);

             if (ptr->cursor_set && (ptr->cursor == ec))
               {
                  ptr->cursor = NULL;
                  ptr->cursor_set = 0;
                  if (ptr->entered)
                    e_comp_wl_input_pointer_cursor_update(ptr);
               }
          }
     }

   if (ec->comp_data->aux_hint.hints)
     {
        E_Comp_Wl_Aux_Hint *hint;
        EINA_LIST_FREE(ec->comp_data->aux_hint.hints, hint)
          {
             eina_stringshare_del(hint->hint);
             eina_stringshare_del(hint->val);
             E_FREE(hint);
          }
     }

   /* The resource destroy callback will walk the state->frames list,
    * so move the list to a temporary first.
    */
   free_list = ec->comp_data->frames;
   ec->comp_data->frames = NULL;
   EINA_LIST_FREE(free_list, cb)
     wl_resource_destroy(cb);

   if (ec->comp_data->surface)
     wl_resource_set_user_data(ec->comp_data->surface, NULL);

   if (ec->internal_elm_win)
     evas_object_hide(ec->frame);

   /* WL clients take an extra ref at startup so they don't get deleted while
    * visible.  Since we drop that in the render loop we need to make sure
    * it's dropped here if the client isn't going to be rendered.
    */
   if (!evas_object_visible_get(ec->frame)) e_object_unref(E_OBJECT(ec));
}

static void
_e_comp_wl_client_cb_focus_set(void *data EINA_UNUSED, E_Client *ec)
{
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_WL) return;

   /* send configure */
   if (ec->comp_data->shell.configure_send)
     {
        if (ec->comp_data->shell.surface)
          _e_comp_wl_configure_send(ec, 1);
     }

   //if ((ec->icccm.take_focus) && (ec->icccm.accepts_focus))
     //e_grabinput_focus(e_client_util_win_get(ec),
                       //E_FOCUS_METHOD_LOCALLY_ACTIVE);
   //else if (!ec->icccm.accepts_focus)
     //e_grabinput_focus(e_client_util_win_get(ec),
                       //E_FOCUS_METHOD_GLOBALLY_ACTIVE);
   //else if (!ec->icccm.take_focus)
     //e_grabinput_focus(e_client_util_win_get(ec), E_FOCUS_METHOD_PASSIVE);

   e_comp_wl->kbd.focus = ec->comp_data->surface;
}

static void
_e_comp_wl_client_cb_focus_unset(void *data EINA_UNUSED, E_Client *ec)
{
   if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_WL) return;

   /* send configure */
   if (ec->comp_data->shell.configure_send)
     {
        if (ec->comp_data->shell.surface)
          _e_comp_wl_configure_send(ec, 1);
     }

   if (e_comp_wl->kbd.focus == ec->comp_data->surface)
     e_comp_wl->kbd.focus = NULL;
}

static void
_e_comp_wl_client_cb_move_begin(void *data EINA_UNUSED, E_Client *ec EINA_UNUSED)
{
   e_comp_wl->ptr.button_mask = 0;
}

static void
_e_comp_wl_client_cb_resize_begin(void *data EINA_UNUSED, E_Client *ec)
{
   e_comp_wl->ptr.button_mask = 0;
   if (e_client_has_xwindow(ec)) return;
   e_comp_wl->resize.edges = 0;
   if (ec->keyboard_resizing) return;
   switch (ec->resize_mode)
     {
      case E_POINTER_RESIZE_T: // 1
        e_comp_wl->resize.edges = 1;
        break;
      case E_POINTER_RESIZE_B: // 2
        e_comp_wl->resize.edges = 2;
        break;
      case E_POINTER_RESIZE_L: // 4
        e_comp_wl->resize.edges = 4;
        break;
      case E_POINTER_RESIZE_R: // 8
        e_comp_wl->resize.edges = 8;
        break;
      case E_POINTER_RESIZE_TL: // 5
        e_comp_wl->resize.edges = 5;
        break;
      case E_POINTER_RESIZE_TR: // 9
        e_comp_wl->resize.edges = 9;
        break;
      case E_POINTER_RESIZE_BL: // 6
        e_comp_wl->resize.edges = 6;
        break;
      case E_POINTER_RESIZE_BR: // 10
        e_comp_wl->resize.edges = 10;
        break;
      default:
        break;
     }
   ec->comp_data->shell.configure_send(ec->comp_data->shell.surface,
                                       e_comp_wl->resize.edges,
                                       0, 0);
}

static void
_e_comp_wl_client_cb_resize_end(void *data EINA_UNUSED, E_Client *ec)
{
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (e_client_has_xwindow(ec)) return;

   e_comp_wl->resize.edges = 0;
   e_comp_wl->resize.resource = NULL;

   ec->comp_data->shell.configure_send(ec->comp_data->shell.surface,
                                       e_comp_wl->resize.edges,
                                       0, 0);

   if (ec->pending_resize)
     {

        ec->changes.pos = EINA_TRUE;
        ec->changes.size = EINA_TRUE;
        EC_CHANGED(ec);
     }

   E_FREE_LIST(ec->pending_resize, free);
}

static void
_e_comp_wl_cb_output_unbind(struct wl_resource *resource)
{
   E_Comp_Wl_Output *output;

   if (!(output = wl_resource_get_user_data(resource))) return;

   output->resources = eina_list_remove(output->resources, resource);
}

static void
_e_comp_wl_cb_output_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   E_Comp_Wl_Output *output;
   struct wl_resource *resource;

   if (!(output = data)) return;

   resource =
     wl_resource_create(client, &wl_output_interface, version, id);
   if (!resource)
     {
        wl_client_post_no_memory(client);
        return;
     }

   DBG("Bound Output: %s", output->id);
   DBG("\tGeom: %d %d %d %d", output->x, output->y, output->w, output->h);

   output->resources = eina_list_append(output->resources, resource);

   wl_resource_set_implementation(resource, NULL, output,
                                  _e_comp_wl_cb_output_unbind);
   wl_resource_set_user_data(resource, output);

   wl_output_send_geometry(resource, output->x, output->y,
                           output->phys_width, output->phys_height,
                           output->subpixel, output->make ?: "",
                           output->model ?: "", output->transform);

   if (version >= WL_OUTPUT_SCALE_SINCE_VERSION)
     wl_output_send_scale(resource, output->scale);

   /* 3 == preferred + current */
   wl_output_send_mode(resource, 3, output->w, output->h, output->refresh);

   if (version >= WL_OUTPUT_DONE_SINCE_VERSION)
     wl_output_send_done(resource);
}

static Eina_Bool
_e_comp_wl_compositor_create(void)
{
   E_Comp_Wl_Data *cdata;

   /* create new compositor data */
   if (!(cdata = E_NEW(E_Comp_Wl_Data, 1)))
     {
       ERR("Could not create compositor data");
       return EINA_FALSE;
     }

   /* set compositor wayland data */
   e_comp_wl = e_comp->wl_comp_data = cdata;

   /* set wayland log handler */
   /* wl_log_set_handler_server(_e_comp_wl_log_cb_print); */

   /* try to create an ecore_wl2 display */
   cdata->ewd = ecore_wl2_display_create(NULL);
   if (!cdata->ewd)
     {
        ERR("Could not create a Wayland display");
        free(cdata);
        return EINA_FALSE;
     }

   cdata->wl.disp = ecore_wl2_display_get(cdata->ewd);
   if (!cdata->wl.disp)
     {
        ERR("Could not create a Wayland display");
        goto disp_err;
     }

   /* e_env_set("WAYLAND_DISPLAY", name); */

   /* initialize compositor signals */
   wl_signal_init(&cdata->signals.surface.create);
   wl_signal_init(&cdata->signals.surface.activate);
   wl_signal_init(&cdata->signals.surface.kill);

   /* cdata->output.transform = WL_OUTPUT_TRANSFORM_NORMAL; */
   /* cdata->output.scale = e_scale; */

   /* try to add compositor to wayland globals */
   if (!wl_global_create(cdata->wl.disp, &wl_compositor_interface,
                         COMPOSITOR_VERSION, e_comp,
                         _e_comp_wl_compositor_cb_bind))
     {
        ERR("Could not add compositor to wayland globals");
        goto comp_global_err;
     }

   /* try to add subcompositor to wayland globals */
   if (!wl_global_create(cdata->wl.disp, &wl_subcompositor_interface, 1,
                         e_comp, _e_comp_wl_subcompositor_cb_bind))
     {
        ERR("Could not add subcompositor to wayland globals");
        goto comp_global_err;
     }

   if (!e_comp_wl_extensions_init()) goto comp_global_err;

   /* initialize shm mechanism */
   wl_display_init_shm(cdata->wl.disp);

   /* _e_comp_wl_cb_randr_change(NULL, 0, NULL); */

   /* try to init data manager */
   if (!e_comp_wl_data_manager_init())
     {
        ERR("Could not initialize data manager");
        goto data_err;
     }

   /* try to init input */
   if (!e_comp_wl_input_init())
     {
        ERR("Could not initialize input");
        goto input_err;
     }
   e_comp_wl->wl.client_disp = ecore_wl2_display_connect(NULL);

   _e_comp_wl_modules_load();

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        e_comp_wl_input_pointer_enabled_set(EINA_TRUE);
        e_comp_wl_input_keyboard_enabled_set(EINA_TRUE);
        e_comp_wl_input_touch_enabled_set(EINA_TRUE);
     }

   return EINA_TRUE;

input_err:
   e_comp_wl_data_manager_shutdown();
data_err:
comp_global_err:
   /* e_env_unset("WAYLAND_DISPLAY"); */
/* sock_err: */
   ecore_wl2_display_destroy(cdata->ewd);
disp_err:
   free(cdata);
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_wl_desklock_show(void)
{
   return e_comp_grab_input(1, 1);
}

static void
_e_comp_wl_desklock_hide(void)
{
   e_comp_ungrab_input(1, 1);
}

static void
_e_comp_wl_gl_shutdown(void)
{
   if (!e_comp->gl) return;
   if (e_comp_wl->wl.glapi && e_comp_wl->wl.glapi->evasglUnbindWaylandDisplay)
     e_comp_wl->wl.glapi->evasglUnbindWaylandDisplay(e_comp_wl->wl.gl, e_comp_wl->wl.disp);
   if (e_comp_wl->wl.glsfc)
     evas_gl_surface_destroy(e_comp_wl->wl.gl, e_comp_wl->wl.glsfc);
   if (e_comp_wl->wl.glctx)
     evas_gl_context_destroy(e_comp_wl->wl.gl, e_comp_wl->wl.glctx);
   evas_gl_free(e_comp_wl->wl.gl);
   evas_gl_config_free(e_comp_wl->wl.glcfg);
}

Eina_Bool
e_comp_wl_query_dmabuf_formats(int max_formats, int *formats, int *num_formats)
{
   *num_formats = 0;
#if EVAS_GL_API_VERSION >= 7
   if (!e_comp_wl->wl.glapi) return EINA_FALSE;
   if (!e_comp_wl->wl.glapi->evasglQueryDmaBufFormats) return EINA_FALSE;
   return e_comp_wl->wl.glapi->evasglQueryDmaBufFormats(e_comp_wl->wl.gl, max_formats, formats, num_formats);
#else
   return EINA_FALSE;
#endif
}

Eina_Bool
e_comp_wl_query_dmabuf_modifiers(int format, int max_modifiers, uint64_t *modifiers, Eina_Bool *external_only, int *num_modifiers)
{
   *num_modifiers = 0;
#if EVAS_GL_API_VERSION >= 7
   if (!e_comp_wl->wl.glapi) return EINA_FALSE;
   if (!e_comp_wl->wl.glapi->evasglQueryDmaBufModifiers) return EINA_FALSE;
   return e_comp_wl->wl.glapi->evasglQueryDmaBufModifiers(e_comp_wl->wl.gl, format, max_modifiers, modifiers, external_only, num_modifiers);
#else
   return EINA_FALSE;
#endif
}

static void
_e_comp_wl_gl_init(void)
{
   e_comp_wl->wl.gl = evas_gl_new(ecore_evas_get(e_comp->ee));
   if (!e_comp_wl->wl.gl) return;
   e_comp_wl->wl.glctx = evas_gl_context_create(e_comp_wl->wl.gl, NULL);
   if (!e_comp_wl->wl.glctx) goto end;
   e_comp_wl->wl.glcfg = evas_gl_config_new();
   if (!e_comp_wl->wl.glcfg) goto end;
   e_comp_wl->wl.glsfc = evas_gl_surface_create(e_comp_wl->wl.gl, e_comp_wl->wl.glcfg, 1, 1);
   if (!e_comp_wl->wl.glsfc) goto end;
   if (!evas_gl_make_current(e_comp_wl->wl.gl, e_comp_wl->wl.glsfc, e_comp_wl->wl.glctx)) goto end;
   e_comp_wl->wl.glapi = evas_gl_context_api_get(e_comp_wl->wl.gl, e_comp_wl->wl.glctx);
   if (e_comp_wl->wl.glapi)
     {
        if (e_comp_wl->wl.glapi->evasglBindWaylandDisplay)
          e_comp->gl = e_comp_wl->wl.glapi->evasglBindWaylandDisplay(e_comp_wl->wl.gl, e_comp_wl->wl.disp);
        if (e_comp->gl)
          {
             e_util_env_set("ELM_ACCEL", "gl");
             return;
          }
     }
   else
     {
        ERR("Cannot get glapi. Is osmesa installed?");
     }
end:
   _e_comp_wl_gl_shutdown();
}

static Eina_Bool
_e_comp_wl_cb_uniconify(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Client *ev = event;
   E_Client *ec = ev->ec;

   if (ec->comp_data->need_xdg_configure)
      ec->comp_data->shell.configure_send(ec->comp_data->shell.surface, 0, 0, 0);
   return ECORE_CALLBACK_PASS_ON;
}

/* public functions */

/**
 * Creates and initializes a Wayland compositor with ecore.
 * Registers callback handlers for keyboard and mouse activity
 * and other client events.
 *
 * @returns true on success, false if initialization failed.
 */
E_API Eina_Bool
e_comp_wl_init(void)
{
   /* try to init ecore_wayland */
   if (!ecore_wl2_init())
     {
        fprintf(stderr, _("Enlightenment cannot initialize Wayland client connection.\n"));
        return EINA_FALSE;
     }

   /* Block session recovery for internal windows */
   ecore_wl2_session_recovery_disable();

   /* set gl available if we have ecore_evas support */
   if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_EGL) ||
       ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_OPENGL_DRM))
     e_comp_gl_set(EINA_TRUE);

   /* try to create a wayland compositor */
   if (!_e_comp_wl_compositor_create())
     {
        fprintf(stderr, _("Enlightenment cannot create a Wayland Compositor.\n"));
        return EINA_FALSE;
     }

   /* create hash to store clients */
   /* clients_win_hash = eina_hash_int64_new(NULL); */

   /* add event handlers to catch E events */
   if (e_comp->comp_type != E_PIXMAP_TYPE_X)
     {
        if (e_randr2_init() && (!e_xinerama_fake_screens_exist()))
          e_randr2_screens_setup(-1, -1);
        elm_config_preferred_engine_set("wayland_shm");
     }
   e_util_env_set("WAYLAND_DEBUG", "0");
   e_util_env_set("ELM_DISPLAY", "wl");
   if (e_comp_gl_get())
     _e_comp_wl_gl_init();

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_RANDR_CHANGE,
                                _e_comp_wl_cb_randr_change, NULL);

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_COMP_OBJECT_ADD,
                         _e_comp_wl_cb_comp_object_add, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_MOVE,
                         _e_comp_wl_cb_mouse_move, NULL);

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_UNICONIFY,
                         _e_comp_wl_cb_uniconify, NULL);

   /* add hooks to catch e_client events */
   e_client_hook_add(E_CLIENT_HOOK_NEW_CLIENT, _e_comp_wl_client_cb_new, NULL);
   e_client_hook_add(E_CLIENT_HOOK_DEL, _e_comp_wl_client_cb_del, NULL);

   e_client_hook_add(E_CLIENT_HOOK_FOCUS_SET,
                     _e_comp_wl_client_cb_focus_set, NULL);
   e_client_hook_add(E_CLIENT_HOOK_FOCUS_UNSET,
                     _e_comp_wl_client_cb_focus_unset, NULL);

   e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN,
                     _e_comp_wl_client_cb_move_begin, NULL);
   e_client_hook_add(E_CLIENT_HOOK_RESIZE_BEGIN,
                     _e_comp_wl_client_cb_resize_begin, NULL);
   e_client_hook_add(E_CLIENT_HOOK_RESIZE_END,
                     _e_comp_wl_client_cb_resize_end, NULL);

   e_desklock_show_hook_add(_e_comp_wl_desklock_show);
   e_desklock_hide_hook_add(_e_comp_wl_desklock_hide);

   E_EVENT_WAYLAND_GLOBAL_ADD = ecore_event_type_new();

   _last_event_time = ecore_loop_time_get();

   return EINA_TRUE;
}

/**
 * Get the signal that is fired for the creation of a Wayland surface.
 *
 * @returns the corresponding Wayland signal
 */
E_API struct wl_signal
e_comp_wl_surface_create_signal_get(void)
{
   return e_comp_wl->signals.surface.create;
}

/* internal functions */
EINTERN void
e_comp_wl_shutdown(void)
{
   E_Comp_Wl_Output *output;

   EINA_LIST_FREE(e_comp_wl->outputs, output)
     {
        if (output->id) eina_stringshare_del(output->id);
        if (output->make) eina_stringshare_del(output->make);
        if (output->model) eina_stringshare_del(output->model);
        free(output);
     }

   /* free handlers */
   E_FREE_LIST(handlers, ecore_event_handler_del);

   /* while (e_comp_wl->wl.globals) */
   /*   { */
   /*      Ecore_Wl_Global *global; */

   /*      global = */
   /*        EINA_INLIST_CONTAINER_GET(e_comp_wl->wl.globals, Ecore_Wl_Global); */

   /*      e_comp_wl->wl.globals = */
   /*        eina_inlist_remove(e_comp_wl->wl.globals, e_comp_wl->wl.globals); */

   /*      free(global->interface); */
   /*      free(global); */
   /*   } */

   if (e_comp_wl->wl.shm) wl_shm_destroy(e_comp_wl->wl.shm);
   _e_comp_wl_gl_shutdown();

   ecore_wl2_display_destroy(e_comp_wl->ewd);

   /* shutdown ecore_wayland */
   ecore_wl2_shutdown();

   /* delete fd handler */
   /* if (e_comp_wl->fd_hdlr) ecore_main_fd_handler_del(e_comp_wl->fd_hdlr); */

   /* free allocated data structure */
   free(e_comp_wl->extensions);
   free(e_comp_wl);
}

EINTERN struct wl_resource *
e_comp_wl_surface_create(struct wl_client *client, int version, uint32_t id)
{
   struct wl_resource *ret = NULL;

   if ((ret = wl_resource_create(client, &wl_surface_interface, version, id)))
     DBG("Created Surface: %d", wl_resource_get_id(ret));

   return ret;
}

EINTERN Eina_Bool
e_comp_wl_surface_commit(E_Client *ec)
{
   _e_comp_wl_surface_state_commit(ec, &ec->comp_data->pending);
   _e_comp_wl_surface_early_frame(ec);
   e_comp_wl_extension_pointer_constraints_commit(ec);

   return EINA_TRUE;
}

EINTERN Eina_Bool
e_comp_wl_subsurface_commit(E_Client *ec)
{
   E_Comp_Wl_Subsurf_Data *sdata;

   /* check for valid subcompositor data */
   if (!(sdata = ec->comp_data->sub.data)) return EINA_FALSE;

   if (_e_comp_wl_subsurface_synchronized_get(sdata))
     _e_comp_wl_subsurface_commit_to_cache(ec);
   else
     {
        E_Client *subc;
        Eina_List *l;

        if (sdata->cached.has_data)
          {
             _e_comp_wl_subsurface_commit_to_cache(ec);
             _e_comp_wl_subsurface_commit_from_cache(ec);
          }
        else
          e_comp_wl_surface_commit(ec);

        EINA_LIST_FOREACH(ec->comp_data->sub.list, l, subc)
          {
             if (ec != subc)
               _e_comp_wl_subsurface_parent_commit(subc, EINA_FALSE);
          }
     }

   return EINA_TRUE;
}

/**
 * Get the buffer for a given resource.
 *
 * Retrieves the Wayland SHM buffer for the resource and
 * uses it to create a new E_Comp_Wl_Buffer object. This
 * buffer will be freed when the resource is destroyed.
 *
 * @param resource that owns the desired buffer
 * @returns a new E_Comp_Wl_Buffer object
 */
E_API E_Comp_Wl_Buffer *
e_comp_wl_buffer_get(struct wl_resource *resource)
{
   E_Comp_Wl_Buffer *buffer;
   struct wl_listener *listener;
   struct wl_shm_buffer *shmbuff;
   struct linux_dmabuf_buffer *dmabuf;

   listener =
     wl_resource_get_destroy_listener(resource, _e_comp_wl_buffer_cb_destroy);
   if (listener)
     return container_of(listener, E_Comp_Wl_Buffer, destroy_listener);

   if (!(buffer = E_NEW(E_Comp_Wl_Buffer, 1))) return NULL;
   shmbuff = wl_shm_buffer_get(resource);
   dmabuf = linux_dmabuf_buffer_get(resource);
   if (shmbuff)
     {
        buffer->w = wl_shm_buffer_get_width(shmbuff);
        buffer->h = wl_shm_buffer_get_height(shmbuff);
     }
   else if (dmabuf)
     {
        buffer->w = dmabuf->attributes.width;
        buffer->h = dmabuf->attributes.height;
     }
   else if (e_comp->gl)
     {
        e_comp_wl->wl.glapi->evasglQueryWaylandBuffer(e_comp_wl->wl.gl, resource, EGL_WIDTH, &buffer->w);
        e_comp_wl->wl.glapi->evasglQueryWaylandBuffer(e_comp_wl->wl.gl, resource, EGL_HEIGHT, &buffer->h);
     }
   buffer->shm_buffer = shmbuff;
   buffer->dmabuf_buffer = dmabuf;

   buffer->resource = resource;
   wl_signal_init(&buffer->destroy_signal);
   buffer->destroy_listener.notify = _e_comp_wl_buffer_cb_destroy;
   wl_resource_add_destroy_listener(resource, &buffer->destroy_listener);

   return buffer;
}

/**
 * Computes the time since the last input event.
 *
 * @returns time in seconds.
 */
E_API double
e_comp_wl_idle_time_get(void)
{
   return (ecore_loop_time_get() - _last_event_time);
}

static E_Comp_Wl_Output *
_e_comp_wl_output_get(Eina_List *outputs, const char *id)
{
   Eina_List *l;
   E_Comp_Wl_Output *output;

   EINA_LIST_FOREACH(outputs, l, output)
     {
       if (!strcmp(output->id, id))
         return output;
     }

   return NULL;
}

/**
 * Initializes information about one display output.
 *
 * Adds or updates the given data about a single display output,
 * with an id matching the provided id.
 *
 * @param id         identification of output to be added or changed
 * @param make       manufacturer name of the display output
 * @param model      model name of the display output
 * @param x          output's top left corner x coordinate
 * @param y          output's top left corner y coordinate
 * @param w          output's width in pixels
 * @param h          output's height in pixels
 * @param pw         output's physical width in millimeters
 * @param ph         output's physical height in millimeters
 * @param refresh    output's refresh rate in Hz
 * @param subpixel   output's subpixel layout
 * @param transform  output's rotation and/or mirror transformation
 *
 * @returns True if a display output object could be added or updated
 */
E_API Eina_Bool
e_comp_wl_output_init(const char *id, const char *make, const char *model,
                      int x, int y, int w, int h, int pw, int ph,
                      unsigned int refresh, unsigned int subpixel,
                      unsigned int transform, unsigned int num)
{
   E_Comp_Wl_Output *output;
   Eina_List *l2;
   struct wl_resource *resource;
   E_Zone *zone;

   /* retrieve named output; or create it if it doesn't exist */
   output = _e_comp_wl_output_get(e_comp_wl->outputs, id);
   if (!output)
     {
        if (e_xinerama_fake_screens_exist())
          zone = e_comp_zone_number_get(num);
        else
          zone = e_zone_for_id_get(id);
        if (!zone) return EINA_FALSE;
        if (!(output = E_NEW(E_Comp_Wl_Output, 1))) return EINA_FALSE;

        if (id) output->id = eina_stringshare_add(id);
        if (make) output->make = eina_stringshare_add(make);
        if (model) output->model = eina_stringshare_add(model);

        e_comp_wl->outputs = eina_list_append(e_comp_wl->outputs, output);

        output->global =
          wl_global_create(e_comp_wl->wl.disp, &wl_output_interface,
                           2, output, _e_comp_wl_cb_output_bind);

        output->resources = NULL;
        output->scale = e_scale;

        zone->output = output;
     }

   /* update the output details */
   output->x = x;
   output->y = y;
   output->w = w;
   output->h = h;
   output->phys_width = pw;
   output->phys_height = ph;
   output->refresh = refresh * 1000;
   output->subpixel = subpixel;
   output->transform = transform;

   if (output->scale <= 0)
     output->scale = e_scale;

   /* if we have bound resources, send updates */
   EINA_LIST_FOREACH(output->resources, l2, resource)
     {
        wl_output_send_geometry(resource,
                                output->x, output->y,
                                output->phys_width,
                                output->phys_height,
                                output->subpixel,
                                output->make ?: "", output->model ?: "",
                                output->transform);

        if (wl_resource_get_version(resource) >= WL_OUTPUT_SCALE_SINCE_VERSION)
          wl_output_send_scale(resource, output->scale);

        /* 3 == preferred + current */
        wl_output_send_mode(resource, 3, output->w, output->h, output->refresh);

        if (wl_resource_get_version(resource) >= WL_OUTPUT_DONE_SINCE_VERSION)
          wl_output_send_done(resource);
     }

   return EINA_TRUE;
}

E_API void
e_comp_wl_output_remove(const char *id)
{
   E_Comp_Wl_Output *output;

   output = _e_comp_wl_output_get(e_comp_wl->outputs, id);
   if (output)
     {
        e_comp_wl->outputs = eina_list_remove(e_comp_wl->outputs, output);

        /* wl_global_destroy(output->global); */

        /* eina_stringshare_del(output->id); */
        /* eina_stringshare_del(output->make); */
        /* eina_stringshare_del(output->model); */

        /* free(output); */
     }
}

EINTERN Eina_Bool
e_comp_wl_key_down(Ecore_Event_Key *ev, E_Client *ec)
{
   uint32_t serial, *end, *k, keycode;

   if ((e_comp->comp_type != E_PIXMAP_TYPE_WL) || (ev->window != e_comp->ee_win)) return EINA_FALSE;
   _last_event_time = ecore_loop_time_get();

   keycode = (ev->keycode - 8);
   if (!(e_comp_wl = e_comp->wl_comp_data)) return EINA_FALSE;

#ifndef E_RELEASE_BUILD
   if ((ev->modifiers & ECORE_EVENT_MODIFIER_CTRL) &&
       ((ev->modifiers & ECORE_EVENT_MODIFIER_ALT) ||
       (ev->modifiers & ECORE_EVENT_MODIFIER_ALTGR)) &&
       eina_streq(ev->key, "BackSpace"))
     {
        if (!e_desklock_state_get()) exit(0);
     }
#endif

   end = (uint32_t *)e_comp_wl->kbd.keys.data + (e_comp_wl->kbd.keys.size / sizeof(*k));

   for (k = e_comp_wl->kbd.keys.data; k < end; k++)
     {
        /* ignore server-generated key repeats */
        if (*k == keycode) return EINA_FALSE;
     }

   e_comp_wl->kbd.keys.size = (const char *)end - (const char *)e_comp_wl->kbd.keys.data;
   if (!(k = wl_array_add(&e_comp_wl->kbd.keys, sizeof(*k))))
     {
        DBG("wl_array_add: Out of memory\n");
        return EINA_FALSE;
     }
   *k = keycode;

   if ((!e_client_action_get()) && (!e_comp->input_key_grabs) &&
       (!e_menu_is_active()))
     {
        if (!ec) ec = e_client_focused_get();
        if (ec && ec->comp_data->surface && e_comp_wl->kbd.focused)
          {
             struct wl_resource *res;
             Eina_List *l;

             serial = wl_display_next_serial(e_comp_wl->wl.disp);
             EINA_LIST_FOREACH(e_comp_wl->kbd.focused, l, res)
               wl_keyboard_send_key(res, serial, ev->timestamp,
                               keycode, WL_KEYBOARD_KEY_STATE_PRESSED);
          }
     }

   /* update modifier state */
   e_comp_wl_input_keyboard_state_update(keycode, EINA_TRUE);
   return !!ec;
}

EINTERN Eina_Bool
e_comp_wl_key_up(Ecore_Event_Key *ev, E_Client *ec)
{
   uint32_t serial, *end, *k, keycode;
   struct wl_resource *res;
   Eina_List *l;

   if ((e_comp->comp_type != E_PIXMAP_TYPE_WL) ||
       (ev->window != e_comp->ee_win)) return EINA_FALSE;

   _last_event_time = ecore_loop_time_get();

   keycode = (ev->keycode - 8);
   if (!(e_comp_wl = e_comp->wl_comp_data)) return EINA_FALSE;

   end = (uint32_t *)e_comp_wl->kbd.keys.data + (e_comp_wl->kbd.keys.size / sizeof(*k));
   for (k = e_comp_wl->kbd.keys.data; k < end; k++)
     {
        if (*k == keycode)
          {
             *k = *--end;
             break;
          }
     }

   e_comp_wl->kbd.keys.size =
     (const char *)end - (const char *)e_comp_wl->kbd.keys.data;

   if ((!e_client_action_get()) && (!e_comp->input_key_grabs) &&
       (!e_menu_is_active()))
     {
        if (!ec) ec = e_client_focused_get();

        if (e_comp_wl->kbd.focused)
          {
             serial = wl_display_next_serial(e_comp_wl->wl.disp);
             EINA_LIST_FOREACH(e_comp_wl->kbd.focused, l, res)
               wl_keyboard_send_key(res, serial, ev->timestamp,
                                    keycode, WL_KEYBOARD_KEY_STATE_RELEASED);
          }
     }

   /* update modifier state */
   e_comp_wl_input_keyboard_state_update(keycode, EINA_FALSE);
   return !!ec;
}

E_API Eina_Bool
e_comp_wl_evas_handle_mouse_button(E_Client *ec, uint32_t timestamp, uint32_t button_id, uint32_t state)
{
   Eina_List *l;
   struct wl_client *wc;
   uint32_t serial, btn, *state_serial;
   struct wl_resource *res;

   if (ec->cur_mouse_action || ec->border_menu || e_comp_wl->drag)
     return EINA_FALSE;
   if (e_object_is_del(E_OBJECT(ec))) return EINA_FALSE;
   if (ec->ignored) return EINA_FALSE;
   if (!ec->mouse.in) return EINA_FALSE;

   switch (button_id)
     {
      case 1:
        btn = BTN_LEFT;
        break;
      case 2:
        btn = BTN_MIDDLE;
        break;
      case 3:
        btn = BTN_RIGHT;
        break;
      case 4:
        btn = BTN_SIDE;
        break;
      case 5:
        btn = BTN_EXTRA;
        break;
      case 6:
        btn = BTN_FORWARD;
        break;
      case 7:
        btn = BTN_BACK;
        break;
      default:
        btn = button_id + BTN_SIDE - 8;
        break;
     }

   if (state == WL_POINTER_BUTTON_STATE_PRESSED)
     {
        e_comp_wl->ptr.button_mask |= 1 << button_id;
        state_serial = &e_comp_wl->ptr.serial[0];
     }
   else
     {
        /* reject release events if button is not pressed */
        if (!(e_comp_wl->ptr.button_mask & (1 << button_id))) return EINA_FALSE;
        e_comp_wl->ptr.button_mask &= ~(1 << button_id);
        state_serial = &e_comp_wl->ptr.serial[1];
     }
   e_comp_wl->ptr.button = btn;

   if (!ec->comp_data->surface) return EINA_FALSE;

   if (!eina_list_count(e_comp_wl->ptr.resources))
     return EINA_TRUE;

   wc = wl_resource_get_client(ec->comp_data->surface);
   *state_serial = serial = wl_display_next_serial(e_comp_wl->wl.disp);

   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        if (wl_resource_get_client(res) != wc) continue;
        if (!e_comp_wl_input_pointer_check(res)) continue;
        wl_pointer_send_button(res, serial, timestamp, btn, state);
     }
   return EINA_TRUE;
}

EINTERN void
e_comp_wl_xwayland_client_queue(E_Client *ec)
{
   e_comp_wl->xwl_pending = eina_list_append(e_comp_wl->xwl_pending, ec);
}

E_API void
e_comp_wl_grab_client_add(E_Client *ec, E_Comp_Wl_Grab_End_Cb cb)
{
   E_Client *gec, *parent = e_client_util_top_parent_get(ec);
   E_Comp_Wl_Grab_End_Cb grabcb;

   if (grab_clients && (parent != e_client_util_top_parent_get(eina_list_data_get(grab_clients))))
     {
        /* dismiss grabs in order when grabbing from new toplevel */
        EINA_LIST_FREE(grab_clients, gec)
          {
             grabcb = eina_list_data_get(grab_cbs);
             if (grabcb) grabcb(gec);
             grab_cbs = eina_list_remove_list(grab_cbs, grab_cbs);
          }
     }
   grab_clients = eina_list_prepend(grab_clients, ec);
   grab_cbs = eina_list_prepend(grab_cbs, cb);
   ec->comp_data->grab = 1;
   if (eina_list_count(grab_clients) == 1) e_bindings_disabled_set(1);
}

E_API void
e_comp_wl_grab_client_del(E_Client *ec, Eina_Bool dismiss)
{
   Eina_List *l, *ll;
   E_Client *gec;
   int n = -1;
   E_Comp_Wl_Grab_End_Cb cb;

   EINA_LIST_FOREACH(grab_clients, l, gec)
     {
        n++;
        if (gec != ec) continue;
        ll = eina_list_nth_list(grab_cbs, n);
        cb = eina_list_data_get(ll);
        if (dismiss && cb) cb(gec);
        grab_cbs = eina_list_remove_list(grab_cbs, ll);
        grab_clients = eina_list_remove_list(grab_clients, l);
        break;
     }
   if (!grab_clients)
     e_bindings_disabled_set(0);
}

E_API Eina_Bool
e_comp_wl_client_is_grabbed(const E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(ec, EINA_FALSE);
   if (e_object_is_del(E_OBJECT(ec))) return EINA_FALSE;
   return ec->comp_data->grab && evas_object_visible_get(ec->frame);
}

static Eina_Bool
_check_grab_coords(E_Client *ec, int x, int y)
{
   E_Client *tec, *pec;

   E_CLIENT_REVERSE_FOREACH(tec)
     {
        if (!e_comp_object_coords_inside_input_area(tec->frame, x, y)) continue;
        if (tec == ec) return EINA_TRUE;
        pec = ec->parent;
        while (pec)
          {
             if (tec == pec) return EINA_TRUE;
             pec = pec->parent;
          }
        return EINA_FALSE;
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_comp_wl_grab_client_mouse_move(const Ecore_Event_Mouse_Move *ev)
{
   E_Client *ec;
   Evas_Object *o;

   if (e_comp->comp_type != E_PIXMAP_TYPE_WL) return ECORE_CALLBACK_RENEW;
   ec = e_client_focused_get();
   if (!ec) return ECORE_CALLBACK_RENEW;
   if (!e_client_util_is_popup(ec)) return ECORE_CALLBACK_RENEW;
   if (!e_comp_wl_client_is_grabbed(ec)) return ECORE_CALLBACK_RENEW;
   /* reject mouse moves from outside the popup */
   if (_check_grab_coords(ec, ev->x, ev->y)) return ECORE_CALLBACK_RENEW;
   /* manually move the pointer since we're about to block the event globally */
   ecore_evas_cursor_get(e_comp->ee, &o, NULL, NULL, NULL);
   evas_object_move(o, ev->x, ev->y);
   return ECORE_CALLBACK_DONE;
}

E_API Eina_Bool
e_comp_wl_grab_client_mouse_button(const Ecore_Event_Mouse_Button *ev)
{
   E_Client *ec;

   if (e_comp->comp_type != E_PIXMAP_TYPE_WL) return ECORE_CALLBACK_RENEW;
   ec = e_client_focused_get();
   if (!ec) return ECORE_CALLBACK_RENEW;
   if (!e_client_util_is_popup(ec)) return ECORE_CALLBACK_RENEW;
   if (!e_comp_wl_client_is_grabbed(ec)) return ECORE_CALLBACK_RENEW;
   /* reject mouse moves from outside the popup */
   if (_check_grab_coords(ec, ev->x, ev->y)) return ECORE_CALLBACK_RENEW;
   e_comp_wl_grab_client_del(ec, 1);
   while (grab_clients)
     e_comp_wl_grab_client_del(eina_list_last_data_get(grab_clients), 1);
   evas_event_feed_mouse_move(e_comp->evas, ev->x, ev->y, 0, NULL);
   e_comp_canvas_feed_mouse_up(0);
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool saver_inhibit = EINA_FALSE;
static Eina_Bool saver_on = EINA_FALSE;
static Ecore_Timer *screensaver_eval_timer = NULL;
static Ecore_Timer *screensaver_idle_timer = NULL;

static Eina_Bool
_e_comp_wl_screensaver_eval_cb(void *d EINA_UNUSED)
{
   screensaver_eval_timer = NULL;
   if (!saver_on)
     {
        if (e_comp->screen && e_comp->screen->dpms)
          e_comp->screen->dpms(0);
     }
   e_screensaver_eval(saver_on);
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_wl_screensaver_idle_cb(void *data EINA_UNUSED)
{
   screensaver_idle_timer = NULL;
   if (e_screensaver_ignore_get()) return EINA_FALSE;
   if (!saver_on)
     {
        saver_on = EINA_TRUE;
        E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
        screensaver_eval_timer = ecore_timer_loop_add
          (0.3, _e_comp_wl_screensaver_eval_cb, NULL);
     }
   return EINA_FALSE;
}

static void
_e_comp_cb_pointer_suspend_resume_done(void *data, Evas_Object *obj, const char *emission, const char *source)
{
   edje_object_signal_callback_del(obj, emission, source,
                                   _e_comp_cb_pointer_suspend_resume_done);
   if (!data)
     {
        e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
     }
}

EINTERN Eina_Bool
_e_comp_wl_screensaver_on()
{
   const char *s;

   if ((!e_comp->pointer) || (!e_comp->pointer->o_ptr)) return ECORE_CALLBACK_RENEW;
   s = edje_object_data_get(e_comp->pointer->o_ptr, "can_suspend");

   if ((s) && (atoi(s) == 1))
     {
        if (!e_desklock_state_get())
          {
             e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
             e_pointer_grab_set(e_comp->pointer, EINA_TRUE);
          }
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_add(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done",
                                        "e",
                                        _e_comp_cb_pointer_suspend_resume_done,
                                        e_comp);
        edje_object_signal_emit(e_comp->pointer->o_ptr,
                                "e,state,mouse,suspend", "e");
     }
   return ECORE_CALLBACK_RENEW;
}

EINTERN Eina_Bool
_e_comp_wl_screensaver_off()
{
   const char *s;

   e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
   if ((!e_comp->pointer) || (!e_comp->pointer->o_ptr)) return ECORE_CALLBACK_RENEW;
   s = edje_object_data_get(e_comp->pointer->o_ptr, "can_suspend");

   if ((s) && (atoi(s) == 1))
     {
        if (!e_desklock_state_get())
          {
             e_pointer_grab_set(e_comp->pointer, EINA_TRUE);
          }
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_add(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done",
                                        "e",
                                        _e_comp_cb_pointer_suspend_resume_done,
                                        NULL);
        edje_object_signal_emit(e_comp->pointer->o_ptr,
                                "e,state,mouse,resume", "e");
     }
   return ECORE_CALLBACK_RENEW;
}

E_API void
e_comp_wl_notidle(void)
{
   int timeout;

   if (saver_on)
     {
        saver_on = EINA_FALSE;
        E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
        screensaver_eval_timer = ecore_timer_loop_add
          (0.3, _e_comp_wl_screensaver_eval_cb, NULL);
     }
   E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
   timeout = e_screensaver_timeout_get(EINA_TRUE);
   if (timeout > 0)
     screensaver_idle_timer = ecore_timer_add
       (timeout, _e_comp_wl_screensaver_idle_cb, NULL);
}

E_API void
e_comp_wl_screensaver_activate(void)
{
   saver_on = EINA_TRUE;
   E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
   E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
   e_screensaver_eval(saver_on);
}

E_API void
e_comp_wl_screensaver_inhibit(Eina_Bool inhibit)
{
   if (inhibit == saver_inhibit) return;
   saver_inhibit = inhibit;
   e_comp_wl_notidle();
   if (inhibit)
     E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
}
/*
 * Copyright © 2011 Kristian Høgsberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#define EXECUTIVE_MODE_ENABLED
#define E_COMP_WL
#include "e.h"

#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif

#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
                     WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
                     WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)

static void
_mime_types_free(E_Comp_Wl_Data_Source *source)
{
   if (!source->mime_types) return;
   while (eina_array_count(source->mime_types))
     eina_stringshare_del(eina_array_pop(source->mime_types));
   eina_array_free(source->mime_types);
}

static void
_e_comp_wl_data_offer_cb_accept(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t serial, const char *mime_type)
{
   E_Comp_Wl_Data_Offer *offer;

   DBG("Data Offer Accept");
   if (!(offer = wl_resource_get_user_data(resource)))
     return;

   /* Protect against untimely calls from older data offers */
   if ((!offer->source) || (offer != offer->source->offer))
     return;

   offer->source->target(offer->source, serial, mime_type);
   offer->source->accepted = !!mime_type;
}

static void
_e_comp_wl_data_offer_cb_receive(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *mime_type, int32_t fd)
{
   E_Comp_Wl_Data_Offer *offer;

   DBG("Data Offer Receive");

   if (!(offer = wl_resource_get_user_data(resource)))
     return;

   if (offer->source)
     offer->source->send(offer->source, mime_type, fd);
   else
     close(fd);
}

/* called by wl_data_offer_destroy */
static void
_e_comp_wl_data_offer_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   DBG("Data Offer Destroy");
   wl_resource_destroy(resource);
}

static void
data_source_notify_finish(E_Comp_Wl_Data_Source *source)
{
   if (!source->actions_set)
     return;

   if (source->offer->in_ask &&
       wl_resource_get_version(source->resource) >=
       WL_DATA_SOURCE_ACTION_SINCE_VERSION)
     {
        wl_data_source_send_action(source->resource,
                                   source->current_dnd_action);
     }

   if (wl_resource_get_version(source->resource) >=
       WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION)
     {
        wl_data_source_send_dnd_finished(source->resource);
     }

   source->offer = NULL;
}

static uint32_t
data_offer_choose_action(E_Comp_Wl_Data_Offer *offer)
{
   uint32_t available_actions, preferred_action = 0;
   uint32_t source_actions, offer_actions;

   if (wl_resource_get_version(offer->resource) >=
       WL_DATA_OFFER_ACTION_SINCE_VERSION)
     {
        offer_actions = offer->dnd_actions;
        preferred_action = offer->preferred_dnd_action;
     }
   else
     {
        offer_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
     }

   if (wl_resource_get_version(offer->source->resource) >=
       WL_DATA_SOURCE_ACTION_SINCE_VERSION)
     source_actions = offer->source->dnd_actions;
   else
     source_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;

   available_actions = offer_actions & source_actions;

   if (!available_actions)
     return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;

   if (     //offer->source->seat &&
     offer->source->compositor_action & available_actions)
     return offer->source->compositor_action;

   /* If the dest side has a preferred DnD action, use it */
   if ((preferred_action & available_actions) != 0)
     return preferred_action;

   /* Use the first found action, in bit order */
   return 1 << (ffs(available_actions) - 1);
}

static void
data_offer_update_action(E_Comp_Wl_Data_Offer *offer)
{
   uint32_t action;

   if (!offer->source)
     return;

   action = data_offer_choose_action(offer);

   if (offer->source->current_dnd_action == action)
     return;

   offer->source->current_dnd_action = action;

   if (offer->in_ask)
     return;

   if (wl_resource_get_version(offer->source->resource) >=
       WL_DATA_SOURCE_ACTION_SINCE_VERSION)
     wl_data_source_send_action(offer->source->resource, action);

   if (wl_resource_get_version(offer->resource) >=
       WL_DATA_OFFER_ACTION_SINCE_VERSION)
     wl_data_offer_send_action(offer->resource, action);
}

static void
data_offer_set_actions(struct wl_client *client,
                       struct wl_resource *resource,
                       uint32_t dnd_actions, uint32_t preferred_action)
{
   E_Comp_Wl_Data_Offer *offer = wl_resource_get_user_data(resource);

   if (dnd_actions & ~ALL_ACTIONS)
     {
        wl_resource_post_error(offer->resource,
                               WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK,
                               "invalid action mask %x", dnd_actions);
        return;
     }

   if (preferred_action &&
       (!(preferred_action & dnd_actions) ||
        __builtin_popcount(preferred_action) > 1))
     {
        wl_resource_post_error(offer->resource,
                               WL_DATA_OFFER_ERROR_INVALID_ACTION,
                               "invalid action %x", preferred_action);
        return;
     }

   offer->dnd_actions = dnd_actions;
   offer->preferred_dnd_action = preferred_action;
   data_offer_update_action(offer);
}

static void
data_offer_finish(struct wl_client *client, struct wl_resource *resource)
{
   E_Comp_Wl_Data_Offer *offer = wl_resource_get_user_data(resource);

   if (!offer->source || offer->source->offer != offer)
     return;

   /* Disallow finish while we have a grab driving drag-and-drop, or
    * if the negotiation is not at the right stage
    */
   if (     //offer->source->seat ||
     !offer->source->accepted)
     {
        wl_resource_post_error(offer->resource,
                               WL_DATA_OFFER_ERROR_INVALID_FINISH,
                               "premature finish request");
        return;
     }

   switch (offer->source->current_dnd_action)
     {
      case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE:
      case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK:
        wl_resource_post_error(offer->resource,
                               WL_DATA_OFFER_ERROR_INVALID_OFFER,
                               "offer finished with an invalid action");
        return;

      default:
        break;
     }

   data_source_notify_finish(offer->source);
}

/* called by wl_resource_destroy */
static void
_e_comp_wl_data_offer_cb_resource_destroy(struct wl_resource *resource)
{
   E_Comp_Wl_Data_Offer *offer = wl_resource_get_user_data(resource);

   if (!offer->source)
     goto out;

   wl_list_remove(&offer->source_destroy_listener.link);

   if (offer->source->offer != offer)
     goto out;

   /* If the drag destination has version < 3, wl_data_offer.finish
    * won't be called, so do this here as a safety net, because
    * we still want the version >=3 drag source to be happy.
    */
   if (wl_resource_get_version(offer->resource) <
       WL_DATA_OFFER_ACTION_SINCE_VERSION)
     {
        data_source_notify_finish(offer->source);
     }
   else if (offer->source->resource &&
            wl_resource_get_version(offer->source->resource) >=
            WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION)
     {
        wl_data_source_send_cancelled(offer->source->resource);
     }

   offer->source->offer = NULL;
out:
   free(offer);
}

/* called by emission of source->destroy_signal */
static void
_e_comp_wl_data_offer_cb_source_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Comp_Wl_Data_Offer *offer;

   DBG("Data Offer Source Destroy");
   if (!listener) return;
   offer = container_of(listener, E_Comp_Wl_Data_Offer,
                        source_destroy_listener);

   offer->source = NULL;
}

static const struct wl_data_offer_interface _e_data_offer_interface =
{
   _e_comp_wl_data_offer_cb_accept,
   _e_comp_wl_data_offer_cb_receive,
   _e_comp_wl_data_offer_cb_destroy,
   data_offer_finish,
   data_offer_set_actions,
};

static void
_e_comp_wl_data_source_cb_offer(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *mime_type)
{
   E_Comp_Wl_Data_Source *source;

   DBG("Data Source Offer");
   if (!(source = wl_resource_get_user_data(resource)))
     return;

   if (!source->mime_types)
     source->mime_types = eina_array_new(1);
   eina_array_push(source->mime_types, eina_stringshare_add(mime_type));
}

/* called by wl_data_source_destroy */
static void
_e_comp_wl_data_source_cb_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   DBG("Data Source Destroy");
   wl_resource_destroy(resource);
}

static void
data_source_set_actions(struct wl_client *client,
                        struct wl_resource *resource,
                        uint32_t dnd_actions)
{
   E_Comp_Wl_Data_Source *source =
     wl_resource_get_user_data(resource);

   if (source->actions_set)
     {
        wl_resource_post_error(source->resource,
                               WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
                               "cannot set actions more than once");
        return;
     }

   if (dnd_actions & ~ALL_ACTIONS)
     {
        wl_resource_post_error(source->resource,
                               WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
                               "invalid action mask %x", dnd_actions);
        return;
     }
/* FIXME
        if (source->seat) {
                wl_resource_post_error(source->resource,
                                       WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
                                       "invalid action change after "
                                       "wl_data_device.start_drag");
                return;
        }
 */
   source->dnd_actions = dnd_actions;
   source->actions_set = 1;
}

/* called by wl_resource_destroy */
static void
_e_comp_wl_data_source_cb_resource_destroy(struct wl_resource *resource)
{
   E_Comp_Wl_Data_Source *source;

   if (!(source = wl_resource_get_user_data(resource)))
     return;

   wl_signal_emit(&source->destroy_signal, source);

   _mime_types_free(source);

   if (e_comp_wl->drag_source == source)
     {
        //free the drag here
        e_object_del(E_OBJECT(e_comp_wl->drag));
        e_comp_wl->drag = NULL;
     }

   free(source);
}

static void
_e_comp_wl_data_source_target_send(E_Comp_Wl_Data_Source *source, uint32_t serial EINA_UNUSED, const char *mime_type)
{
   DBG("Data Source Target Send");
   wl_data_source_send_target(source->resource, mime_type);
}

static void
_e_comp_wl_data_source_send_send(E_Comp_Wl_Data_Source *source, const char *mime_type, int32_t fd)
{
   DBG("Data Source Source Send");
   wl_data_source_send_send(source->resource, mime_type, fd);
   close(fd);
}

static void
_e_comp_wl_data_source_cancelled_send(E_Comp_Wl_Data_Source *source)
{
   DBG("Data Source Cancelled Send");
   wl_data_source_send_cancelled(source->resource);
}

static const struct wl_data_source_interface _e_data_source_interface =
{
   _e_comp_wl_data_source_cb_offer,
   _e_comp_wl_data_source_cb_destroy,
   data_source_set_actions,
};

static void
_e_comp_wl_data_device_destroy_selection_data_source(struct wl_listener *listener EINA_UNUSED, void *data)
{
   E_Comp_Wl_Data_Source *source;
   struct wl_resource *data_device_res = NULL, *focus = NULL;

   DBG("Data Device Destroy Selection Source");
   if (!(source = (E_Comp_Wl_Data_Source *)data))
     return;

   e_comp_wl->selection.data_source = NULL;

   if (e_comp_wl->kbd.enabled)
     focus = e_comp_wl->kbd.focus;

   if (focus)
     {
        if (source->resource)
          data_device_res =
            e_comp_wl_data_find_for_client(wl_resource_get_client(source->resource));

        if (data_device_res)
          wl_data_device_send_selection(data_device_res, NULL);
     }

   wl_signal_emit(&e_comp_wl->selection.signal, e_comp->wl_comp_data);
}

static struct wl_resource *
_e_comp_wl_data_device_data_offer_create(E_Comp_Wl_Data_Source *source, struct wl_resource *data_device)
{
   E_Comp_Wl_Data_Offer *offer;
   Eina_Iterator *it;
   char *t;

   DBG("Data Offer Create");

   offer = E_NEW(E_Comp_Wl_Data_Offer, 1);
   if (!offer) return NULL;

   offer->resource =
     wl_resource_create(wl_resource_get_client(data_device),
                        &wl_data_offer_interface, wl_resource_get_version(data_device), 0);
   if (!offer->resource)
     {
        free(offer);
        return NULL;
     }

   wl_resource_set_implementation(offer->resource,
                                  &_e_data_offer_interface, offer,
                                  _e_comp_wl_data_offer_cb_resource_destroy);
   offer->source = source;
   source->offer = offer;
   offer->source_destroy_listener.notify =
     _e_comp_wl_data_offer_cb_source_destroy;
   wl_signal_add(&source->destroy_signal, &offer->source_destroy_listener);

   wl_data_device_send_data_offer(data_device, offer->resource);

   it = eina_array_iterator_new(source->mime_types);
   EINA_ITERATOR_FOREACH(it, t)
     wl_data_offer_send_offer(offer->resource, t);
   eina_iterator_free(it);

   return offer->resource;
}

static void
_e_comp_wl_data_device_selection_set(void *data EINA_UNUSED, E_Comp_Wl_Data_Source *source, uint32_t serial)
{
   E_Comp_Wl_Data_Source *sel_source;
   struct wl_resource *offer_res, *data_device_res, *focus = NULL;

   sel_source = (E_Comp_Wl_Data_Source *)e_comp_wl->selection.data_source;
   if (sel_source && (e_comp_wl->selection.serial - serial < UINT32_MAX / 2))
     {
        if ((source) && (!serial))
          {
             /* drm canvas will always have serial 0 */
             pid_t pid;

             wl_client_get_credentials(wl_resource_get_client(source->resource), &pid, NULL, NULL);
             if (pid != getpid()) return;
          }
        else return;
     }

   if (sel_source)
     {
        if (!e_comp_wl->clipboard.xwl_owner)
          wl_list_remove(&e_comp_wl->selection.data_source_listener.link);
        if (sel_source->cancelled)
          sel_source->cancelled(sel_source);
        e_comp_wl->selection.data_source = NULL;
     }

   e_comp_wl->selection.data_source = sel_source = source;
   e_comp_wl->clipboard.xwl_owner = 0;
   e_comp_wl->selection.serial = serial;
   if (source) source->serial = serial;

   if (e_comp_wl->kbd.enabled)
     focus = e_comp_wl->kbd.focus;

   if (focus)
     {
        data_device_res =
          e_comp_wl_data_find_for_client(wl_resource_get_client(focus));
        if ((data_device_res) && (source))
          {
             offer_res =
               _e_comp_wl_data_device_data_offer_create(source,
                                                        data_device_res);
             wl_data_device_send_selection(data_device_res, offer_res);
          }
        else if (data_device_res)
          wl_data_device_send_selection(data_device_res, NULL);
     }

   wl_signal_emit(&e_comp_wl->selection.signal, e_comp->wl_comp_data);

   if (source)
     {
        e_comp_wl->selection.data_source_listener.notify =
          _e_comp_wl_data_device_destroy_selection_data_source;
        wl_signal_add(&source->destroy_signal,
                      &e_comp_wl->selection.data_source_listener);
     }
}

static void
_e_comp_wl_data_device_drag_finished(E_Drag *drag, int dropped)
{
   struct wl_resource *res = NULL;
   Evas_Object *o, *z;
   E_Comp_Wl_Data_Source *data_source = e_comp_wl->drag_source;

   o = edje_object_part_swallow_get(drag->comp_object, "e.swallow.content");
   if (eina_streq(evas_object_type_get(o), "e_comp_object"))
     edje_object_part_unswallow(drag->comp_object, o);
   else
     {
        z = o;
        o = e_zoomap_child_get(z);
        e_zoomap_child_set(z, NULL);
     }
   evas_object_hide(o);
   evas_object_pass_events_set(o, 1);
   if (e_comp_wl->drag != drag) return;
   e_comp_wl->drag = NULL;
   e_comp_wl->drag_client = NULL;
   e_screensaver_inhibit_toggle(0);
   if (dropped) return;
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_wl->selection.target && e_client_has_xwindow(e_comp_wl->selection.target))
     {
        ecore_x_client_message32_send(e_client_util_win_get(e_comp_wl->selection.target),
                                      ECORE_X_ATOM_XDND_DROP,
                                      ECORE_X_EVENT_MASK_NONE,
                                      e_comp->cm_selection, 0,
                                      ecore_x_current_time_get(), 0, 0);
        return;
     }
#endif

   if (e_comp_wl->selection.target)
     res = e_comp_wl_data_find_for_client(wl_resource_get_client(e_comp_wl->selection.target->comp_data->surface));
   if (res && data_source->accepted && data_source->current_dnd_action)
     {
        wl_data_device_send_drop(res);
        if (wl_resource_get_version(data_source->resource) >=
            WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION)
          wl_data_source_send_dnd_drop_performed(data_source->resource);

        data_source->offer->in_ask = data_source->current_dnd_action ==
          WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
     }
   else if (wl_resource_get_version(data_source->resource) >=
                 WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION)
     wl_data_source_send_cancelled(data_source->resource);
   if (res) wl_data_device_send_leave(res);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_xwayland())
     {
        ecore_x_selection_owner_set(0, ECORE_X_ATOM_SELECTION_XDND,
                                    ecore_x_current_time_get());
        ecore_x_window_hide(e_comp->cm_selection);
     }
#endif
   e_comp_wl->selection.target = NULL;
   e_comp_wl->drag_source = NULL;
}

static void
_e_comp_wl_data_device_drag_key(E_Drag *drag EINA_UNUSED, Ecore_Event_Key *ev)
{
   uint32_t compositor_action = 0;
   const Evas_Modifier *m;
   E_Comp_Wl_Data_Source *drag_source = e_comp_wl->drag_source;

   m = evas_key_modifier_get(e_comp->evas);
   if (evas_key_modifier_is_set(m, "Shift"))
     compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
   else if (evas_key_modifier_is_set(m, "Control"))
     compositor_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
   if (drag_source->compositor_action == compositor_action) return;
   drag_source->compositor_action = compositor_action;
   if (drag_source->offer)
     data_offer_update_action(drag_source->offer);
}

static void
_e_comp_wl_data_device_cb_drag_start(struct wl_client *client, struct wl_resource *resource EINA_UNUSED, struct wl_resource *source_resource, struct wl_resource *origin_resource, struct wl_resource *icon_resource, uint32_t serial)
{
   E_Comp_Wl_Data_Source *source;
   Eina_List *l;
   struct wl_resource *res;
   E_Client *ec = NULL;
   int x, y;

   DBG("Data Device Drag Start");

   if ((e_comp_wl->kbd.focus) && (e_comp_wl->kbd.focus != origin_resource))
     return;

   if (!(source = wl_resource_get_user_data(source_resource))) return;
   e_comp_wl->drag_source = source;

   if (icon_resource)
     {
        DBG("\tHave Icon Resource: %p", icon_resource);
        ec = wl_resource_get_user_data(icon_resource);
        if (!ec->re_manage)
          {
             ec->re_manage = 1;

             ec->lock_focus_out = ec->override = 1;
             ec->icccm.title = eina_stringshare_add("noshadow");
             ec->layer = E_LAYER_CLIENT_DRAG;
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_DRAG);
             e_client_focus_stack_set(eina_list_remove(e_client_focus_stack_get(), ec));
             EC_CHANGED(ec);
             e_comp_wl->drag_client = ec;
          }
        if (ec->comp_data->pending.input)
          eina_tiler_clear(ec->comp_data->pending.input);
        else
          {
             ec->comp_data->pending.input = eina_tiler_new(65535, 65535);
             eina_tiler_tile_size_set(ec->comp_data->pending.input, 1, 1);
          }
     }

   EINA_LIST_FOREACH(e_comp_wl->ptr.resources, l, res)
     {
        if (!e_comp_wl_input_pointer_check(res)) continue;
        if (wl_resource_get_client(res) != client) continue;
        wl_pointer_send_leave(res, serial, e_comp_wl->kbd.focus);
     }

   evas_pointer_canvas_xy_get(e_comp->evas, &x, &y);
   e_comp_wl->drag = e_drag_new(x, y, NULL, 0, NULL, 0, NULL,
                                _e_comp_wl_data_device_drag_finished);
   e_drag_key_down_cb_set(e_comp_wl->drag, _e_comp_wl_data_device_drag_key);
   e_drag_key_up_cb_set(e_comp_wl->drag, _e_comp_wl_data_device_drag_key);
   e_comp_wl->drag->button_mask =
     evas_pointer_button_down_mask_get(e_comp->evas);
   if (ec)
     e_drag_object_set(e_comp_wl->drag, ec->frame);
   e_drag_start(e_comp_wl->drag, x, y);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_xwayland())
     {
        ecore_x_window_show(e_comp->cm_selection);
        ecore_x_selection_owner_set(e_comp->cm_selection,
                                    ECORE_X_ATOM_SELECTION_XDND,
                                    ecore_x_current_time_get());
     }
#endif
   if (e_comp_wl->ptr.ec)
     e_comp_wl_data_device_send_enter(e_comp_wl->ptr.ec);
   e_screensaver_inhibit_toggle(1);
   e_comp_canvas_feed_mouse_up(0);
}

static void
_e_comp_wl_data_device_cb_selection_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, struct wl_resource *source_resource, uint32_t serial)
{
   E_Comp_Wl_Data_Source *source;

   DBG("Data Device Selection Set");
   if (!source_resource) return;
   if (!(source = wl_resource_get_user_data(source_resource))) return;
   if (source->actions_set)
     {
        wl_resource_post_error(source_resource,
                               WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
                               "cannot set drag-and-drop source as selection");
        return;
     }

   _e_comp_wl_data_device_selection_set(e_comp->wl_comp_data, source, serial);
}

static void
_e_comp_wl_data_device_cb_release(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   DBG("Data Device Release");
   wl_resource_destroy(resource);
}

static const struct wl_data_device_interface _e_data_device_interface =
{
   _e_comp_wl_data_device_cb_drag_start,
   _e_comp_wl_data_device_cb_selection_set,
   _e_comp_wl_data_device_cb_release
};

static void
_e_comp_wl_data_device_cb_unbind(struct wl_resource *resource)
{
   struct wl_client *wc = wl_resource_get_client(resource);
   eina_hash_del_by_key(e_comp_wl->mgr.data_resources, &wc);
}

static void
_e_comp_wl_data_manager_cb_device_get(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *seat_resource EINA_UNUSED)
{
   struct wl_resource *res;

   DBG("Data Manager Device Get");

   /* try to create the data device resource */
   res = wl_resource_create(client, &wl_data_device_interface, wl_resource_get_version(manager_resource), id);
   if (!res)
     {
        ERR("Could not create data device resource");
        wl_resource_post_no_memory(manager_resource);
        return;
     }

   eina_hash_add(e_comp_wl->mgr.data_resources, &client, res);
   wl_resource_set_implementation(res, &_e_data_device_interface,
                                  e_comp->wl_comp_data,
                                  _e_comp_wl_data_device_cb_unbind);
}

static const struct wl_data_device_manager_interface _e_manager_interface =
{
   (void *)e_comp_wl_data_manager_source_create,
   _e_comp_wl_data_manager_cb_device_get
};

/* static void  */
/* _e_comp_wl_data_cb_unbind_manager(struct wl_resource *resource) */
/* { */
/*    E_Comp_Data *e_comp->wl_comp_data; */

/*    DBG("Comp_Wl_Data: Unbind Manager"); */

/*    if (!(e_comp->wl_comp_data = wl_resource_get_user_data(resource))) return; */

/*    e_comp_wl->mgr.resource = NULL; */
/* } */

static void
_e_comp_wl_data_cb_bind_manager(struct wl_client *client, void *data EINA_UNUSED, uint32_t version EINA_UNUSED, uint32_t id)
{
   struct wl_resource *res;
   pid_t pid;

   /* try to create data manager resource */
   res = wl_resource_create(client, &wl_data_device_manager_interface, 3, id);
   if (!res)
     {
        ERR("Could not create data device manager");
        wl_client_post_no_memory(client);
        return;
     }
   wl_client_get_credentials(client, &pid, NULL, NULL);
   if (pid == getpid())
     e_comp_wl->mgr.resource = res;

   wl_resource_set_implementation(res, &_e_manager_interface,
                                  e_comp->wl_comp_data, NULL);
}

static Eina_Bool
_e_comp_wl_clipboard_offer_load(void *data, Ecore_Fd_Handler *handler)
{
   E_Comp_Wl_Clipboard_Offer *offer;
   char *p;
   size_t size;
   int len;
   int fd;

   if (!(offer = (E_Comp_Wl_Clipboard_Offer *)data))
     return ECORE_CALLBACK_CANCEL;

   fd = ecore_main_fd_handler_fd_get(handler);

   size = offer->source->contents.size;
   p = (char *)offer->source->contents.data;
   len = write(fd, p + offer->offset, size - offer->offset);
   if (len > 0) offer->offset += len;

   if ((offer->offset == size) || (len <= 0))
     {
        close(fd);
        ecore_main_fd_handler_del(handler);
        e_comp_wl_clipboard_source_unref(offer->source);
        free(offer);
     }

   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_wl_clipboard_offer_create(E_Comp_Wl_Clipboard_Source *source, int fd)
{
   E_Comp_Wl_Clipboard_Offer *offer;

   offer = E_NEW(E_Comp_Wl_Clipboard_Offer, 1);

   offer->offset = 0;
   offer->source = source;
   source->ref++;
   offer->fd_handler =
     ecore_main_fd_handler_add(fd, ECORE_FD_WRITE,
                               _e_comp_wl_clipboard_offer_load, offer,
                               NULL, NULL);
}

static Eina_Bool
_e_comp_wl_clipboard_source_save(void *data EINA_UNUSED, Ecore_Fd_Handler *handler)
{
   E_Comp_Wl_Clipboard_Source *source;
   char *p;
   int len, size;

   if (!(source = (E_Comp_Wl_Clipboard_Source *)e_comp_wl->clipboard.source))
     return ECORE_CALLBACK_CANCEL;

   /* extend contents buffer */
   if ((source->contents.alloc - source->contents.size) < CLIPBOARD_CHUNK)
     {
        wl_array_add(&source->contents, CLIPBOARD_CHUNK);
        source->contents.size -= CLIPBOARD_CHUNK;
     }

   p = (char *)source->contents.data + source->contents.size;
   size = source->contents.alloc - source->contents.size;
   len = read(source->fd, p, size);

   if (len == 0)
     {
        ecore_main_fd_handler_del(handler);
        close(source->fd);
        source->fd_handler = NULL;
     }
   else if (len < 0)
     {
        e_comp_wl_clipboard_source_unref(source);
        e_comp_wl->clipboard.source = NULL;
     }
   else
     {
        source->contents.size += len;
     }

   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_wl_clipboard_source_target_send(E_Comp_Wl_Data_Source *source EINA_UNUSED, uint32_t serial EINA_UNUSED, const char *mime_type EINA_UNUSED)
{
}

static void
_e_comp_wl_clipboard_source_send_send(E_Comp_Wl_Data_Source *source, const char *mime_type, int fd)
{
   E_Comp_Wl_Clipboard_Source *clip_source;
   char *t;

   clip_source = container_of(source, E_Comp_Wl_Clipboard_Source, data_source);
   if (!clip_source) return;

   t = eina_array_data_get(source->mime_types, 0);
   if (!strcmp(mime_type, t))
     _e_comp_wl_clipboard_offer_create(clip_source, fd);
   else
     close(fd);
}

static void
_e_comp_wl_clipboard_source_cancelled_send(E_Comp_Wl_Data_Source *source EINA_UNUSED)
{
}

static void
_e_comp_wl_clipboard_selection_set(struct wl_listener *listener EINA_UNUSED, void *data EINA_UNUSED)
{
   E_Comp_Wl_Data_Source *sel_source;
   E_Comp_Wl_Clipboard_Source *clip_source;
   int p[2];
   char *mime_type;

   sel_source = (E_Comp_Wl_Data_Source *)e_comp_wl->selection.data_source;
   clip_source = (E_Comp_Wl_Clipboard_Source *)e_comp_wl->clipboard.source;

   if (!sel_source)
     {
        if (clip_source)
          _e_comp_wl_data_device_selection_set(e_comp->wl_comp_data,
                                               &clip_source->data_source,
                                               clip_source->serial);
        return;
     }
   else if (sel_source->target == _e_comp_wl_clipboard_source_target_send)
     return;

   if (clip_source)
     e_comp_wl_clipboard_source_unref(clip_source);

   e_comp_wl->clipboard.source = NULL;
   mime_type = eina_array_data_get(sel_source->mime_types, 0);

   if (pipe2(p, O_CLOEXEC) == -1)
     return;

   sel_source->send(sel_source, mime_type, p[1]);

   e_comp_wl->clipboard.source =
     e_comp_wl_clipboard_source_create(mime_type,
                                       e_comp_wl->selection.serial, p[0]);

   if (!e_comp_wl->clipboard.source)
     close(p[0]);
}

static void
_e_comp_wl_clipboard_create(void)
{
   e_comp_wl->clipboard.listener.notify = _e_comp_wl_clipboard_selection_set;
   wl_signal_add(&e_comp_wl->selection.signal, &e_comp_wl->clipboard.listener);
}

E_API void
e_comp_wl_data_device_send_enter(E_Client *ec)
{
   struct wl_resource *data_device_res = NULL, *offer_res = NULL;
   uint32_t serial;
   int x, y;

   if (e_client_has_xwindow(ec) &&
       e_client_has_xwindow(e_comp_wl->drag_client))
     return;
   if (e_comp_wl->drag && (e_comp_wl->drag->object == ec->frame)) return;
   if (!e_client_has_xwindow(ec))
     {
        E_Comp_Wl_Data_Source *drag_source = e_comp_wl->drag_source;
        data_device_res =
          e_comp_wl_data_find_for_client(wl_resource_get_client(ec->comp_data->surface));
        if (!data_device_res) return;
        offer_res = e_comp_wl_data_device_send_offer(ec);
        if (e_comp_wl->drag_source && (!offer_res)) return;
        if (e_client_has_xwindow(e_comp_wl->drag_client))
          {
             drag_source->offer->dnd_actions = drag_source->dnd_actions;
             drag_source->offer->preferred_dnd_action = drag_source->current_dnd_action;
          }
        data_offer_update_action(drag_source->offer);
        if (offer_res)
          {
             if (wl_resource_get_version(offer_res) >= WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION)
               wl_data_offer_send_source_actions(offer_res, drag_source->dnd_actions);
          }
     }
   e_comp_wl->selection.target = ec;

#ifndef HAVE_WAYLAND_ONLY
   if (e_client_has_xwindow(ec))
     {
        int d1 = 0x5UL, d2, d3, d4;
        E_Comp_Wl_Data_Source *source;

        d2 = d3 = d4 = 0;
        source = e_comp_wl->drag_source;

        if ((source->mime_types) && (eina_array_count(source->mime_types) > 3))
          {
             const char *type, *types[eina_array_count(source->mime_types)];
             int i = 0;
             Eina_Iterator *it;

             d1 |= 0x1UL;
             it = eina_array_iterator_new(source->mime_types);
             EINA_ITERATOR_FOREACH(it, type)
               types[i++] = type;
             eina_iterator_free(it);
             ecore_x_dnd_types_set(e_comp->cm_selection, types, i);
          }
        else if (source->mime_types)
          {
             if (eina_array_count(source->mime_types))
               d2 = ecore_x_atom_get(eina_array_data_get(source->mime_types, 0));
             if (eina_array_count(source->mime_types) > 1)
               d3 = ecore_x_atom_get(eina_array_data_get(source->mime_types, 1));
             if (eina_array_count(source->mime_types) > 2)
               d4 = ecore_x_atom_get(eina_array_data_get(source->mime_types, 2));
          }

        ecore_x_client_message32_send(e_client_util_win_get(ec),
                                      ECORE_X_ATOM_XDND_ENTER,
                                      ECORE_X_EVENT_MASK_NONE,
                                      e_comp->cm_selection, d1, d2, d3, d4);

        return;
     }
#endif
   x = e_comp_wl->ptr.x - e_comp_wl->selection.target->client.x;
   y = e_comp_wl->ptr.y - e_comp_wl->selection.target->client.y;
   serial = wl_display_next_serial(e_comp_wl->wl.disp);
   wl_data_device_send_enter(data_device_res, serial, ec->comp_data->surface,
                             wl_fixed_from_int(x), wl_fixed_from_int(y),
                             offer_res);
}

E_API void
e_comp_wl_data_device_send_leave(E_Client *ec)
{
   struct wl_resource *res;

   if (e_client_has_xwindow(ec) &&
       e_client_has_xwindow(e_comp_wl->drag_client))
     return;
   if (e_comp_wl->drag && (e_comp_wl->drag->object == ec->frame)) return;
   if (e_comp_wl->selection.target == ec)
     e_comp_wl->selection.target = NULL;
#ifndef HAVE_WAYLAND_ONLY
   if (e_client_has_xwindow(ec))
     {
        ecore_x_client_message32_send(e_client_util_win_get(ec),
                                      ECORE_X_ATOM_XDND_LEAVE,
                                      ECORE_X_EVENT_MASK_NONE,
                                      e_comp->cm_selection, 0, 0, 0, 0);
        return;
     }
#endif
   {
      E_Comp_Wl_Data_Source *drag_source = e_comp_wl->drag_source;
      if (drag_source &&
          drag_source->offer)
        {
           E_Comp_Wl_Data_Offer *offer;
           /* Unlink the offer from the source */
           offer = drag_source->offer;
           offer->source = NULL;
           drag_source->offer = NULL;
           drag_source->accepted = 0;
           wl_list_remove(&offer->source_destroy_listener.link);
        }
   }
   res = e_comp_wl_data_find_for_client(wl_resource_get_client(ec->comp_data->surface));
   if (res)
     wl_data_device_send_leave(res);
}

EINTERN void *
e_comp_wl_data_device_send_offer(E_Client *ec)
{
   struct wl_resource *data_device_res, *offer_res = NULL;
   E_Comp_Wl_Data_Source *source;

   data_device_res =
     e_comp_wl_data_find_for_client(wl_resource_get_client(ec->comp_data->surface));
   if (!data_device_res) return NULL;
   source = e_comp_wl->drag_source;
   if (source)
     {
        offer_res =
          _e_comp_wl_data_device_data_offer_create(source, data_device_res);
     }

   return offer_res;
}

E_API void
e_comp_wl_data_device_keyboard_focus_set(void)
{
   struct wl_resource *data_device_res, *offer_res = NULL, *focus;
   E_Comp_Wl_Data_Source *source;
   E_Client *focused;

   if (!e_comp_wl->kbd.enabled)
     {
        ERR("Keyboard not enabled");
        return;
     }

   if (!(focus = e_comp_wl->kbd.focus))
     {
        ERR("No focused resource");
        return;
     }
   focused = wl_resource_get_user_data(focus);
   source = (E_Comp_Wl_Data_Source *)e_comp_wl->selection.data_source;

#ifndef HAVE_WAYLAND_ONLY
   do
     {
        if (!e_comp_util_has_xwayland()) break;
        if (e_comp_wl->clipboard.xwl_owner)
          {
             if (e_client_has_xwindow(focused)) return;
             break;
          }
        else if (source && e_client_has_xwindow(focused))
          {
             /* wl -> x11 */
             ecore_x_selection_owner_set(e_comp->cm_selection,
                                         ECORE_X_ATOM_SELECTION_CLIPBOARD,
                                         ecore_x_current_time_get());
             return;
          }
     } while (0);
#endif
   data_device_res =
     e_comp_wl_data_find_for_client(wl_resource_get_client(focus));
   if (!data_device_res) return;

   if (source)
     {
        offer_res =
          _e_comp_wl_data_device_data_offer_create(source, data_device_res);
     }

   wl_data_device_send_selection(data_device_res, offer_res);
}

EINTERN Eina_Bool
e_comp_wl_data_manager_init(void)
{
   /* try to create global data manager */
   e_comp_wl->mgr.global =
     wl_global_create(e_comp_wl->wl.disp, &wl_data_device_manager_interface, 3,
                      NULL, _e_comp_wl_data_cb_bind_manager);
   if (!e_comp_wl->mgr.global)
     {
        ERR("Could not create global for data device manager");
        return EINA_FALSE;
     }

   wl_signal_init(&e_comp_wl->selection.signal);

   /* create clipboard */
   _e_comp_wl_clipboard_create();
   e_comp_wl->mgr.data_resources = eina_hash_pointer_new(NULL);

   return EINA_TRUE;
}

EINTERN void
e_comp_wl_data_manager_shutdown(void)
{
   /* destroy the global manager resource */
   /* if (e_comp_wl->mgr.global) wl_global_destroy(e_comp_wl->mgr.global); */

   wl_list_remove(&e_comp_wl->clipboard.listener.link);
   E_FREE_FUNC(e_comp_wl->mgr.data_resources, eina_hash_free);
}

E_API struct wl_resource *
e_comp_wl_data_find_for_client(struct wl_client *client)
{
   return eina_hash_find(e_comp_wl->mgr.data_resources, &client);
}

E_API E_Comp_Wl_Data_Source *
e_comp_wl_data_manager_source_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   E_Comp_Wl_Data_Source *source;

   DBG("Data Manager Source Create");

   source = E_NEW(E_Comp_Wl_Data_Source, 1);
   if (!source)
     {
        wl_resource_post_no_memory(resource);
        return NULL;
     }

   wl_signal_init(&source->destroy_signal);
   source->target = _e_comp_wl_data_source_target_send;
   source->send = _e_comp_wl_data_source_send_send;
   source->cancelled = _e_comp_wl_data_source_cancelled_send;

   source->resource =
     wl_resource_create(client, &wl_data_source_interface, wl_resource_get_version(resource), id);
   if (!source->resource)
     {
        ERR("Could not create data source resource");
        free(source);
        wl_resource_post_no_memory(resource);
        return NULL;
     }

   wl_resource_set_implementation(source->resource,
                                  &_e_data_source_interface, source,
                                  _e_comp_wl_data_source_cb_resource_destroy);
   return source;
}

E_API E_Comp_Wl_Clipboard_Source *
e_comp_wl_clipboard_source_create(const char *mime_type, uint32_t serial, int fd)
{
   E_Comp_Wl_Clipboard_Source *source;

   source = E_NEW(E_Comp_Wl_Clipboard_Source, 1);
   if (!source) return NULL;

   source->data_source.resource = NULL;
   source->data_source.target = _e_comp_wl_clipboard_source_target_send;
   source->data_source.send = _e_comp_wl_clipboard_source_send_send;
   source->data_source.cancelled = _e_comp_wl_clipboard_source_cancelled_send;

   wl_array_init(&source->contents);
   wl_signal_init(&source->data_source.destroy_signal);

   source->ref = 1;
   source->serial = serial;

   if (mime_type)
     {
        if (!source->data_source.mime_types)
          source->data_source.mime_types = eina_array_new(1);
        eina_array_push(source->data_source.mime_types,
                        eina_stringshare_add(mime_type));
     }

   if (fd > 0)
     {
        source->fd_handler =
          ecore_main_fd_handler_file_add(fd, ECORE_FD_READ | ECORE_FD_ERROR,
                                         _e_comp_wl_clipboard_source_save,
                                         e_comp->wl_comp_data, NULL, NULL);
        if (!source->fd_handler)
          {
             _mime_types_free(&source->data_source);
             free(source);
             return NULL;
          }
     }

   source->fd = fd;

   return source;
}

E_API void
e_comp_wl_clipboard_source_unref(E_Comp_Wl_Clipboard_Source *source)
{
   EINA_SAFETY_ON_NULL_RETURN(source);
   source->ref--;
   if (source->ref > 0) return;

   if (source->fd_handler)
     {
        ecore_main_fd_handler_del(source->fd_handler);
        close(source->fd);
     }

   _mime_types_free(&source->data_source);
   if (source == e_comp_wl->clipboard.source)
     e_comp_wl->clipboard.source = NULL;
   if (&source->data_source == e_comp_wl->selection.data_source)
     e_comp_wl->selection.data_source = NULL;

   wl_signal_emit(&source->data_source.destroy_signal, &source->data_source);
   wl_array_release(&source->contents);
   free(source);
}

/* Shamelessly stolen from weston and modified, original license boiler plate
 * follows.
 */

#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif


/*
 * Copyright © 2014, 2015 Collabora, Ltd.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holders not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  The copyright holders make
 * no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#define E_COMP_WL
#include "e.h"

#include <sys/mman.h>

#include <assert.h>

#include "linux-dmabuf-unstable-v1-server-protocol.h"

#ifndef DRM_FORMAT_MOD_LINEAR
# define DRM_FORMAT_MOD_LINEAR 0
#endif

void
linux_dmabuf_buffer_ref(struct linux_dmabuf_buffer *buffer)
{
   buffer->refs++;
}

void
linux_dmabuf_buffer_unref(struct linux_dmabuf_buffer *buffer)
{
   int i;

   buffer->refs--;
   if (buffer->refs > 0) return;

   for (i = 0; i < buffer->attributes.n_planes; i++)
     {
        close(buffer->attributes.fd[i]);
        buffer->attributes.fd[i] = -1;
     }

   buffer->attributes.n_planes = 0;
   free(buffer);
}

static void
linux_dmabuf_buffer_destroy(struct linux_dmabuf_buffer *buffer)
{
   linux_dmabuf_buffer_unref(buffer);
}

static void
destroy_params(struct wl_resource *params_resource)
{
   struct linux_dmabuf_buffer *buffer;

   buffer = wl_resource_get_user_data(params_resource);

   if (!buffer) return;

   linux_dmabuf_buffer_destroy(buffer);
}

static void
params_destroy(struct wl_client *client, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
params_add(struct wl_client *client, struct wl_resource *params_resource,
	   int32_t name_fd, uint32_t plane_idx, uint32_t offset,
	   uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo)
{
   struct linux_dmabuf_buffer *buffer;

   buffer = wl_resource_get_user_data(params_resource);
   if (!buffer)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
                               "params was already used to create a wl_buffer");
        close(name_fd);
        return;
     }

   assert(buffer->params_resource == params_resource);
   assert(!buffer->buffer_resource);

   if (plane_idx >= MAX_DMABUF_PLANES)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
                               "plane index %u is too high", plane_idx);
        close(name_fd);
        return;
     }

   if (buffer->attributes.fd[plane_idx] != -1)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
                               "a dmabuf has already been added for plane %u",
                               plane_idx);
        close(name_fd);
        return;
     }

   buffer->attributes.fd[plane_idx] = name_fd;
   buffer->attributes.offset[plane_idx] = offset;
   buffer->attributes.stride[plane_idx] = stride;
   if (wl_resource_get_version(params_resource) < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION)
     buffer->attributes.modifier[plane_idx] = DRM_FORMAT_MOD_INVALID;
   else
     {
        buffer->attributes.modifier[plane_idx] =
          ((uint64_t)modifier_hi << 32) | modifier_lo;
     }

   buffer->attributes.n_planes++;
}

static void
linux_dmabuf_wl_buffer_destroy(struct wl_client *client, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static const struct wl_buffer_interface linux_dmabuf_buffer_implementation =
{
   linux_dmabuf_wl_buffer_destroy
};

static void
destroy_linux_dmabuf_wl_buffer(struct wl_resource *resource)
{
   struct linux_dmabuf_buffer *buffer;

   buffer = wl_resource_get_user_data(resource);
   assert(buffer->buffer_resource == resource);
   assert(!buffer->params_resource);

   if (buffer->user_data_destroy_func)
     buffer->user_data_destroy_func(buffer);

   linux_dmabuf_buffer_destroy(buffer);
}

static void
params_create_common(struct wl_client *client, struct wl_resource *params_resource,
		     uint32_t buffer_id, int32_t width, int32_t height,
		     uint32_t format, uint32_t flags)
{
   struct linux_dmabuf_buffer *buffer;
   int i;

   buffer = wl_resource_get_user_data(params_resource);

   if (!buffer)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
                               "params was already used to create a wl_buffer");
        return;
     }

   assert(buffer->params_resource == params_resource);
   assert(!buffer->buffer_resource);

   /* Switch the linux_dmabuf_buffer object from params resource to
    * eventually wl_buffer resource.
    */
   wl_resource_set_user_data(buffer->params_resource, NULL);
   buffer->params_resource = NULL;

   if (!buffer->attributes.n_planes)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
                               "no dmabuf has been added to the params");
        goto err_out;
     }

   /* Check for holes in the dmabufs set (e.g. [0, 1, 3]) */
   for (i = 0; i < buffer->attributes.n_planes; i++)
     {
        if (buffer->attributes.fd[i] == -1)
          {
             wl_resource_post_error(params_resource,
                                    ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
                                    "no dmabuf has been added for plane %i", i);
             goto err_out;
          }
     }

   buffer->attributes.version = 1;
   buffer->attributes.width = width;
   buffer->attributes.height = height;
   buffer->attributes.format = format;
   buffer->attributes.flags = flags;

   if (width < 1 || height < 1)
     {
        wl_resource_post_error(params_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
                               "invalid width %d or height %d", width, height);
        goto err_out;
     }

   for (i = 0; i < buffer->attributes.n_planes; i++)
     {
        off_t size;

        if ((uint64_t) buffer->attributes.offset[i] + buffer->attributes.stride[i] > UINT32_MAX) {
           wl_resource_post_error(params_resource,
                                  ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                  "size overflow for plane %i", i);
           goto err_out;
        }

        if (i == 0 &&
            (uint64_t) buffer->attributes.offset[i] +
            (uint64_t) buffer->attributes.stride[i] * height > UINT32_MAX)
          {
             wl_resource_post_error(params_resource,
                                    ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                    "size overflow for plane %i", i);
             goto err_out;
          }

        /* Don't report an error as it might be caused
         * by the kernel not supporting seeking on dmabuf */
        size = lseek(buffer->attributes.fd[i], 0, SEEK_END);
        if (size == -1) continue;

        if (buffer->attributes.offset[i] >= size)
          {
             wl_resource_post_error(params_resource,
                                    ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                    "invalid offset %i for plane %i",
                                    buffer->attributes.offset[i], i);
             goto err_out;
          }

        if (buffer->attributes.offset[i] + buffer->attributes.stride[i] > size)
          {
             wl_resource_post_error(params_resource,
                                    ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                    "invalid stride %i for plane %i",
                                    buffer->attributes.stride[i], i);
             goto err_out;
          }

        /* Only valid for first plane as other planes might be
         * sub-sampled according to fourcc format */
        if (i == 0 &&
            buffer->attributes.offset[i] + buffer->attributes.stride[i] * height > size)
          {
             wl_resource_post_error(params_resource,
                                    ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                    "invalid buffer stride or height for plane %i", i);
             goto err_out;
          }
     }

   /* XXX: Some additional sanity checks could be done with respect
    * to the fourcc format. A centralized collection (kernel or
    * libdrm) would be useful to avoid code duplication for these
    * checks (e.g. drm_format_num_planes).
    */

   if (!e_pixmap_dmabuf_test(buffer)) goto err_failed;

   buffer->buffer_resource =
     wl_resource_create(client, &wl_buffer_interface, 1, buffer_id);
   if (!buffer->buffer_resource)
     {
        wl_resource_post_no_memory(params_resource);
        goto err_buffer;
     }

   wl_resource_set_implementation(buffer->buffer_resource,
                                  &linux_dmabuf_buffer_implementation,
                                  buffer, destroy_linux_dmabuf_wl_buffer);

   /* send 'created' event when the request is not for an immediate
    * import, ie buffer_id is zero */
   if (buffer_id == 0)
     zwp_linux_buffer_params_v1_send_created(params_resource,
                                             buffer->buffer_resource);

   return;

err_buffer:
   if (buffer->user_data_destroy_func) buffer->user_data_destroy_func(buffer);

err_failed:
   if (buffer_id == 0)
     zwp_linux_buffer_params_v1_send_failed(params_resource);
   else
     /* since the behavior is left implementation defined by the
      * protocol in case of create_immed failure due to an unknown cause,
      * we choose to treat it as a fatal error and immediately kill the
      * client instead of creating an invalid handle and waiting for it
      * to be used.
      */
     wl_resource_post_error(params_resource,
                            ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
                            "importing the supplied dmabufs failed");

err_out:
   linux_dmabuf_buffer_destroy(buffer);
}

static void
params_create(struct wl_client *client, struct wl_resource *params_resource,
	      int32_t width, int32_t height, uint32_t format, uint32_t flags)
{
   params_create_common(client, params_resource, 0, width, height, format, flags);
}

static void
params_create_immed(struct wl_client *client, struct wl_resource *params_resource,
		    uint32_t buffer_id, int32_t width, int32_t height,
		    uint32_t format, uint32_t flags)
{
   params_create_common(client, params_resource, buffer_id, width, height,
                        format, flags);
}

static const struct zwp_linux_buffer_params_v1_interface
zwp_linux_buffer_params_implementation =
{
   params_destroy,
   params_add,
   params_create,
   params_create_immed
};

static void
linux_dmabuf_destroy(struct wl_client *client, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
linux_dmabuf_create_params(struct wl_client *client,
			   struct wl_resource *linux_dmabuf_resource,
			   uint32_t params_id)
{
   void *compositor;
   struct linux_dmabuf_buffer *buffer;
   uint32_t version;
   int i;

   version = wl_resource_get_version(linux_dmabuf_resource);
   compositor = wl_resource_get_user_data(linux_dmabuf_resource);

   buffer = calloc(1, sizeof *buffer);
   if (!buffer) goto err_out;

   buffer->refs = 1;
   for (i = 0; i < MAX_DMABUF_PLANES; i++)
     buffer->attributes.fd[i] = -1;

   buffer->compositor = compositor;
   buffer->params_resource =
     wl_resource_create(client, &zwp_linux_buffer_params_v1_interface,
                        version, params_id);
   if (!buffer->params_resource)
     goto err_dealloc;

   wl_resource_set_implementation(buffer->params_resource,
                                  &zwp_linux_buffer_params_implementation,
                                  buffer, destroy_params);

   return;

err_dealloc:
   free(buffer);

err_out:
   wl_resource_post_no_memory(linux_dmabuf_resource);
}

/** Get the linux_dmabuf_buffer from a wl_buffer resource
 *
 * If the given wl_buffer resource was created through the linux_dmabuf
 * protocol interface, returns the linux_dmabuf_buffer object. This can
 * be used as a type check for a wl_buffer.
 *
 * \param resource A wl_buffer resource.
 * \return The linux_dmabuf_buffer if it exists, or NULL otherwise.
 */
EINTERN struct linux_dmabuf_buffer *
linux_dmabuf_buffer_get(struct wl_resource *resource)
{
   struct linux_dmabuf_buffer *buffer;

   if (!resource) return NULL;

   if (!wl_resource_instance_of(resource, &wl_buffer_interface,
                                &linux_dmabuf_buffer_implementation))
     return NULL;

   buffer = wl_resource_get_user_data(resource);
   assert(buffer);
   assert(!buffer->params_resource);
   assert(buffer->buffer_resource == resource);

   return buffer;
}

/** Set renderer-private data
 *
 * Set the user data for the linux_dmabuf_buffer. It is invalid to overwrite
 * a non-NULL user data with a new non-NULL pointer. This is meant to
 * protect against renderers fighting over linux_dmabuf_buffer user data
 * ownership.
 *
 * The renderer-private data is usually set from the
 * weston_renderer::import_dmabuf hook.
 *
 * \param buffer The linux_dmabuf_buffer object to set for.
 * \param data The new renderer-private data pointer.
 * \param func Destructor function to be called for the renderer-private
 *             data when the linux_dmabuf_buffer gets destroyed.
 *
 * \sa weston_compositor_import_dmabuf
 */
EINTERN void
linux_dmabuf_buffer_set_user_data(struct linux_dmabuf_buffer *buffer, void *data,
				  dmabuf_user_data_destroy_func func)
{
   assert(data == NULL || buffer->user_data == NULL);

   buffer->user_data = data;
   buffer->user_data_destroy_func = func;
}

/** Get renderer-private data
 *
 * Get the user data from the linux_dmabuf_buffer.
 *
 * \param buffer The linux_dmabuf_buffer to query.
 * \return Renderer-private data pointer.
 *
 * \sa linux_dmabuf_buffer_set_user_data
 */
EINTERN void *
linux_dmabuf_buffer_get_user_data(struct linux_dmabuf_buffer *buffer)
{
   return buffer->user_data;
}

static const struct zwp_linux_dmabuf_v1_interface linux_dmabuf_implementation =
{
   linux_dmabuf_destroy,
   linux_dmabuf_create_params
};

static void
bind_linux_dmabuf(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   void *compositor = data;
   struct wl_resource *resource;
   int *formats = NULL;
   uint64_t *modifiers = NULL;
   int num_formats, num_modifiers;
   uint64_t modifier_invalid = DRM_FORMAT_MOD_INVALID;
   int i, j;

   resource =
     wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, version, id);
   if (resource == NULL)
     {
        wl_client_post_no_memory(client);
        return;
     }

   wl_resource_set_implementation(resource, &linux_dmabuf_implementation,
                                  compositor, NULL);

   /*
    * Use EGL_EXT_image_dma_buf_import_modifiers to query and advertise
    * format/modifier codes.
    */
   e_pixmap_dmabuf_formats_query(&formats, &num_formats);

   for (i = 0; i < num_formats; i++)
     {
        e_pixmap_dmabuf_modifiers_query(formats[i], &modifiers, &num_modifiers);

        /* send DRM_FORMAT_MOD_INVALID token when no modifiers are supported
         * for this format */
        if (num_modifiers == 0)
          {
             num_modifiers = 1;
             modifiers = &modifier_invalid;
          }

        for (j = 0; j < num_modifiers; j++)
          {
             if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION)
               {
                  uint32_t modifier_lo = modifiers[j] & 0xFFFFFFFF;
                  uint32_t modifier_hi = modifiers[j] >> 32;

                  zwp_linux_dmabuf_v1_send_modifier(resource, formats[i],
                                                    modifier_hi,
                                                    modifier_lo);
               }
             else if (modifiers[j] == DRM_FORMAT_MOD_LINEAR ||
                      modifiers == &modifier_invalid)
               {
                  zwp_linux_dmabuf_v1_send_format(resource, formats[i]);
               }
          }
        if (modifiers != &modifier_invalid)
          free(modifiers);
     }
   free(formats);
}

/** Advertise linux_dmabuf support
 *
 * Calling this initializes the zwp_linux_dmabuf protocol support, so that
 * the interface will be advertised to clients. Essentially it creates a
 * global. Do not call this function multiple times in the compositor's
 * lifetime. There is no way to deinit explicitly, globals will be reaped
 * when the wl_display gets destroyed.
 *
 * \param compositor The compositor to init for.
 * \return Zero on success, -1 on failure.
 */
EINTERN int
linux_dmabuf_setup(struct wl_display *display)
{
   if (!wl_global_create(display, &zwp_linux_dmabuf_v1_interface, 3,
                         NULL, bind_linux_dmabuf))
     return -1;

   return 0;
}

/** Resolve an internal compositor error by disconnecting the client.
 *
 * This function is used in cases when the dmabuf-based wl_buffer
 * turns out unusable and there is no fallback path. This is used by
 * renderers which are the fallback path in the first place.
 *
 * It is possible the fault is caused by a compositor bug, the underlying
 * graphics stack bug or normal behaviour, or perhaps a client mistake.
 * In any case, the options are to either composite garbage or nothing,
 * or disconnect the client. This is a helper function for the latter.
 *
 * The error is sent as an INVALID_OBJECT error on the client's wl_display.
 *
 * \param buffer The linux_dmabuf_buffer that is unusable.
 * \param msg A custom error message attached to the protocol error.
 */
EINTERN void
linux_dmabuf_buffer_send_server_error(struct linux_dmabuf_buffer *buffer,
				      const char *msg)
{
   struct wl_client *client;
   struct wl_resource *display_resource;
   uint32_t id;

   assert(buffer->buffer_resource);
   id = wl_resource_get_id(buffer->buffer_resource);
   client = wl_resource_get_client(buffer->buffer_resource);
   display_resource = wl_client_get_object(client, 1);

   assert(display_resource);
   wl_resource_post_error(display_resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
                          "linux_dmabuf server error with "
                          "wl_buffer@%u: %s", id, msg);
}
#define E_COMP_WL
#include "e.h"

#include <uuid.h>
#include "session-recovery-server-protocol.h"
#include "xdg-foreign-unstable-v1-server-protocol.h"
#include "relative-pointer-unstable-v1-server-protocol.h"
#include "pointer-constraints-unstable-v1-server-protocol.h"
#include "action_route-server-protocol.h"

/* mutter uses 32, seems reasonable */
#define HANDLE_LEN 32

typedef struct Exported
{
   E_Client *ec;
   struct wl_resource *res;
   char handle[HANDLE_LEN + 1];
   Eina_List *imported;
} Exported;

typedef struct Imported
{
   /* child */
   E_Client *ec;
   struct wl_resource *res;
   Exported *ex;
} Imported;

typedef struct Constraint
{
   E_Client *ec;
   struct wl_resource *res;
   struct wl_resource *seat;
   struct wl_resource *surface;
   Eina_Tiler *region;
   Eina_Tiler *pending;
   Evas_Point *pending_xy;
   Evas_Point *pointer_xy;
   Eina_Bool lock E_BITFIELD; // if not lock, confine
   Eina_Bool persistent E_BITFIELD;
   Eina_Bool active E_BITFIELD;
} Constraint;

static Eina_List *active_constraints;

static void
_e_comp_wl_extensions_client_move_begin(void *d EINA_UNUSED, E_Client *ec)
{
   if (e_client_has_xwindow(ec) || e_object_is_del(E_OBJECT(ec))) return;
}

static void
_e_comp_wl_extensions_client_move_end(void *d EINA_UNUSED, E_Client *ec)
{
   if (e_client_has_xwindow(ec) || e_object_is_del(E_OBJECT(ec))) return;
}

static void
_e_comp_wl_session_recovery_get_uuid(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *surface)
{
   E_Client *ec;
   uuid_t u;
   char uuid[37];

   ec = wl_resource_get_user_data(surface);
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->internal || ec->uuid) return;
   uuid_generate(u);
   uuid_unparse_lower(u, uuid);
   zwp_e_session_recovery_send_create_uuid(resource, surface, uuid);
   if (ec->sr_remember)
     {
        e_remember_unuse(ec->sr_remember);
        e_remember_del(ec->sr_remember);
     }
   eina_stringshare_replace(&ec->uuid, uuid);
   ec->sr_remember = e_remember_new();
   e_remember_use(ec->sr_remember);
   ec->sr_remember->apply = E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE | E_REMEMBER_APPLY_DESKTOP |
                         E_REMEMBER_APPLY_LAYER | E_REMEMBER_APPLY_ZONE | E_REMEMBER_APPLY_UUID;
   e_remember_update(ec);
}

static void
_e_comp_wl_session_recovery_set_uuid(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, struct wl_resource *surface, const char *uuid)
{
   E_Client *ec;
   E_Remember *rem;

   ec = wl_resource_get_user_data(surface);
   if (e_object_is_del(E_OBJECT(ec))) return;
   if (ec->internal || ec->uuid) return; //FIXME: error
   eina_stringshare_replace(&ec->uuid, uuid);
   rem = e_remember_sr_find(ec);
   if ((!rem) || (rem == ec->sr_remember)) return;
   if (ec->sr_remember)
     {
        e_remember_unuse(ec->sr_remember);
        e_remember_del(ec->sr_remember);
     }
   ec->sr_remember = rem;
   e_remember_use(rem);
   e_remember_apply(rem, ec);
   ec->re_manage = 1;
}

static void
_e_comp_wl_session_recovery_destroy_uuid(struct wl_client *client EINA_UNUSED, struct wl_resource *resource EINA_UNUSED, struct wl_resource *surface, const char *uuid)
{
   E_Client *ec;

   ec = wl_resource_get_user_data(surface);
   if (!eina_streq(ec->uuid, uuid)) return; //FIXME: error
   eina_stringshare_replace(&ec->uuid, NULL);
   if (ec->sr_remember)
     {
        e_remember_unuse(ec->sr_remember);
        e_remember_del(ec->sr_remember);
     }
   ec->sr_remember = e_remember_sr_find(ec);
   if (!ec->sr_remember) return;
   e_remember_use(ec->sr_remember);
   e_remember_apply(ec->sr_remember, ec);
}

///////////////////////////////////////////////////////

static void
_e_comp_wl_zxdg_exported_v1_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void _imported_v1_del(Imported *im);
static void _exported_del(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void
_exported_v1_del(Exported *ex)
{
   while (ex->imported)
     {
        Imported *im = eina_list_data_get(ex->imported);

        zxdg_imported_v1_send_destroyed(im->res);
        _imported_v1_del(im);
     }
   evas_object_event_callback_del(ex->ec->frame, EVAS_CALLBACK_DEL, _exported_del);
   wl_resource_set_user_data(ex->res, NULL);
   eina_hash_del_by_key(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, ex->handle);
   free(ex);
}

static void
_e_zxdg_exported_v1_del(struct wl_resource *resource)
{
   Exported *ex = wl_resource_get_user_data(resource);

   if (ex) _exported_v1_del(ex);
}

static void
_exported_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _exported_v1_del(data);
}

static void
_e_comp_wl_zxdg_exporter_v1_exporter_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static const struct zxdg_exported_v1_interface _e_zxdg_exported_v1_interface =
{
   _e_comp_wl_zxdg_exported_v1_destroy,
};

static void
_e_comp_wl_zxdg_exporter_v1_export(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface)
{
   E_Client *ec = wl_resource_get_user_data(surface);
   Exported *ex;

   if ((!ec) || (!ec->comp_data->is_xdg_surface) || ec->comp_data->cursor)
     {
        wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "invalid role for exported surface");
        return;
     }

   ex = E_NEW(Exported, 1);
   ex->ec = ec;
   ex->res = wl_resource_create(client, &zxdg_exported_v1_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(ex->res, &_e_zxdg_exported_v1_interface, ex, _e_zxdg_exported_v1_del);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_DEL, _exported_del, ex);

   do
     {
        int n;

        for (n = 0; n < HANDLE_LEN; n++)
          {
             /* only printable ascii */
             ex->handle[n] = (rand() % (127 - 32)) + 32;
          }
     } while (eina_hash_find(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, ex->handle));
   eina_hash_add(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, ex->handle, ex);

   zxdg_exported_v1_send_handle(ex->res, ex->handle);
}


static void
_e_comp_wl_zxdg_imported_v1_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void _imported_del(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void
_imported_v1_del(Imported *im)
{
   im->ex->imported = eina_list_remove(im->ex->imported, im);
   if (im->ec)
     {
        evas_object_event_callback_del(im->ec->frame, EVAS_CALLBACK_DEL, _imported_del);
        e_client_parent_set(im->ec, NULL);
     }
   if (im->res) wl_resource_set_user_data(im->res, NULL);
   free(im);
}

static void
_e_zxdg_imported_v1_del(struct wl_resource *resource)
{
   Imported *im = wl_resource_get_user_data(resource);

   if (im) _imported_v1_del(im);
}

static void
_imported_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Imported *im = data;

   im->ec = NULL;
}

static void
_e_comp_wl_zxdg_importer_v1_importer_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_zxdg_imported_v1_set_parent_of(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *surface_resource)
{
   Imported *im = wl_resource_get_user_data(resource);
   E_Client *ec = NULL;

   if (surface_resource) ec = wl_resource_get_user_data(surface_resource);

   if (ec && ((ec->netwm.type != E_WINDOW_TYPE_NORMAL) || (!ec->comp_data->is_xdg_surface)))
     {
        wl_resource_post_error(im->res, WL_DISPLAY_ERROR_INVALID_OBJECT,
          "xdg_imported.set_parent_of called with invalid surface");
        return;
     }

   if (im->ec)
     evas_object_event_callback_del(im->ec->frame, EVAS_CALLBACK_DEL, _imported_del);

   im->ec = ec;

   if (ec)
     {
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_DEL, _imported_del, im);
        e_client_parent_set(ec, im->ex->ec);
        ec->parent->lock_close = 1;
        ec->parent->comp_data->need_center = 1;
     }
}

static const struct zxdg_imported_v1_interface _e_zxdg_imported_v1_interface =
{
   _e_comp_wl_zxdg_imported_v1_destroy,
   _e_comp_wl_zxdg_imported_v1_set_parent_of,
};

static void
_e_comp_wl_zxdg_importer_v1_import(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle)
{
   Imported *im;
   Exported *ex;

   im = E_NEW(Imported, 1);
   im->res = wl_resource_create(client, &zxdg_imported_v1_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(im->res, &_e_zxdg_imported_v1_interface, NULL, _e_zxdg_imported_v1_del);

   ex = eina_hash_find(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, handle);
   if ((!ex) || (!ex->ec->netwm.type))
     {
        zxdg_imported_v1_send_destroyed(im->res);
        free(im);
        return;
     }

   im->ex = ex;
   wl_resource_set_user_data(im->res, im);
   ex->imported = eina_list_append(ex->imported, im);
}

/////////////////////////////////////////////////////////

static void
_e_comp_wl_zwp_relative_pointer_manager_v1_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_relative_pointer_destroy(struct wl_resource *resource)
{
   e_comp_wl->extensions->zwp_relative_pointer_manager_v1.resources =
     eina_list_remove(e_comp_wl->extensions->zwp_relative_pointer_manager_v1.resources, resource);
}

static void
_e_comp_wl_zwp_relative_pointer_v1_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}


static const struct zwp_relative_pointer_v1_interface _e_zwp_relative_pointer_v1_interface =
{
   _e_comp_wl_zwp_relative_pointer_v1_destroy,
};

static void
_e_comp_wl_zwp_relative_pointer_manager_v1_get_relative_pointer(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *pointer)
{
   struct wl_resource *res;

   res = wl_resource_create(client, &zwp_relative_pointer_v1_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(res, &_e_zwp_relative_pointer_v1_interface, pointer, _relative_pointer_destroy);
   e_comp_wl->extensions->zwp_relative_pointer_manager_v1.resources =
     eina_list_append(e_comp_wl->extensions->zwp_relative_pointer_manager_v1.resources, res);
}

/////////////////////////////////////////////////////////

static void
_e_comp_wl_zwp_pointer_constraints_v1_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_constraint_destroy(struct wl_resource *resource)
{
   Eina_Hash *constraints;
   Constraint *c = wl_resource_get_user_data(resource);

   constraints = eina_hash_find(e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints, &c->seat);
   if (constraints)
     eina_hash_del_by_key(constraints, &c->surface);
   if (c->active)
     {
        active_constraints = eina_list_remove(active_constraints, c);
        if (c->lock && c->pointer_xy)
          ecore_evas_pointer_warp(e_comp->ee, c->ec->client.x + c->pointer_xy->x, c->ec->client.y + c->pointer_xy->y);
     }
   if (c->ec)
     {
        if (c->ec->comp_data->constraints)
          c->ec->comp_data->constraints = eina_list_remove(c->ec->comp_data->constraints, c);
     }
   eina_tiler_free(c->pending);
   eina_tiler_free(c->region);
   free(c->pointer_xy);
   free(c->pending_xy);
   free(c);
}

static void
_constraint_set_pending(Constraint *c)
{
   if (c->ec->comp_data->constraints)
     c->ec->comp_data->constraints = eina_list_remove(c->ec->comp_data->constraints, c);
   c->ec->comp_data->constraints = eina_list_append(c->ec->comp_data->constraints, c);
}

static void
_constraint_set_region(struct wl_resource *resource, struct wl_resource *region)
{
   Constraint *c = wl_resource_get_user_data(resource);
   Eina_Tiler *r = NULL;

   if (region) r = wl_resource_get_user_data(region);
   else E_FREE_FUNC(c->pending, eina_tiler_free);

   if (c->pending)
     eina_tiler_clear(c->pending);
   else
     {
        c->pending = eina_tiler_new(65535, 65535);
        eina_tiler_tile_size_set(c->pending, 1, 1);
     }
   if (r)
     eina_tiler_union(c->pending, r);
   _constraint_set_pending(c);
}

static void
_e_comp_wl_locked_pointer_v1_set_region(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region)
{
   _constraint_set_region(resource, region);
}

static void
_e_comp_wl_locked_pointer_v1_set_cursor_position_hint(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, wl_fixed_t surface_x, wl_fixed_t surface_y)
{
   Constraint *c = wl_resource_get_user_data(resource);

   if (!c->pending_xy)
     c->pending_xy = E_NEW(Evas_Point, 1);
   c->pending_xy->x = wl_fixed_to_int(surface_x);
   c->pending_xy->y = wl_fixed_to_int(surface_y);
   _constraint_set_pending(c);
}

static const struct zwp_locked_pointer_v1_interface _e_comp_wl_locked_pointer_v1_interface =
{
   _e_comp_wl_zwp_pointer_constraints_v1_destroy,
   _e_comp_wl_locked_pointer_v1_set_cursor_position_hint,
   _e_comp_wl_locked_pointer_v1_set_region,
};


static void
_e_comp_wl_confined_pointer_v1_set_region(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region)
{
   _constraint_set_region(resource, region);
}

static const struct zwp_confined_pointer_v1_interface _e_comp_wl_confined_pointer_v1_interface =
{
   _e_comp_wl_zwp_pointer_constraints_v1_destroy,
   _e_comp_wl_confined_pointer_v1_set_region,
};

static Constraint *
do_constraint(const struct wl_interface *interface, const void *impl, struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface, struct wl_resource *pointer, struct wl_resource *region, uint32_t lifetime)
{
   struct wl_resource *res, *seat;
   Eina_Hash *constraints;
   Constraint *c;

   seat = wl_resource_get_user_data(pointer);
   constraints = eina_hash_find(e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints, &seat);
   if (constraints)
     {
        c = eina_hash_find(constraints, &surface);
        if (c)
          {
             wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT,
               "constraint already exists for requested seat+surface");
             return NULL;
          }
     }
   else
     {
        constraints = eina_hash_pointer_new(NULL);
        eina_hash_add(e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints, &seat, constraints);
     }
   c = E_NEW(Constraint, 1);
   c->seat = seat;
   c->surface = surface;
   c->persistent = lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT;
   c->ec = wl_resource_get_user_data(surface);
   c->res = res = wl_resource_create(client, interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(res, impl, pointer, _constraint_destroy);
   wl_resource_set_user_data(res, c);
   _constraint_set_region(res, region);
   return c;
}

static void
_e_comp_wl_zwp_pointer_constraints_v1_lock_pointer(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface, struct wl_resource *pointer, struct wl_resource *region, uint32_t lifetime)
{
   Constraint *c;

   c = do_constraint(&zwp_locked_pointer_v1_interface, &_e_comp_wl_locked_pointer_v1_interface,
     client, resource, id, surface, pointer, region, lifetime);
   if (c)
     c->lock = 1;
}

static void
_e_comp_wl_zwp_pointer_constraints_v1_confine_pointer(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface, struct wl_resource *pointer, struct wl_resource *region, uint32_t lifetime)
{
   do_constraint(&zwp_confined_pointer_v1_interface, &_e_comp_wl_confined_pointer_v1_interface,
     client, resource, id, surface, pointer, region, lifetime);
}

/////////////////////////////////////////////////////////
extern E_Action *(*e_binding_key_list_cb)(E_Binding_Context, Ecore_Event_Key*, E_Binding_Modifier, E_Binding_Key **);
static Eina_Hash *key_bindings;
static int action_route_version = 1;

typedef struct Action_Route_Key
{
   struct
   {
      E_Binding_Key key;
   } binding;
   uint32_t mode;
   uint32_t state;
   struct wl_resource *res;
   struct wl_resource *surface;
} Action_Route_Key;

typedef struct Action_Route_Bind
{
   E_Action *action;
   uint32_t state;
   struct wl_resource *res;
   Evas_Object *end_obj;
} Action_Route_Bind;

static Eina_Hash *allowed_pids;

static E_Action *
_action_route_key_list_cb(E_Binding_Context ctxt, Ecore_Event_Key *ev, E_Binding_Modifier mod, E_Binding_Key **bind_ret)
{
   Eina_List *l, *ll;
   Action_Route_Key *ar;
   E_Binding_Key *binding;

   if (bind_ret) *bind_ret = NULL;
   l = eina_hash_find(key_bindings, ev->key);
   EINA_LIST_FOREACH(l, ll, ar)
     {
        E_Client *ec;

        if (ar->state != ACTION_ROUTE_KEY_GRAB_STATE_ACTIVE) break;
        binding = &ar->binding.key;
        if ((!binding->any_mod) && (binding->mod != mod)) continue;
        if (!e_bindings_context_match(binding->ctxt, ctxt)) continue;
        ec = wl_resource_get_user_data(ar->surface);
        if (!ec) continue;//wtf?
        if (bind_ret) *bind_ret = binding;
        switch (ar->mode)
          {
             /* if exclusive client has grab, activate. otherwise, no actions allowed */
             case ACTION_ROUTE_MODE_EXCLUSIVE:
               if (!ec->focused) return NULL;
               return e_action_find(binding->action);
             /* all surfaces for wl_client share the grab; if any surface has focus, activate. */
             case ACTION_ROUTE_MODE_FOCUS_SHARED:
               {
                  struct wl_client *client;

                  client = wl_resource_get_client(ar->surface);
                  if (!e_client_focused_get()) continue;
                  if (wl_resource_get_client(e_client_focused_get()->comp_data->surface) != client)
                    continue;
                  return e_action_find(binding->action);
               }
             /* only activate if surface has focus and is on top */
             case ACTION_ROUTE_MODE_FOCUS_TOPMOST:
             if (!ec->focused) continue;
             {
                E_Client *aec;
                Eina_Bool valid = EINA_TRUE;

                for (aec = e_client_above_get(ec); aec; aec = e_client_above_get(aec))
                  {
                     if (aec->layer > E_LAYER_CLIENT_PRIO)
                       return e_action_find(binding->action);
                     if (evas_object_visible_get(aec->frame))
                       {
                          valid = EINA_FALSE;
                          break;
                       }
                  }
                if (valid)
                  return e_action_find(binding->action);
                continue;
              }
          }
     }
   if (bind_ret) *bind_ret = NULL;
   return NULL;
}

static Eina_Bool
_action_route_key_is_allowed(Eina_Hash *h EINA_UNUSED, const char *action EINA_UNUSED, const char *params EINA_UNUSED)
{
   /* FIXME: no users of this yet... */
   return EINA_FALSE;
}

static Eina_Bool
_action_route_can_override(const E_Binding_Key *binding)
{
   if (!binding) return EINA_TRUE;
   /* FIXME: determine full list of overridable binding contexts */
   if ((binding->ctxt == E_BINDING_CONTEXT_ANY) ||
       (binding->ctxt == E_BINDING_CONTEXT_WINLIST))
     return EINA_TRUE;
   return EINA_FALSE;
}

static void
_e_comp_wl_action_route_key_grab_del(struct wl_resource *resource)
{
   Eina_List *l, *ll;
   Action_Route_Key *ar;
   Eina_Bool update;

   /* FIXME: delete active actions? */
   ar = wl_resource_get_user_data(resource);
   if (!ar) return;
   eina_hash_list_remove(key_bindings, ar->binding.key.key, ar);
   update = (ar->mode == ACTION_ROUTE_MODE_EXCLUSIVE) && (ar->state == ACTION_ROUTE_KEY_GRAB_STATE_ACTIVE);
   l = eina_hash_find(key_bindings, ar->binding.key.key);
   eina_stringshare_del(ar->binding.key.key);
   eina_stringshare_del(ar->binding.key.action);
   eina_stringshare_del(ar->binding.key.params);
   free(ar);
   if (!update) return;
   EINA_LIST_FOREACH(l, ll, ar)
     {
        /* all action routes are active until an exclusive or active grab has been reached */
        if (ar->state == ACTION_ROUTE_KEY_GRAB_STATE_ACTIVE) break;//futureproofing...
        ar->state = ACTION_ROUTE_KEY_GRAB_STATE_ACTIVE;
        action_route_key_grab_send_status(ar->res, ar->state);
        if (ar->mode == ACTION_ROUTE_MODE_EXCLUSIVE) break;
     }
}

static void
_e_comp_wl_action_route_key_grab_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static const struct action_route_key_grab_interface _e_action_route_key_grab_interface =
{
   _e_comp_wl_action_route_key_grab_destroy,
};

static void
_e_comp_wl_action_route_grab_key(struct wl_client *client, struct wl_resource *resource,
                                                           uint32_t id,
                                                           struct wl_resource *surface,
                                                           const char *key,
                                                           uint32_t mode,
                                                           uint32_t modifiers,
                                                           const char *action,
                                                           const char *params)
{
   struct wl_resource *rt;
   E_Binding_Key *binding;
   Eina_List *l, *ll;
   Action_Route_Key *ar, *lar;
   E_Binding_Context ctxt = E_BINDING_CONTEXT_WINDOW;
   Eina_Hash *h;

   rt = wl_resource_create(client, &action_route_key_grab_interface, 1, id);
   if (!rt)
     {
        ERR("Could not create action route");
        wl_client_post_no_memory(client);
        return;
     }
   wl_resource_set_implementation(rt, &_e_action_route_key_grab_interface, NULL,
                                  _e_comp_wl_action_route_key_grab_del);
   binding = e_bindings_key_find(key, modifiers, 0);
   h = wl_resource_get_user_data(resource);
   if ((!_action_route_key_is_allowed(h, action, params)) || (!_action_route_can_override(binding)))
     {
        action_route_key_grab_send_status(rt, ACTION_ROUTE_KEY_GRAB_STATE_REJECTED);
        wl_resource_destroy(rt);
        return;
     }
   ar = E_NEW(Action_Route_Key, 1);
   ar->mode = mode;
   ar->res = rt;
   ar->surface = surface;
   binding = &ar->binding.key;
   switch (mode)
     {
      case ACTION_ROUTE_MODE_EXCLUSIVE:
        ctxt = E_BINDING_CONTEXT_ANY;
        break;
      case ACTION_ROUTE_MODE_FOCUS_SHARED:
        ctxt = E_BINDING_CONTEXT_WINDOW;
        break;
      case ACTION_ROUTE_MODE_FOCUS_TOPMOST:
        ctxt = E_BINDING_CONTEXT_WINDOW;
        break;
     }
   binding->ctxt = ctxt;
   binding->key = eina_stringshare_add(key);
   binding->mod = modifiers;
   binding->any_mod = 0;
   binding->action = eina_stringshare_add(action);
   binding->params = eina_stringshare_add(params);
   wl_resource_set_user_data(rt, ar);
   l = eina_hash_find(key_bindings, key);
   EINA_LIST_FOREACH(l, ll, lar)
     {
        if (lar->mode == ACTION_ROUTE_MODE_EXCLUSIVE)
          {
             ar->state = ACTION_ROUTE_KEY_GRAB_STATE_QUEUED;
             continue;
          }
        ar->state = ACTION_ROUTE_KEY_GRAB_STATE_ACTIVE;
        if (ar->mode == ACTION_ROUTE_MODE_EXCLUSIVE)
          l = eina_list_prepend_relative(l, ar, ll);
        else
          l = eina_list_append(l, ar);
        break;
     }
   eina_hash_set(key_bindings, key, l);
   action_route_key_grab_send_status(ar->res, ar->state);
}

static void
_e_comp_wl_action_route_bind_menu_end(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Action_Route_Bind *ar = data;
   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_HIDE,
          _e_comp_wl_action_route_bind_menu_end, ar);
   action_route_bind_send_end(ar->res);
}

static void
_e_comp_wl_action_route_bind_action_del(struct wl_resource *resource)
{
   Action_Route_Bind *ar = wl_resource_get_user_data(resource);

   if (strstr(ar->action->name, "menu"))
     {
        E_Menu *m = e_menu_active_get();
        if (!m) return;
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_HIDE,
          _e_comp_wl_action_route_bind_menu_end, ar);
     }

   e_object_unref(E_OBJECT(ar->action));
   free(ar);
}

static void
_e_comp_wl_action_route_bind_action_activate(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *params)
{
   Action_Route_Bind *ar = wl_resource_get_user_data(resource);
   if (!ar) return;
   ar->action->func.go(NULL, params);
   if (strstr(ar->action->name, "menu"))
     {
        E_Menu *m = e_menu_active_get();
        if (!m) return;
        evas_object_event_callback_add(m->comp_object, EVAS_CALLBACK_HIDE,
          _e_comp_wl_action_route_bind_menu_end, ar);
     }
}

static void
_e_comp_wl_action_route_bind_action_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static const struct action_route_bind_interface _e_action_route_bind_action_interface =
{
   _e_comp_wl_action_route_bind_action_activate,
   _e_comp_wl_action_route_bind_action_destroy,
};

static E_Action *
_action_route_bind_is_allowed(Eina_Hash *h, int32_t pid, const char *action)
{
   const char **b, *blacklist[] =
   {
    "exit_now",
    "halt_now",
    "suspend_now",
    "hibernate_now",
    NULL,
   };
   if (!h) return NULL;
   if (!eina_hash_find(h, &pid)) return NULL;
   if ((!action[0])) return NULL;
   if (!strncmp(action, "module_", sizeof("module_") - 1)) return NULL;
   for (b = blacklist; *b; b++)
     if (eina_streq(*b, action)) return NULL;
   return e_action_find(action);
}

static void
_e_comp_wl_action_route_bind_action(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *action)
{
   struct wl_resource *rt;
   Action_Route_Bind *ar;
   E_Action *act;
   int32_t pid;
   Eina_Hash *h;

   rt = wl_resource_create(client, &action_route_bind_interface, 1, id);
   if (!rt)
     {
        ERR("Could not create action route");
        wl_client_post_no_memory(client);
        return;
     }
   wl_resource_set_implementation(rt, &_e_action_route_bind_action_interface, NULL,
                                  _e_comp_wl_action_route_bind_action_del);
   wl_client_get_credentials(client, &pid, NULL, NULL);
   h = wl_resource_get_user_data(resource);
   act = _action_route_bind_is_allowed(h, pid, action);
   if (!act)
     {
        action_route_bind_send_status(rt, ACTION_ROUTE_BIND_STATE_REJECTED);
        wl_resource_destroy(rt);
        return;
     }
   ar = E_NEW(Action_Route_Bind, 1);
   ar->res = rt;
   ar->action = act;
   e_object_ref(E_OBJECT(act));
   ar->state = ACTION_ROUTE_BIND_STATE_ACTIVE;
   wl_resource_set_user_data(rt, ar);
   action_route_bind_send_status(ar->res, ar->state);
}

E_API void
e_comp_wl_extension_action_route_pid_allowed_set(uint32_t pid, Eina_Bool allow)
{
   if (allow)
     eina_hash_add(allowed_pids, &pid, (void*)1);
   else
     eina_hash_del_by_key(allowed_pids, &pid);
}

/////////////////////////////////////////////////////////

static const struct zwp_e_session_recovery_interface _e_session_recovery_interface =
{
   _e_comp_wl_session_recovery_get_uuid,
   _e_comp_wl_session_recovery_set_uuid,
   _e_comp_wl_session_recovery_destroy_uuid,
};

static const struct zxdg_exporter_v1_interface _e_zxdg_exporter_v1_interface =
{
   _e_comp_wl_zxdg_exporter_v1_exporter_destroy,
   _e_comp_wl_zxdg_exporter_v1_export,
};

static const struct zxdg_importer_v1_interface _e_zxdg_importer_v1_interface =
{
   _e_comp_wl_zxdg_importer_v1_importer_destroy,
   _e_comp_wl_zxdg_importer_v1_import,
};

static const struct zwp_relative_pointer_manager_v1_interface _e_zwp_relative_pointer_manager_v1_interface =
{
   _e_comp_wl_zwp_relative_pointer_manager_v1_destroy,
   _e_comp_wl_zwp_relative_pointer_manager_v1_get_relative_pointer,
};

static const struct zwp_pointer_constraints_v1_interface _e_zwp_pointer_constraints_v1_interface =
{
   _e_comp_wl_zwp_pointer_constraints_v1_destroy,
   _e_comp_wl_zwp_pointer_constraints_v1_lock_pointer,
   _e_comp_wl_zwp_pointer_constraints_v1_confine_pointer,
};

static const struct action_route_interface _e_action_route_interface =
{
   _e_comp_wl_action_route_bind_action,
   _e_comp_wl_action_route_grab_key,
};

E_API const void *
e_comp_wl_extension_action_route_interface_get(int *version)
{
   if (version) *version = action_route_version;
   return &_e_action_route_interface;
}

#define GLOBAL_BIND_CB(NAME, IFACE, ...) \
static void \
_e_comp_wl_##NAME##_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id) \
{ \
   struct wl_resource *res; \
\
   if (!(res = wl_resource_create(client, &(IFACE), version, id))) \
     { \
        ERR("Could not create %s interface", #NAME);\
        wl_client_post_no_memory(client);\
        return;\
     }\
\
   wl_resource_set_implementation(res, &_e_##NAME##_interface, NULL, NULL);\
   __VA_ARGS__ \
}

GLOBAL_BIND_CB(session_recovery, zwp_e_session_recovery_interface)
GLOBAL_BIND_CB(zxdg_exporter_v1, zxdg_exporter_v1_interface)
GLOBAL_BIND_CB(zxdg_importer_v1, zxdg_importer_v1_interface)
GLOBAL_BIND_CB(zwp_relative_pointer_manager_v1, zwp_relative_pointer_manager_v1_interface)
GLOBAL_BIND_CB(zwp_pointer_constraints_v1, zwp_pointer_constraints_v1_interface)
GLOBAL_BIND_CB(action_route, action_route_interface,
     e_binding_key_list_cb = _action_route_key_list_cb;
     key_bindings = eina_hash_string_superfast_new(NULL);
     allowed_pids = eina_hash_int32_new(NULL);
     wl_resource_set_user_data(res, allowed_pids);
)


#define GLOBAL_CREATE_OR_RETURN(NAME, IFACE, VERSION) \
   do { \
      struct wl_global *global; \
\
      global = wl_global_create(e_comp_wl->wl.disp, &(IFACE), VERSION, \
                                NULL, _e_comp_wl_##NAME##_cb_bind); \
      if (!global) \
        { \
           ERR("Could not add %s to wayland globals", #IFACE); \
           return EINA_FALSE; \
        } \
      e_comp_wl->extensions->NAME.global = global; \
   } while (0)

static Eina_Bool
_dmabuf_add(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Wl2_Event_Sync_Done *ev;

   ev = event;
   if (ev->display != e_comp_wl->wl.client_disp)
     return ECORE_CALLBACK_PASS_ON;

   /* Proxy not supported yet */
   if (!(e_comp_wl->dmabuf_disable || e_comp_wl->dmabuf_proxy))
     linux_dmabuf_setup(e_comp_wl->wl.disp);

   return ECORE_CALLBACK_PASS_ON;
}

extern E_Binding_Key *e_binding_key_current;

static void
_e_comp_wl_action_route_act_key_route_go_end(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED, Ecore_Event_Key *ev)
{
   Eina_List *l, *ll;
   Action_Route_Key *ar;
   E_Binding_Key *binding;

   l = eina_hash_find(key_bindings, ev->key);
   EINA_LIST_FOREACH(l, ll, ar)
     {
        E_Client *ec = wl_resource_get_user_data(ar->surface);
        if (!ec) continue;
        binding = &ar->binding.key;
        if (((e_binding_key_current->mod != binding->mod) && (e_binding_key_current->any_mod != binding->any_mod)) ||
          (e_binding_key_current->ctxt != binding->ctxt)) continue;
        e_comp_wl_key_up(ev, ec);
        break;
     }
}

static void
_e_comp_wl_action_route_act_key_route_go(E_Object *obj EINA_UNUSED, const char *params EINA_UNUSED, Ecore_Event_Key *ev)
{
   Eina_List *l, *ll;
   Action_Route_Key *ar;
   E_Binding_Key *binding;

   l = eina_hash_find(key_bindings, ev->key);
   EINA_LIST_FOREACH(l, ll, ar)
     {
        E_Client *ec = wl_resource_get_user_data(ar->surface);
        if (!ec) continue;
        binding = &ar->binding.key;
        if (((e_binding_key_current->mod != binding->mod) && (e_binding_key_current->any_mod != binding->any_mod)) ||
          (e_binding_key_current->ctxt != binding->ctxt)) continue;
        e_comp_wl_key_down(ev, ec);
        break;
     }
}

EINTERN Eina_Bool e_comp_wl_extensions_tizen_init(void);

EINTERN Eina_Bool
e_comp_wl_extensions_init(void)
{
   E_Action *act;

   e_comp_wl->extensions = E_NEW(E_Comp_Wl_Extension_Data, 1);

   /* try to add session_recovery to wayland globals */
   GLOBAL_CREATE_OR_RETURN(session_recovery, zwp_e_session_recovery_interface, 1);
   GLOBAL_CREATE_OR_RETURN(zxdg_exporter_v1, zxdg_exporter_v1_interface, 1);
   e_comp_wl->extensions->zxdg_exporter_v1.surfaces = eina_hash_string_superfast_new(NULL);
   GLOBAL_CREATE_OR_RETURN(zxdg_importer_v1, zxdg_importer_v1_interface, 1);
   GLOBAL_CREATE_OR_RETURN(zwp_relative_pointer_manager_v1, zwp_relative_pointer_manager_v1_interface, 1);
   GLOBAL_CREATE_OR_RETURN(zwp_pointer_constraints_v1, zwp_pointer_constraints_v1_interface, 1);
   e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints = eina_hash_pointer_new(NULL);
   GLOBAL_CREATE_OR_RETURN(action_route, action_route_interface, 1);

   ecore_event_handler_add(ECORE_WL2_EVENT_SYNC_DONE, _dmabuf_add, NULL);

   e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN, _e_comp_wl_extensions_client_move_begin, NULL);
   e_client_hook_add(E_CLIENT_HOOK_MOVE_END, _e_comp_wl_extensions_client_move_end, NULL);

   act = e_action_add("key_route");
   act->func.go_key = _e_comp_wl_action_route_act_key_route_go;
   act->func.end_key = _e_comp_wl_action_route_act_key_route_go_end;

   e_comp_wl_extensions_tizen_init();

   return EINA_TRUE;
}

E_API void
e_comp_wl_extension_relative_motion_event(uint64_t time_usec, double dx, double dy, double dx_unaccel, double dy_unaccel)
{
   Eina_List *l;
   struct wl_resource *res;
   E_Client *focused;
   struct wl_client *wc;
   uint32_t hi, lo;

   focused = e_client_focused_get();
   if ((!focused) || e_object_is_del(E_OBJECT(focused)) || (!focused->mouse.in)) return;
   if (e_comp_object_frame_exists(focused->frame) && (!focused->comp_data->ssd_mouse_in)) return;

   wc = wl_resource_get_client(focused->comp_data->surface);

   hi = time_usec >> 32;
   lo = (uint32_t)time_usec;

   EINA_LIST_FOREACH(e_comp_wl->extensions->zwp_relative_pointer_manager_v1.resources, l, res)
     {
        if (wl_resource_get_client(res) != wc) continue;
        zwp_relative_pointer_v1_send_relative_motion(res, hi, lo, wl_fixed_from_double(dx),
          wl_fixed_from_double(dy), wl_fixed_from_double(dx_unaccel), wl_fixed_from_double(dy_unaccel));
     }
}

E_API void
e_comp_wl_extension_pointer_constraints_commit(E_Client *ec)
{
   Eina_List *l;
   Constraint *c;

   if ((!ec) || (!ec->comp_data)) return;
   EINA_LIST_FOREACH(ec->comp_data->constraints, l, c)
     {
        if (c->pending)
          {
             eina_tiler_free(c->region);
             c->region = c->pending;
             c->pending = NULL;
          }
        if (c->pending_xy)
          {
             if (c->pointer_xy)
               free(c->pointer_xy);
             c->pointer_xy = c->pending_xy;
             c->pending_xy = NULL;
          }
     }
}

static Eina_Bool
_inside_tiler(Eina_Tiler *r, Eina_Bool active, int x, int y, int px, int py, int *ax, int *ay)
{
   Eina_Iterator *it;
   Eina_Rectangle *rect;
   Eina_Bool ret = EINA_FALSE;
   Eina_Rectangle prect, arect;
   Eina_Bool cur = EINA_FALSE, prev = EINA_FALSE;

   if (!r) return EINA_TRUE;
   it = eina_tiler_iterator_new(r);
   if (!it) return EINA_TRUE;

   EINA_ITERATOR_FOREACH(it, rect)
     {
        Eina_Bool found = EINA_FALSE;
        if (active && eina_rectangle_coords_inside(rect, px, py))
          {
             found = prev = EINA_TRUE;
             prect = *rect;
             if (cur) break;
          }
        if (eina_rectangle_coords_inside(rect, x, y))
          {
             if (active)
               {
                  cur = EINA_TRUE;
                  arect = *rect;
                  if (found) ret = EINA_TRUE;
                  if (prev) break;
               }
             else
               ret = EINA_TRUE;
             if (!active) break;
          }
     }
   eina_iterator_free(it);
   if ((!ret) && cur && prev)
     {
        int dx, dy;

        dx = abs(x - px);
        dy = abs(y - py);
        if ((!dx) || (!dy))
          {
             /* line motion along a single axis: check for adjacent confine rects */
             if (dx)
               ret = (arect.x + arect.w == prect.x) || (prect.x + prect.w == arect.x);
             else
               ret = (arect.y + arect.h == prect.y) || (prect.y + prect.h == arect.y);
          }
        else
          {
             /* check for completely contiguous regions over entire motion vector
              * use rect of vector points and check for overlap
              */
             Eina_Tiler *a;
             int w, h;
             unsigned int size, usize = 0;

             eina_tiler_area_size_get(r, &w, &h);
             a = eina_tiler_new(w, h);
             eina_tiler_tile_size_set(a, 1, 1);
             eina_tiler_rect_add(a, &arect);
             eina_tiler_rect_add(a, &prect);
             eina_tiler_rect_del(a, &(Eina_Rectangle){MIN(x, px), MIN(y, py), dx, dy});
             size = (arect.w * arect.h) + (prect.w * prect.h);
             it = eina_tiler_iterator_new(a);
             EINA_ITERATOR_FOREACH(it, rect)
               usize += rect->w * rect->h;
             ret = size - dx * dy == usize;
             eina_iterator_free(it);
             eina_tiler_free(a);
          }
     }
   if (prev && (!ret))
     {
        if (!eina_rectangle_xcoord_inside(&prect, x))
          {
             if (x < prect.x)
               *ax = prect.x;
             else
               *ax = prect.x + prect.w - 1;
             if (*ax == px)
               {
                  if (eina_rectangle_ycoord_inside(&prect, y))
                    *ay = y;
                  else
                    {
                       _inside_tiler(r, active, *ax, y, px, py, ax, ay);
                    }
               }
             else
               *ay = lround(((y - py) / (double)(x - px)) * (*ax - x)) + y;
          }
        else
          {
             if (y < prect.y)
               *ay = prect.y;
             else
               *ay = prect.y + prect.h - 1;
             if (*ay == py)
               *ax = x;
             else
               *ax = lround(((double)(x - px) / (y - py)) * (*ay - y)) + x;
          }
     }

   return ret;
}

E_API Eina_Bool
e_comp_wl_extension_pointer_constraints_update(E_Client *ec, int x, int y)
{
   Eina_List *l, *ll;
   Constraint *c;
   Eina_Bool inside;
   int px, py;

   if ((!ec) || (!ec->comp_data)) return EINA_FALSE;
   inside = e_comp_object_coords_inside_input_area(ec->frame, x, y);
   evas_pointer_canvas_xy_get(e_comp->evas, &px, &py);

   /* if constraint is active, check prev canvas coords and lock if */

   EINA_LIST_FOREACH_SAFE(ec->comp_data->constraints, l, ll, c)
     {
        int ax = px - ec->client.x, ay = py - ec->client.y;
        Eina_Bool inside_region = _inside_tiler(c->region, c->active, x - ec->client.x, y - ec->client.y,
          px - ec->client.x, py - ec->client.y, &ax, &ay);
        if ((!c->active) && inside && inside_region)
          {
             c->active = 1;
             active_constraints = eina_list_append(active_constraints, c);
             if (c->lock)
               zwp_locked_pointer_v1_send_locked(c->res);
             else
               zwp_confined_pointer_v1_send_confined(c->res);
          }
        if (!c->active) continue;
        if (c->lock || ((!inside) || (!inside_region)))
          {
             ecore_evas_pointer_warp(e_comp->ee, ax + ec->client.x, ay + ec->client.y);
             return EINA_TRUE;
          }
     }
   return EINA_FALSE;
}

E_API void
e_comp_wl_extension_pointer_unconstrain(E_Client *ec)
{
   Constraint *c;

   if (ec)
     {
        /* deleting client */
        EINA_LIST_FREE(ec->comp_data->constraints, c)
          {
             c->active = EINA_FALSE;
             if (c->lock)
               zwp_locked_pointer_v1_send_unlocked(c->res);
             else
               zwp_confined_pointer_v1_send_unconfined(c->res);
             active_constraints = eina_list_remove(active_constraints, c);
             c->ec = NULL;
          }
     }
   EINA_LIST_FREE(active_constraints, c)
     {
        c->active = EINA_FALSE;
        if (c->lock)
          zwp_locked_pointer_v1_send_unlocked(c->res);
        else
          zwp_confined_pointer_v1_send_unconfined(c->res);
     }
}
#define E_COMP_WL
#include "e.h"

#include "efl-aux-hints-server-protocol.h"

struct Tizen_Extensions
{
   Eina_Stringshare *stack_id;
};

enum _E_Policy_Hint_Type
{
   E_POLICY_HINT_MSG_USE = 0,
   E_POLICY_HINT_STACK_BASE,
   E_POLICY_HINT_STACK_ID,
   E_POLICY_HINT_STACK_MASTER_ID,
   E_POLICY_HINT_STACK_POP_TO,
};

static const char *hint_names[] =
{
   "wm.policy.win.msg.use",
   "stack_base",
   "stack_id",
   "stack_master_id",
   "stack_pop_to",
};
static Eina_List *aux_hints;

static void
_e_policy_wl_allowed_aux_hint_send(E_Client *ec, int id)
{
   Eina_List *l;
   struct wl_client *wc;
   struct wl_resource *res;

   wc = wl_resource_get_client(ec->comp_data->surface);
   EINA_LIST_FOREACH(aux_hints, l, res)
     if (wl_resource_get_client(res) == wc) break;
   if (!res) return;

   efl_aux_hints_send_allowed_aux_hint(
     res,
      ec->comp_data->surface,
      id);
}

static void
_tizen_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Tizen_Extensions *tizen = data;
   eina_stringshare_del(tizen->stack_id);
   free(tizen);
}

static void
_tizen_alloc(E_Client *ec)
{
   if (ec->comp_data->tizen) return;
   ec->comp_data->tizen = E_NEW(Tizen_Extensions, 1);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_DEL, _tizen_del, ec->comp_data->tizen);
}

static void
_tizen_del_request(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   e_policy_wl_aux_message_send(ec, "stack_del", "1", NULL);
}

static void
_e_policy_wl_aux_hint_apply(E_Client *ec)
{
   E_Comp_Wl_Aux_Hint *hint;
   Eina_List *l;
   Eina_Bool send;
   Tizen_Extensions *tizen;

   if (!ec->comp_data) return;
   if (!ec->comp_data->aux_hint.changed) return;
   _tizen_alloc(ec);
   tizen = ec->comp_data->tizen;
   EINA_LIST_FOREACH(ec->comp_data->aux_hint.hints, l, hint)
     {
        if (!hint->changed) continue;
        send = EINA_TRUE;
        hint->changed = EINA_FALSE;
        if (!strcmp(hint->hint, hint_names[E_POLICY_HINT_MSG_USE]))
          {
             if ((hint->deleted) || (!strcmp(hint->val, "0")))
               ec->comp_data->aux_hint.use_msg = EINA_FALSE;
             else if (!strcmp(hint->val, "1"))
               ec->comp_data->aux_hint.use_msg = EINA_TRUE;
          }
        else if (!strcmp(hint->hint, hint_names[E_POLICY_HINT_STACK_BASE]))
          {
             if (hint->deleted)
               ec->e.state.stack = 0;
             else
               ec->e.state.stack = 1;
          }
        else if (!strcmp(hint->hint, hint_names[E_POLICY_HINT_STACK_ID]))
          {
             ec->icccm.delete_request = 1;
             if (hint->deleted)
               eina_stringshare_replace(&tizen->stack_id, NULL);
             else
               {
                  pid_t pid;
                  int stack_pid;
                  wl_client_get_credentials(wl_resource_get_client(ec->comp_data->surface), &pid, NULL, NULL);
                  stack_pid = strtol(hint->val, NULL, 10);
                  if (stack_pid != pid)
                    wl_client_post_no_memory(wl_resource_get_client(ec->comp_data->surface));
                  if (!tizen->stack_id)
                    evas_object_smart_callback_add(ec->frame, "delete_request", _tizen_del_request, ec);
                  eina_stringshare_replace(&tizen->stack_id, hint->val);
               }
          }
        else if (!strcmp(hint->hint, hint_names[E_POLICY_HINT_STACK_MASTER_ID]))
          {
             if (hint->deleted)
               e_client_parent_set(ec, NULL);
             else
               {
                  E_Client *pec;
                  E_CLIENT_FOREACH(pec)
                    {
                       Tizen_Extensions *ptizen;
                       if (e_client_util_ignored_get(pec)) continue;
                       if (e_client_has_xwindow(pec)) continue;
                       if (e_object_is_del(E_OBJECT(pec))) continue;
                       ptizen = pec->comp_data->tizen;
                       if (!ptizen) continue;
                       if (!eina_streq(hint->val, ptizen->stack_id)) continue;
                       if (ec->parent) abort();

                       e_client_parent_set(ec, pec);
                       // find last one
                       for (; pec->stack.next; pec = pec->stack.next);
                       ec->stack.prev = pec;
                       ec->stack.next = NULL;
                       ec->stack.prev->stack.next = ec;
                       break;
                    }
               }
          }
        else if (!strcmp(hint->hint, hint_names[E_POLICY_HINT_STACK_POP_TO]))
          {
             E_Client *tec;
             if (hint->deleted) continue;
             eina_stringshare_replace(&hint->val, NULL);
             if (!ec->parent) continue;
             tec = ec->stack.next;
             if (tec) e_policy_wl_aux_message_send(tec, "stack_del", "1", NULL);
          }
        else send = EINA_FALSE;
        if (send) _e_policy_wl_allowed_aux_hint_send(ec, hint->id);
     }
}

static void
_tzpol_iface_cb_aux_hint_add(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol, struct wl_resource *surf, int32_t id, const char *name, const char *value)
{
   E_Client *ec;
   Eina_Bool res = EINA_FALSE;


   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   res = e_hints_aux_hint_add(ec, id, name, value);

   if (res)
     {
        _e_policy_wl_aux_hint_apply(ec);
        efl_aux_hints_send_allowed_aux_hint(res_tzpol, surf, id);
        EC_CHANGED(ec);
     }
}

static void
_tzpol_iface_cb_aux_hint_add_fd(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol, struct wl_resource *surf, int32_t id, const char *name, int32_t fd)
{
   E_Client *ec;
   Eina_Bool res = EINA_FALSE;


   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   res = e_hints_aux_hint_add_fd(ec, id, name, fd);

   if (res)
     {
        _e_policy_wl_aux_hint_apply(ec);
        efl_aux_hints_send_allowed_aux_hint(res_tzpol, surf, id);
        EC_CHANGED(ec);
     }
}

static void
_tzpol_iface_cb_aux_hint_change(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol, struct wl_resource *surf, int32_t id, const char *value)
{
   E_Client *ec;
   Eina_Bool res = EINA_FALSE;

   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   res = e_hints_aux_hint_change(ec, id, value);

   if (res)
     {
        _e_policy_wl_aux_hint_apply(ec);
        efl_aux_hints_send_allowed_aux_hint(res_tzpol, surf, id);
        EC_CHANGED(ec);
     }
}

static void
_tzpol_iface_cb_aux_hint_change_fd(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol, struct wl_resource *surf, int32_t id, int32_t fd)
{
   E_Client *ec;
   Eina_Bool res = EINA_FALSE;

   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   res = e_hints_aux_hint_change_fd(ec, id, fd);

   if (res)
     {
        _e_policy_wl_aux_hint_apply(ec);
        efl_aux_hints_send_allowed_aux_hint(res_tzpol, surf, id);
        EC_CHANGED(ec);
     }
}

static void
_tzpol_iface_cb_aux_hint_del(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol EINA_UNUSED, struct wl_resource *surf, int32_t id)
{
   E_Client *ec;
   unsigned int res = -1;

   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   res = e_hints_aux_hint_del(ec, id);

   if (res)
     {
        _e_policy_wl_aux_hint_apply(ec);
        EC_CHANGED(ec);
     }
}

static void
_tzpol_iface_cb_supported_aux_hints_get(struct wl_client *client EINA_UNUSED, struct wl_resource *res_tzpol, struct wl_resource *surf)
{
   E_Client *ec;
   const Eina_List *hints_list;
   const Eina_List *l;
   struct wl_array hints;
   const char *hint_name;
   int len;
   char *p;

   ec = wl_resource_get_user_data(surf);
   EINA_SAFETY_ON_NULL_RETURN(ec);

   hints_list = e_hints_aux_hint_supported_get();

   wl_array_init(&hints);
   EINA_LIST_FOREACH(hints_list, l, hint_name)
     {
        len = strlen(hint_name) + 1;
        p = wl_array_add(&hints, len);

        if (p == NULL)
          break;
        strncpy(p, hint_name, len);
     }

   efl_aux_hints_send_supported_aux_hints(res_tzpol, surf, &hints, eina_list_count(hints_list));
   wl_array_release(&hints);
}

E_API void
e_policy_wl_aux_message_send(E_Client *ec,
                             const char *key,
                             const char *val,
                             Eina_List *options)
{
   Eina_List *l;
   struct wl_client *wc;
   struct wl_resource *res;
   struct wl_array opt_array;
   const char *option;
   int len;
   char *p;

   if (!ec->comp_data) return;
   if (!ec->comp_data->aux_hint.use_msg) return;
   wc = wl_resource_get_client(ec->comp_data->surface);
   EINA_LIST_FOREACH(aux_hints, l, res)
     if (wl_resource_get_client(res) == wc) break;
   if (!res) return;

   wl_array_init(&opt_array);
   EINA_LIST_FOREACH(options, l, option)
     {
        len = strlen(option) + 1;
        p = wl_array_add(&opt_array, len);

        if (p == NULL)
          break;
        strncpy(p, option, len);
     }

   efl_aux_hints_send_aux_message(res, ec->comp_data->surface,
                                 key, val, &opt_array);
   wl_array_release(&opt_array);
}

void
e_policy_wl_aux_hint_init(void)
{
   int i, n;
   //E_Config_Aux_Hint_Supported *auxhint;
   //Eina_List *l;

   n = (sizeof(hint_names) / sizeof(char *));

   for (i = 0; i < n; i++)
     {
        e_hints_aux_hint_supported_add(hint_names[i]);
     }

   //EINA_LIST_FOREACH(e_config->aux_hint_supported, l, auxhint)
     //{
        //if (!auxhint->name) continue;
        //e_hints_aux_hint_supported_add(auxhint->name);
     //}

   return;
}

#define GLOBAL_BIND_CB(NAME, IFACE, ...) \
static void \
_e_comp_wl_##NAME##_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version EINA_UNUSED, uint32_t id) \
{ \
   struct wl_resource *res; \
\
   if (!(res = wl_resource_create(client, &(IFACE), 1, id))) \
     { \
        ERR("Could not create %s interface", #NAME);\
        wl_client_post_no_memory(client);\
        return;\
     }\
\
   wl_resource_set_implementation(res, &_e_##NAME##_interface, NULL, NULL);\
}

static const struct efl_aux_hints_interface _e_efl_aux_hints_interface =
{
   _tzpol_iface_cb_aux_hint_add,
   _tzpol_iface_cb_aux_hint_change,
   _tzpol_iface_cb_aux_hint_del,
   _tzpol_iface_cb_supported_aux_hints_get,
   _tzpol_iface_cb_aux_hint_add_fd,
   _tzpol_iface_cb_aux_hint_change_fd,
};

static void
_efl_aux_hints_destroy(struct wl_resource *res)
{
   aux_hints = eina_list_remove(aux_hints, res);
}

static void
_e_comp_wl_efl_aux_hints_cb_bind(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!(res = wl_resource_create(client, &efl_aux_hints_interface, MIN(version, 2), id)))
     {
        ERR("Could not create %s interface", "efl-aux-hints");
        wl_client_post_no_memory(client);
        return;
     }

   wl_resource_set_implementation(res, &_e_efl_aux_hints_interface, NULL, _efl_aux_hints_destroy);
   aux_hints = eina_list_append(aux_hints, res);
}

#define GLOBAL_CREATE_OR_RETURN(NAME, IFACE, VERSION) \
   do { \
      struct wl_global *global; \
\
      global = wl_global_create(e_comp_wl->wl.disp, &(IFACE), VERSION, \
                                NULL, _e_comp_wl_##NAME##_cb_bind); \
      if (!global) \
        { \
           ERR("Could not add %s to wayland globals", #IFACE); \
           return EINA_FALSE; \
        } \
      e_comp_wl->extensions->NAME.global = global; \
   } while (0)

EINTERN Eina_Bool
e_comp_wl_extensions_tizen_init(void)
{
   GLOBAL_CREATE_OR_RETURN(efl_aux_hints, efl_aux_hints_interface, 1);
   e_policy_wl_aux_hint_init();
   return EINA_TRUE;
}
#define EXECUTIVE_MODE_ENABLED
#define E_COMP_WL
#include "e.h"
#include <sys/mman.h>
#ifdef HAVE_WL_DRM
#include <Ecore_Drm2.h>
#endif
#include <Efl_Canvas_Wl.h>

E_API int E_EVENT_TEXT_INPUT_PANEL_VISIBILITY_CHANGE = -1;
static xkb_keycode_t (*_xkb_keymap_key_by_name)(void *, const char *);
static void _e_comp_wl_input_context_keymap_set(struct xkb_keymap *keymap, struct xkb_context *context);

static Eina_Hash *input_gen_modifiers;

//the following two fields are just set by e_comp_wl_input_keymap_set if it is called before e_comp_wl is valid.
//when then later init is called those two fields are used in the keymap of the e_comp_wl struct
static struct xkb_context *cached_context;
static struct xkb_keymap *cached_keymap;
static xkb_layout_index_t choosen_group;

static void
_e_comp_wl_input_update_seat_caps(void)
{
   Eina_List *l;
   struct wl_resource *res;
   enum wl_seat_capability caps = 0;

   if (e_comp_wl->ptr.enabled)
     caps |= WL_SEAT_CAPABILITY_POINTER;
   if (e_comp_wl->kbd.enabled)
     caps |= WL_SEAT_CAPABILITY_KEYBOARD;
   if (e_comp_wl->touch.enabled)
     caps |= WL_SEAT_CAPABILITY_TOUCH;

   EINA_LIST_FOREACH(e_comp_wl->seat.resources, l, res)
     wl_seat_send_capabilities(res, caps);
}

static void
_e_comp_wl_input_cb_resource_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static void
_e_comp_wl_input_pointer_cb_cursor_set(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t serial EINA_UNUSED, struct wl_resource *surface_resource, int32_t x, int32_t y)
{
   E_Client *ec;
   E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(resource);

   if (!surface_resource)
     {
        ptr->cursor_set = 1;
        ptr->cursor = NULL;
        ptr->offset.x = x;
        ptr->offset.y = y;
        if (ptr->entered)
          {
             ecore_evas_cursor_unset(e_comp->ee);
             evas_object_hide(e_comp->pointer->o_ptr);
          }
        return;
     }
   ec = wl_resource_get_user_data(surface_resource);
   /* I think this only happens when we've deleted the resource from
    * the client del callback - so the client is gone and shouldn't be
    * setting a cursor, but the surface still exists so stale requests
    * are being processed... let's BAIL.
    */
   if (!ec) return;
   ptr->cursor_set = 1;
   ptr->cursor = ec;
   ptr->offset.x = x;
   ptr->offset.y = y;
   if (ec->comp_data->pending.input)
     eina_tiler_clear(ec->comp_data->pending.input);
   else
     {
        ec->comp_data->pending.input = eina_tiler_new(65535, 65535);
        eina_tiler_tile_size_set(ec->comp_data->pending.input, 1, 1);
     }
   if (!ec->re_manage)
     {
        ec->comp_data->cursor = ec->re_manage = 1;
        ec->ignored = 0;
        evas_object_layer_set(ec->frame, E_LAYER_CLIENT_PRIO);

        ec->lock_focus_out = ec->layer_block = ec->visible = ec->override = 1;
        ec->icccm.title = eina_stringshare_add("noshadow");
        evas_object_pass_events_set(ec->frame, 1);
        evas_object_show(ec->frame);
        if (e_comp_object_damage_exists(ec->frame))
          e_comp_object_render_update_add(ec->frame);
        ec->comp_data->mapped = 1;
        e_client_focus_stack_set(eina_list_remove(e_client_focus_stack_get(), ec));
        EC_CHANGED(ec);
     }

   if (ptr->entered)
     e_comp_wl_input_pointer_cursor_update(ptr);
}

static const struct wl_pointer_interface _e_pointer_interface =
{
   _e_comp_wl_input_pointer_cb_cursor_set,
   _e_comp_wl_input_cb_resource_destroy
};

static const struct wl_keyboard_interface _e_keyboard_interface =
{
   _e_comp_wl_input_cb_resource_destroy
};

static const struct wl_touch_interface _e_touch_interface =
{
   _e_comp_wl_input_cb_resource_destroy
};

static void
_e_comp_wl_input_cb_pointer_unbind(struct wl_resource *resource)
{
   E_Comp_Wl_Pointer *ptr = wl_resource_get_user_data(resource);

   if (ptr->cursor_set && ptr->entered)
     e_pointer_object_set(e_comp->pointer, NULL, 0, 0);
   e_comp_wl->ptr.resources =
     eina_list_remove(e_comp_wl->ptr.resources, resource);
   free(ptr);
}

static void
_e_comp_wl_input_cb_pointer_get(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   struct wl_resource *res;
   E_Comp_Wl_Pointer *ptr;

   /* try to create pointer resource */
   res = wl_resource_create(client, &wl_pointer_interface,
                            wl_resource_get_version(resource), id);
   if (!res)
     {
        ERR("Could not create pointer on seat %s",
            e_comp_wl->seat.name);
        wl_client_post_no_memory(client);
        return;
     }
   ptr = E_NEW(E_Comp_Wl_Pointer, 1);

   e_comp_wl->ptr.resources =
     eina_list_append(e_comp_wl->ptr.resources, res);
   wl_resource_set_implementation(res, &_e_pointer_interface,
                                 ptr,
                                 _e_comp_wl_input_cb_pointer_unbind);
}

static void
_e_comp_wl_input_cb_keyboard_unbind(struct wl_resource *resource)
{
   Eina_List *l, *ll;
   struct wl_resource *res;

   e_comp_wl->kbd.resources =
     eina_list_remove(e_comp_wl->kbd.resources, resource);
   EINA_LIST_FOREACH_SAFE(e_comp_wl->kbd.focused, l, ll, res)
     {
        if (res == resource)
          {
             e_comp_wl->kbd.focused =
               eina_list_remove_list(e_comp_wl->kbd.focused, l);
          }
     }
}

void
e_comp_wl_input_keyboard_enter_send(E_Client *ec)
{
   struct wl_resource *res;
   Eina_List *l;
   uint32_t serial;

   if (!ec->comp_data->surface) return;

   if (!e_comp_wl->kbd.focused) return;

   e_comp_wl_input_keyboard_modifiers_serialize();

   serial = wl_display_next_serial(e_comp_wl->wl.disp);

   EINA_LIST_FOREACH(e_comp_wl->kbd.focused, l, res)
     {
        wl_keyboard_send_enter(res, serial, ec->comp_data->surface,
                               &e_comp_wl->kbd.keys);
        wl_keyboard_send_modifiers(res, serial,
                                   e_comp_wl->kbd.mod_depressed,
                                   e_comp_wl->kbd.mod_latched,
                                   e_comp_wl->kbd.mod_locked,
                                   e_comp_wl->kbd.mod_group);
     }
}

static void
_e_comp_wl_input_cb_keyboard_get(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   E_Client *focused;
   struct wl_resource *res;

   /* try to create keyboard resource */
   res = wl_resource_create(client, &wl_keyboard_interface,
                            wl_resource_get_version(resource), id);
   if (!res)
     {
        ERR("Could not create keyboard on seat %s", e_comp_wl->seat.name);
        wl_client_post_no_memory(client);
        return;
     }
   wl_resource_set_user_data(res, resource);

   e_comp_wl->kbd.resources =
     eina_list_append(e_comp_wl->kbd.resources, res);
   wl_resource_set_implementation(res, &_e_keyboard_interface,
                                  e_comp->wl_comp_data,
                                  _e_comp_wl_input_cb_keyboard_unbind);

   /* send current repeat_info */
   if (wl_resource_get_version(res) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)
     wl_keyboard_send_repeat_info(res, e_config->keyboard.repeat_rate, e_config->keyboard.repeat_delay);

   /* send current keymap */
   e_comp_wl_input_keymap_send(res);

   /* if the client owns the focused surface, we need to send an enter */
   focused = e_client_focused_get();
   if (!focused) return;

   if (client != wl_resource_get_client(focused->comp_data->surface)) return;
   e_comp_wl->kbd.focused = eina_list_append(e_comp_wl->kbd.focused, res);

   e_comp_wl_input_keyboard_enter_send(focused);
}

static void
_e_comp_wl_input_cb_touch_unbind(struct wl_resource *resource)
{
   e_comp_wl->touch.resources =
     eina_list_remove(e_comp_wl->touch.resources, resource);
}

static void
_e_comp_wl_input_cb_touch_get(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t id EINA_UNUSED)
{
   struct wl_resource *res;

    /* try to create pointer resource */
   res = wl_resource_create(client, &wl_touch_interface,
                            wl_resource_get_version(resource), id);
   if (!res)
     {
        ERR("Could not create touch on seat %s",
            e_comp_wl->seat.name);
        wl_client_post_no_memory(client);
        return;
     }

   e_comp_wl->touch.resources =
     eina_list_append(e_comp_wl->touch.resources, res);
   wl_resource_set_implementation(res, &_e_touch_interface,
                                  e_comp->wl_comp_data,
                                  _e_comp_wl_input_cb_touch_unbind);
}

static void
_e_comp_wl_input_cb_release(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

static const struct wl_seat_interface _e_seat_interface =
{
   _e_comp_wl_input_cb_pointer_get,
   _e_comp_wl_input_cb_keyboard_get,
   _e_comp_wl_input_cb_touch_get,
   _e_comp_wl_input_cb_release,
};

static void
_e_comp_wl_input_cb_unbind_seat(struct wl_resource *resource)
{
   e_comp_wl->seat.resources =
     eina_list_remove(e_comp_wl->seat.resources, resource);
}

static void
_e_comp_wl_input_cb_bind_seat(struct wl_client *client, void *data EINA_UNUSED, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   res = wl_resource_create(client, &wl_seat_interface, version, id);
   if (!res)
     {
        ERR("Could not create seat resource");
        return;
     }

   /* store version of seat interface for reuse in updating capabilities */
   e_comp_wl->seat.version = version;
   e_comp_wl->seat.resources =
     eina_list_append(e_comp_wl->seat.resources, res);

   wl_resource_set_implementation(res, &_e_seat_interface,
                                  e_comp->wl_comp_data,
                                  _e_comp_wl_input_cb_unbind_seat);

   _e_comp_wl_input_update_seat_caps();
   if (e_comp_wl->seat.version >= WL_SEAT_NAME_SINCE_VERSION)
     wl_seat_send_name(res, e_comp_wl->seat.name);
}

int
_e_comp_wl_input_keymap_fd_get(void)
{
   int fd = 0, blen = 0, len = 0;
   const char *path;
   char tmp[PATH_MAX];
   long flags;
   void *mm;
   Eina_Tmpstr *tmpstr = NULL;

   blen = sizeof(tmp) - 1;

   if (!(path = getenv("XDG_RUNTIME_DIR")))
     return -1;

   len = strlen(path);
   if (len < blen)
     {
        strcpy(tmp, path);
        strcat(tmp, "/e-wl-keymap-XXXXXX");
     }
   else
     return -1;

   if ((fd = eina_file_mkstemp(tmp, &tmpstr)) < 0) return -1;

   flags = fcntl(fd, F_GETFD);
   if (flags < 0)
     {
        eina_tmpstr_del(tmpstr);
        close(fd);
        return -1;
     }

   if (fcntl(fd, F_SETFD, (flags | FD_CLOEXEC)) == -1)
     {
        eina_tmpstr_del(tmpstr);
        close(fd);
        return -1;
     }

   if (ftruncate(fd, e_comp_wl->xkb.map_size) < 0)
     {
        eina_tmpstr_del(tmpstr);
        close(fd);
        return -1;
     }

   unlink(tmpstr);
   eina_tmpstr_del(tmpstr);

   mm = mmap(NULL, e_comp_wl->xkb.map_size, (PROT_READ | PROT_WRITE),
             MAP_SHARED, fd, 0);
   if (mm == MAP_FAILED)
     {
        ERR("Failed to mmap keymap area: %m");
        close(fd);
        return -1;
     }

   memcpy(mm, e_comp_wl->xkb.map_string, e_comp_wl->xkb.map_size);
   munmap(mm, e_comp_wl->xkb.map_size);

   return fd;
}

static void
_e_comp_wl_input_state_update(void)
{
   xkb_mod_mask_t latched = 0, locked = 0;

   /* unreference any existing keyboard state */
   if (e_comp_wl->xkb.state)
     {
        latched =
          xkb_state_serialize_mods(e_comp_wl->xkb.state,
                                   XKB_STATE_MODS_LATCHED);
        locked =
          xkb_state_serialize_mods(e_comp_wl->xkb.state,
                                   XKB_STATE_MODS_LOCKED);
        xkb_state_unref(e_comp_wl->xkb.state);
     }

   /* create a new xkb state */
   e_comp_wl->xkb.state = xkb_state_new(e_comp_wl->xkb.keymap);

   xkb_state_update_mask(e_comp_wl->xkb.state, 0,
                         latched, locked, e_comp_wl->kbd.choosen_group,
                         0, 0);
}

void
e_comp_wl_input_keymap_send(struct wl_resource *res)
{
   int fd;

   fd = _e_comp_wl_input_keymap_fd_get();
   if (fd == -1)
     return;

   wl_keyboard_send_keymap(res, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
                           fd, e_comp_wl->xkb.map_size);

   close(fd);
}

static void
nested_keymap_update(void)
{
   Eina_List *l;
   Evas_Object *obj;

   EINA_LIST_FOREACH(e_comp_wl->efl_wls, l, obj)
     efl_canvas_wl_seat_keymap_set(obj, NULL, e_comp_wl->xkb.state, e_comp_wl->xkb.map_string, &e_comp_wl->kbd.keys);
}

static void
_e_comp_wl_input_keymap_update(struct xkb_keymap *keymap)
{
   struct wl_resource *res;
   Eina_List *l;

   /* unreference any existing keymap */
   if (e_comp_wl->xkb.keymap)
     xkb_map_unref(e_comp_wl->xkb.keymap);

   /* free any existing keyboard map string */
   free(e_comp_wl->xkb.map_string);
   e_comp_wl->xkb.map_string = NULL;

   /* increment keymap reference */
   e_comp_wl->xkb.keymap = keymap;

   /* update the state */
   _e_comp_wl_input_state_update();

   if (!(e_comp_wl->xkb.map_string = xkb_map_get_as_string(keymap)))
     {
        ERR("Could not get keymap string");
        return;
     }

   e_comp_wl->xkb.map_size = strlen(e_comp_wl->xkb.map_string) + 1;

   /* send updated keymap */
   EINA_LIST_FOREACH(e_comp_wl->kbd.resources, l, res)
     e_comp_wl_input_keymap_send(res);

   /* update modifiers */
   e_comp_wl_input_keyboard_modifiers_update();
   nested_keymap_update();
}

EINTERN Eina_Bool
e_comp_wl_input_init(void)
{
   /* set default seat name */
   if (!e_comp_wl->seat.name)
     e_comp_wl->seat.name = "seat0";

   /* create the global resource for input seat */
   e_comp_wl->seat.global =
     wl_global_create(e_comp_wl->wl.disp, &wl_seat_interface, 4,
                      e_comp->wl_comp_data, _e_comp_wl_input_cb_bind_seat);
   if (!e_comp_wl->seat.global)
     {
        ERR("Could not create global for seat");
        return EINA_FALSE;
     }

   wl_array_init(&e_comp_wl->kbd.keys);

   E_EVENT_TEXT_INPUT_PANEL_VISIBILITY_CHANGE = ecore_event_type_new();

   _xkb_keymap_key_by_name = dlsym(NULL, "xkb_keymap_key_by_name");

    if (cached_keymap)
      _e_comp_wl_input_context_keymap_set(cached_keymap, cached_context);
    else
      e_comp_wl_input_keymap_set(NULL, NULL, NULL, NULL, NULL);

    if (choosen_group)
      e_comp_wl_input_keymap_index_set(choosen_group);
    else
      e_comp_wl_input_keymap_index_set(0);

   e_comp_wl_input_keyboard_modifiers_update();

   return EINA_TRUE;
}

EINTERN void
e_comp_wl_input_shutdown(void)
{
   struct wl_resource *res;

   E_FREE_FUNC(input_gen_modifiers, eina_hash_free);

   /* destroy pointer resources */
   EINA_LIST_FREE(e_comp_wl->ptr.resources, res)
     wl_resource_destroy(res);

   /* destroy keyboard resources */
   EINA_LIST_FREE(e_comp_wl->kbd.resources, res)
     wl_resource_destroy(res);
   e_comp_wl->kbd.resources = eina_list_free(e_comp_wl->kbd.resources);

   /* destroy touch resources */
   EINA_LIST_FREE(e_comp_wl->touch.resources, res)
     wl_resource_destroy(res);

   /* destroy e_comp_wl->kbd.keys array */
   wl_array_release(&e_comp_wl->kbd.keys);

   /* free the string copy of the keyboard map */
   free(e_comp_wl->xkb.map_string);

   /* unreference any existing keyboard state */
   if (e_comp_wl->xkb.state)
     xkb_state_unref(e_comp_wl->xkb.state);

   /* unreference any existing keymap */
   if (e_comp_wl->xkb.keymap)
     xkb_map_unref(e_comp_wl->xkb.keymap);

   /* unreference any existing context */
   if (e_comp_wl->xkb.context)
     xkb_context_unref(e_comp_wl->xkb.context);

   /* destroy the global seat resource */
   if (e_comp_wl->seat.global)
     wl_global_destroy(e_comp_wl->seat.global);
   e_comp_wl->seat.global = NULL;
}

EINTERN void
e_comp_wl_input_pointer_cursor_update(E_Comp_Wl_Pointer *ptr)
{
   EINA_SAFETY_ON_TRUE_RETURN(!ptr->entered);
   if (ptr->cursor_set && (!e_comp_util_mouse_grabbed()))
     {
        if (ptr->cursor)
          e_pointer_object_set(e_comp->pointer, ptr->cursor->frame, ptr->offset.x, ptr->offset.y);
        else
          {
             ecore_evas_cursor_unset(e_comp->ee);
             evas_object_hide(e_comp->pointer->o_ptr);
          }
     }
   else
     e_pointer_object_set(e_comp->pointer, NULL, 0, 0);
}

EINTERN Eina_Bool
e_comp_wl_input_pointer_check(struct wl_resource *res)
{
   return wl_resource_instance_of(res, &wl_pointer_interface,
                                  &_e_pointer_interface);
}

EINTERN Eina_Bool
e_comp_wl_input_keyboard_check(struct wl_resource *res)
{
   return wl_resource_instance_of(res, &wl_keyboard_interface,
                                  &_e_keyboard_interface);
}

EINTERN Eina_Bool
e_comp_wl_input_keyboard_modifiers_serialize(void)
{
   Eina_Bool changed = EINA_FALSE;
   xkb_mod_mask_t mod;
   xkb_layout_index_t grp;

   mod = xkb_state_serialize_mods(e_comp_wl->xkb.state, XKB_STATE_DEPRESSED);
   changed |= mod != e_comp_wl->kbd.mod_depressed;
   e_comp_wl->kbd.mod_depressed = mod;

   mod = xkb_state_serialize_mods(e_comp_wl->xkb.state, XKB_STATE_MODS_LATCHED);
   changed |= mod != e_comp_wl->kbd.mod_latched;
   e_comp_wl->kbd.mod_latched = mod;

   mod = xkb_state_serialize_mods(e_comp_wl->xkb.state, XKB_STATE_MODS_LOCKED);
   changed |= mod != e_comp_wl->kbd.mod_locked;
   e_comp_wl->kbd.mod_locked = mod;

   grp = xkb_state_serialize_layout(e_comp_wl->xkb.state,
                                    XKB_STATE_LAYOUT_EFFECTIVE);
   if (grp != e_comp_wl->kbd.mod_group)
     {
        e_config->xkb.cur_group = grp;
        ecore_event_add(E_EVENT_XKB_CHANGED, NULL, NULL, NULL);
     }
   changed |= grp != e_comp_wl->kbd.mod_group;
   e_comp_wl->kbd.mod_group = grp;
   return changed;
}

EINTERN void
e_comp_wl_input_keyboard_modifiers_update(void)
{
   uint32_t serial;
   struct wl_resource *res;
   Eina_List *l;

   if (!e_comp_wl_input_keyboard_modifiers_serialize()) return;

   if ((!e_comp_wl->kbd.focused) || e_comp->input_key_grabs) return;

   serial = wl_display_next_serial(e_comp_wl->wl.disp);
   EINA_LIST_FOREACH(e_comp_wl->kbd.focused, l, res)
     wl_keyboard_send_modifiers(res, serial,
                                e_comp_wl->kbd.mod_depressed,
                                e_comp_wl->kbd.mod_latched,
                                e_comp_wl->kbd.mod_locked,
                                e_comp_wl->kbd.mod_group);
}

EINTERN void
e_comp_wl_input_keyboard_state_update(uint32_t keycode, Eina_Bool pressed)
{
   enum xkb_key_direction dir;

   if (!e_comp_wl->xkb.state) return;

   if (pressed) dir = XKB_KEY_DOWN;
   else dir = XKB_KEY_UP;

   e_comp_wl->kbd.mod_changed =
     xkb_state_update_key(e_comp_wl->xkb.state, keycode + 8, dir);

   e_comp_wl_input_keyboard_modifiers_update();
}

E_API void
e_comp_wl_input_pointer_enabled_set(Eina_Bool enabled)
{
   /* check for valid compositor data */
   if (!e_comp->wl_comp_data)
     {
        ERR("No compositor data");
        return;
     }

   e_comp_wl->ptr.enabled = !!enabled;
   _e_comp_wl_input_update_seat_caps();
}

E_API void
e_comp_wl_input_keyboard_enabled_set(Eina_Bool enabled)
{
   /* check for valid compositor data */
   if (!e_comp->wl_comp_data)
     {
        ERR("No compositor data");
        return;
     }

   e_comp_wl->kbd.enabled = !!enabled;
   _e_comp_wl_input_update_seat_caps();
}

static void
_e_comp_wl_input_context_keymap_set(struct xkb_keymap *keymap, struct xkb_context *context)
{
   if (e_comp_wl)
     {
        //we have a initializied and running compositor here
        if (e_comp_wl->xkb.context)
          xkb_context_unref(e_comp_wl->xkb.context);
        e_comp_wl->xkb.context = context;

        /* update compositor keymap */
        _e_comp_wl_input_keymap_update(keymap);
     }
   else
     {
        //we dont have a running compositor here, cache the values, init will pick them
        cached_context = context;
        cached_keymap = keymap;
     }

   if (!e_comp->ee) return;
//set the values to the drm devices
#ifdef HAVE_WL_DRM
   if (strstr(ecore_evas_engine_name_get(e_comp->ee), "drm"))
     {
        Ecore_Drm2_Device *dev;

        dev = ecore_evas_data_get(e_comp->ee, "device");
        if (dev)
          ecore_drm2_device_keyboard_info_set(dev, context, keymap,
            e_comp_wl ? e_comp_wl->kbd.choosen_group : choosen_group);
     }
#endif
}

E_API void
e_comp_wl_input_keymap_index_set(xkb_layout_index_t index)
{
#ifdef HAVE_WL_DRM
   if (e_comp && e_comp->ee && strstr(ecore_evas_engine_name_get(e_comp->ee), "drm"))
     {
        Ecore_Drm2_Device *dev;

        dev = ecore_evas_data_get(e_comp->ee, "device");
        if (dev)
          ecore_drm2_device_keyboard_group_set(dev, index);
     }
#endif
   if (e_comp_wl)
     {
        e_comp_wl->kbd.choosen_group = index;
        _e_comp_wl_input_state_update();
        e_comp_wl_input_keyboard_modifiers_update();
        nested_keymap_update();
     }
   else
     choosen_group = index;
}

E_API void
e_comp_wl_input_keymap_set(const char *rules, const char *model, const char *layout, const char *variant, const char *options)
{
   struct xkb_keymap *keymap;
   struct xkb_context *context;
   struct xkb_rule_names names;

   /* DBG("COMP_WL: Keymap Set: %s %s %s", rules, model, layout); */

   /* assemble xkb_rule_names so we can fetch keymap */
   memset(&names, 0, sizeof(names));

   if (rules) names.rules = rules;
   else names.rules = "evdev";

   if (model) names.model = model;
   else names.model = "pc105";

   if (layout) names.layout = layout;
   else names.layout = "us";

   if (variant) names.variant = variant;
   else names.variant = NULL;

   if (options) names.options = options;
   else names.options = NULL;

   /* create a new xkb context */
   context = xkb_context_new(0);

   /* fetch new keymap based on names */
   keymap = xkb_map_new_from_names(context, &names, 0);

   if (!keymap)
     {
        ERR("Failed to compile keymap");
        return;
     }

   _e_comp_wl_input_context_keymap_set(keymap, context);
}

E_API void
e_comp_wl_input_touch_enabled_set(Eina_Bool enabled)
{
   /* check for valid compositor data */
   if (!e_comp->wl_comp_data)
     {
        ERR("No compositor data");
        return;
     }

   e_comp_wl->touch.enabled = !!enabled;
   _e_comp_wl_input_update_seat_caps();
}

EINTERN Eina_Bool
e_comp_wl_input_touch_check(struct wl_resource *res)
{
   return wl_resource_instance_of(res, &wl_touch_interface,
                                  &_e_touch_interface);
}

EINTERN void
e_comp_wl_input_keyboard_modifers_clear(void)
{
   e_comp_wl->kbd.mod_depressed = 0;
   e_comp_wl->kbd.mod_latched = 0;
   e_comp_wl->kbd.mod_locked = 0;
   e_comp_wl->kbd.mod_group = 0;

   e_comp_wl_input_keyboard_modifiers_serialize();
}

static void
_event_generate(const char *key, const char *keyname, int mods, Eina_Bool up)
{
   Ecore_Event_Key *ev;
   int keycode;

   keycode = _xkb_keymap_key_by_name(e_comp_wl->xkb.keymap, keyname ?: key);
   if (keycode == -1)
     {
        ERR("no keycode found for key '%s'", keyname ?: key);
        return;
     }
   ev = calloc(1, sizeof(Ecore_Event_Key) + (2 * (strlen(key) + 1)));

   ev->keyname = (char *)(ev + 1);
   ev->key = ev->keyname + strlen(key) + 1;

   strcpy((char *)ev->keyname, key);
   strcpy((char *)ev->key, key);

   ev->window = e_comp->ee_win;
   ev->event_window = e_comp->ee_win;
   ev->timestamp = 0;
   ev->modifiers = mods;
   ev->keycode = keycode;
   ecore_event_add(up ? ECORE_EVENT_KEY_UP : ECORE_EVENT_KEY_DOWN, ev, NULL, NULL);
}

static void
_event_generate_mods(int mods, Eina_Bool up)
{
   if (!mods) return;
   if (mods & ECORE_EVENT_MODIFIER_SHIFT)
     _event_generate("Shift", "LFSH", mods, up);
   if (mods & ECORE_EVENT_MODIFIER_CTRL)
     _event_generate("Control_L", "LCTL", mods, up);
   if (mods & ECORE_EVENT_MODIFIER_ALT)
     _event_generate("Alt_L", "LALT", mods, up);
   if (mods & ECORE_EVENT_MODIFIER_WIN)
     _event_generate("Super_L", "LWIN", mods, up);
   if (mods & ECORE_EVENT_MODIFIER_ALTGR)
     _event_generate("Mode_switch", "ALGR", mods, up);
}

E_API void
e_comp_wl_input_keyboard_event_generate(const char *key, int mods, Eina_Bool up)
{
   const char *keyname = NULL;
   /* assumes qwerty layout */
   /* /usr/share/X11/xkb/keycodes/evdev */
   static const char *keycodes[] =
   {
      ['`'] = "TLDE",
      ['1'] = "AE01",
      ['2'] = "AE02",
      ['3'] = "AE03",
      ['4'] = "AE04",
      ['5'] = "AE05",
      ['6'] = "AE06",
      ['7'] = "AE07",
      ['8'] = "AE08",
      ['9'] = "AE09",
      ['0'] = "AE10",
      ['-'] = "AE11",
      ['='] = "AE12",
      //''] = "BKSP",
      ['\t'] = "TAB",
      ['q'] = "AD01",
      ['w'] = "AD02",
      ['e'] = "AD03",
      ['r'] = "AD04",
      ['t'] = "AD05",
      ['y'] = "AD06",
      ['u'] = "AD07",
      ['i'] = "AD08",
      ['o'] = "AD09",
      ['p'] = "AD10",
      ['['] = "AD11",
      [']'] = "AD12",
      ['\\'] = "BKSL",
      ['\r'] = "RTRN",
      //''] = "CAPS",
      ['a'] = "AC01",
      ['s'] = "AC02",
      ['d'] = "AC03",
      ['f'] = "AC04",
      ['g'] = "AC05",
      ['h'] = "AC06",
      ['j'] = "AC07",
      ['k'] = "AC08",
      ['l'] = "AC09",
      [';'] = "AC10",
      ['\''] = "AC11",
      //''] = "LFSH",
      ['z'] = "AB01",
      ['x'] = "AB02",
      ['c'] = "AB03",
      ['v'] = "AB04",
      ['b'] = "AB05",
      ['n'] = "AB06",
      ['m'] = "AB07",
      [','] = "AB08",
      ['.'] = "AB09",
      ['/'] = "AB10",
      //''] = "RTSH",
      [' '] = "SPCE",
   };

   if (!_xkb_keymap_key_by_name)
     {
        ERR("xkbcommon >= 0.6.0 required for keyboard event generation!");
        return;
     }
   EINA_SAFETY_ON_NULL_RETURN(key);
   EINA_SAFETY_ON_TRUE_RETURN(!key[0]);
   if (!input_gen_modifiers)
     {
//<RTSH> = 62;
//<LALT> = 64;
//<LCTL> = 37;
//<RCTL> = 105;
//<RALT> = 108;
//<LWIN> = 133;
//<RWIN> = 134;
//<COMP> = 135;
//alias <MENU> = <COMP>;
//<ESC> = 9;
        static const char *modcodes[] =
        {
           "Shift_L",
           "LFSH",

           "Control_L",
           "LCTL",

           "Super_L",
           "LWIN",

           "Alt_L",
           "LALT",

           "Escape",
           "ESC",

           "Alt_R",
           "RALT",

           "Super_R",
           "RWIN",

           "Menu",
           "MENU",

           "Control_R",
           "RCTRL",

           "Mode_switch",
           "ALGR",

           "Return",
           "RTRN",

           "Caps_Lock",
           "CAPS",
        };
        unsigned int i;

        input_gen_modifiers = eina_hash_string_superfast_new(NULL);
        for (i = 0; i < EINA_C_ARRAY_LENGTH(modcodes); i += 2)
          eina_hash_add(input_gen_modifiers, modcodes[i], modcodes[i + 1]);
     }

   if (!up)
     _event_generate_mods(mods, up);
   keyname = eina_hash_find(input_gen_modifiers, key);
   if ((!keyname) && (!key[1]))
     {
        if (key[0] < (int)EINA_C_ARRAY_LENGTH(keycodes))
          keyname = keycodes[(unsigned char)key[0]];
     }
   _event_generate(key, keyname, mods, up);
   if (up)
     _event_generate_mods(mods, up);
}
#define EXECUTIVE_MODE_ENABLED
#define E_COMP_X
#include "e.h"

#define RANDR_VERSION_1_3 ((1 << 16) | 3)
#define RANDR_VERSION_1_4 ((1 << 16) | 4)

#define GRAV_SET(ec, grav)                                                         \
  ecore_x_window_gravity_set(e_client_util_pwin_get(ec), grav);                          \
  if (_e_comp_x_client_data_get(ec)->lock_win) ecore_x_window_gravity_set(_e_comp_x_client_data_get(ec)->lock_win, grav);

#ifdef HAVE_WAYLAND
# define E_COMP_X_PIXMAP_CHECK if ((e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_X) && (!e_client_has_xwindow(ec))) return
#else
# define E_COMP_X_PIXMAP_CHECK if (!e_pixmap_is_x(ec->pixmap)) return
#endif
/* maybe make configurable?
 * not sure why anyone would want to change it...
 */
#define MOVE_COUNTER_LIMIT 50

#define PARENT_ACTIVATE_TIME 200
#define PARENT_ACTIVATE_LIMIT 2

EINTERN void _e_main_cb_x_fatal(void *data EINA_UNUSED);

typedef struct _Frame_Extents Frame_Extents;

struct _Frame_Extents
{
   int l, r, t, b;
};

struct _E_Comp_X_Data
{
   Ecore_X_Window lock_grab_break_wnd;

   Eina_List *retry_clients;
   Ecore_Timer *retry_timer;
   Eina_Bool restack E_BITFIELD;
};

typedef struct Pending_Configure
{
   Evas_Point point;
   Ecore_X_Window win;
   Ecore_Timer *timer;
} Pending_Configure;

static Eina_Hash *pending_configures;

static unsigned int focus_time = 0;
static unsigned int focus_canvas_time = 0;
static Ecore_Timer *focus_timer;
static E_Client *mouse_client;
static Eina_List *handlers = NULL;
static Eina_Hash *clients_win_hash = NULL;
static Eina_Hash *damages_hash = NULL;
static Eina_Hash *frame_extents = NULL;
static Eina_Hash *alarm_hash = NULL;

static Evas_Point mouse_in_coords = {-1, -1};
static Ecore_Job *mouse_in_job;
static E_Client *focus_job_client;
static Ecore_Job *focus_job;
static E_Client *unfocus_job_client;
static Ecore_Job *unfocus_job;
static Ecore_Idle_Enterer *_e_comp_x_post_client_idler = NULL;
static Ecore_Idle_Enterer *_x_idle_flush = NULL;
static Eina_List *post_clients = NULL;

static int _e_comp_x_mapping_change_disabled = 0;

static Ecore_X_Randr_Screen_Size screen_size = { -1, -1 };
static int screen_size_index = -1;

static Ecore_X_Atom backlight_atom = 0;

static Ecore_Timer *mouse_in_fix_check_timer = NULL;

static Eina_Hash *dead_wins;

static Ecore_Window _e_comp_x_suspend_grabbed; // window grabber for suspending pointer

static void _e_comp_x_hook_client_pre_frame_assign(void *d EINA_UNUSED, E_Client *ec);

static inline E_Comp_X_Client_Data *
_e_comp_x_client_data_get(const E_Client *ec)
{
#ifdef HAVE_WAYLAND
   if (!e_pixmap_is_x(ec->pixmap))
     return e_comp_wl_client_xwayland_data(ec);
#endif
   return ec->comp_data;
}

static Eina_Bool
_e_comp_x_flusher(void *data EINA_UNUSED)
{
   ecore_x_flush();
   return ECORE_CALLBACK_RENEW;
}

static inline Ecore_X_Window
_e_comp_x_client_window_get(const E_Client *ec)
{
   E_Comp_X_Client_Data *cd = _e_comp_x_client_data_get(ec);
   if (cd && cd->reparented)
     return e_client_util_pwin_get(ec);
   return e_client_util_win_get(ec);
}

static void
_e_comp_x_client_damage_add(E_Client *ec)
{
   Ecore_X_Window win;

   if (_e_comp_x_client_data_get(ec)->damage) return;
   win = _e_comp_x_client_window_get(ec);
   _e_comp_x_client_data_get(ec)->damage = ecore_x_damage_new(win, ECORE_X_DAMAGE_REPORT_DELTA_RECTANGLES);
   eina_hash_add(damages_hash, &_e_comp_x_client_data_get(ec)->damage, ec);
}

static void
_e_comp_x_focus_check(void)
{
   E_Client *focused;

   if (stopping || e_comp->nocomp) return;
   focused = e_client_focused_get();
   /* if there is no new focused or it is a non-X client,
    * focus comp canvas on focus-out */
   if ((!focused) || (!e_client_has_xwindow(focused)))
     {
        focus_canvas_time = ecore_x_current_time_get();
        focus_time = 0;
        if (e_comp->comp_type != E_PIXMAP_TYPE_X)
          e_grabinput_focus(e_comp->root, E_FOCUS_METHOD_PASSIVE);
// this breaks Qt because it seems to create override-redirect popup windows
// that it FOCUSES instead of grabbking the kbd like most other menu windows
// so this causes the parent window to lose focus in a way that then causes
// the popups to dismiss instantly
//             e_grabinput_focus(e_comp->ee_win, E_FOCUS_METHOD_PASSIVE);
// This might be more specific a workaround bit might miss other cases, so keep
// here as an idea and for future reference
        else
          {
             Ecore_X_Window focus_win = ecore_x_window_focus_get();
             if (focus_win)
               {
                  if ((ecore_x_window_root_get(focus_win) == focus_win) ||
                      (!ecore_x_icccm_transient_for_get(focus_win)))
                    e_grabinput_focus(e_comp->ee_win, E_FOCUS_METHOD_PASSIVE);
               }
             else
               e_grabinput_focus(e_comp->ee_win, E_FOCUS_METHOD_PASSIVE);
          }
     }
}

static void
_e_comp_x_client_modal_setup(E_Client *ec)
{
   E_Comp_X_Client_Data *pcd;

   ec->parent->modal = ec;
   ec->parent->lock_close = 1;
   pcd = _e_comp_x_client_data_get(ec->parent);
   if (!pcd->lock_win)
     {
        eina_hash_add(clients_win_hash, &pcd->lock_win, ec->parent);
        pcd->lock_win = ecore_x_window_input_new(e_client_util_pwin_get(ec->parent), 0, 0, ec->parent->w, ec->parent->h);
        e_comp_ignore_win_add(E_PIXMAP_TYPE_X, pcd->lock_win);
        ecore_x_window_show(pcd->lock_win);
        ecore_x_icccm_name_class_set(pcd->lock_win, "comp_data->lock_win", "comp_data->lock_win");
     }
}

static void
_e_comp_x_client_frame_update(E_Client *ec, int l, int r, int t, int b)
{
   ecore_x_netwm_frame_size_set(e_client_util_win_get(ec), l, r, t, b);
   ecore_x_e_frame_size_set(e_client_util_win_get(ec), l, r, t, b);
   _e_comp_x_client_data_get(ec)->frame_update = 0;
}

static Eina_List *iconshare = NULL;

typedef struct _E_Client_Icon_Entry E_Client_Icon_Entry;

struct _E_Client_Icon_Entry
{
   Ecore_X_Icon *icons;
   int num_icons;
   int ref;
};

static Ecore_X_Icon *
_e_comp_x_client_icon_deduplicate(Ecore_X_Icon *icons, int num_icons)
{
   int i;
   Eina_List *l;
   E_Client_Icon_Entry *ie;

   // unless the rest of e uses border icons OTHER than icon #0
   // then free the rest that we don't need anymore.
   for (i = 1; i < num_icons; i++)
     {
        E_FREE(icons[i].data);
     }
   // lookup icon data in icons cache/share
   EINA_LIST_FOREACH(iconshare, l, ie)
     {
        if ((ie->num_icons == num_icons) &&
            (num_icons  > 0) &&
            (ie->icons[0].width == icons[0].width) &&
            (ie->icons[0].height == icons[0].height) &&
            (!memcmp(ie->icons[0].data, icons[0].data,
                     icons[0].width * icons[0].height * 4)))
          {
             // found so free the input icons
             for (i = 0; i < num_icons; i++)
               free(icons[i].data);
             free(icons);
             // ref the shared/cached one
             ie->ref++;
             iconshare = eina_list_promote_list(iconshare, l);
             // and return that
             return ie->icons;
          }
     }
   // no hit - new entry to cache. add it
   ie = calloc(1, sizeof(E_Client_Icon_Entry));
   if (ie)
     {
        ie->icons = icons;
        ie->num_icons = num_icons;
        ie->ref = 1;
        iconshare = eina_list_prepend(iconshare, ie);
     }
   return icons;
}

static void
_e_comp_x_client_icon_free(Ecore_X_Icon *icons, int num_icons)
{
   int i;
   Eina_List *l;
   E_Client_Icon_Entry *ie;

   // lookup in icon share cache
   EINA_LIST_FOREACH(iconshare, l, ie)
     {
        if ((ie->num_icons == num_icons) &&
            (num_icons  > 0) &&
            (ie->icons[0].width == icons[0].width) &&
            (ie->icons[0].height == icons[0].height) &&
            (!memcmp(ie->icons[0].data, icons[0].data,
                     icons[0].width * icons[0].height * 4)))
          {
             // found so deref
             ie->ref--;
             if (ie->ref <= 0)
               {
                  // no refs left - free the icon from the share/cache
                  iconshare = eina_list_remove_list(iconshare, l);
                  for (i = 0; i < ie->num_icons; i++)
                    free(ie->icons[i].data);
                  free(ie->icons);
                  free(ie);
               }
             return;
          }
     }
   // not found - so just free it ... odd - we should never be here
   for (i = 0; i < num_icons; i++)
     free(icons[i].data);
   free(icons);
}

static void
_e_comp_x_client_event_free(void *d EINA_UNUSED, void *e)
{
   E_Event_Client *ev = e;

   UNREFD(ev->ec, 1);
   e_object_unref(E_OBJECT(ev->ec));
   free(ev);
}

static void
_e_comp_x_print_win(Ecore_X_Window win)
{
   int x, y, w, h;
   Eina_Bool vis;

   ecore_x_window_geometry_get(win, &x, &y, &w, &h);
   vis = ecore_x_window_visible_get(win);
   fprintf(stderr, "%s 0x%x: %d,%d @ %dx%d\n", vis ? "VIS" : "HID", win, x, y, w, h);
}

static void
_e_comp_x_focus_grab(E_Client *ec)
{
   if (ec->internal_elm_win) return;
   ecore_x_window_button_grab(e_client_util_win_get(ec), 1,
                              ECORE_X_EVENT_MASK_MOUSE_DOWN |
                              ECORE_X_EVENT_MASK_MOUSE_UP |
                              ECORE_X_EVENT_MASK_MOUSE_MOVE, 0, 1);
   ecore_x_window_button_grab(e_client_util_win_get(ec), 2,
                              ECORE_X_EVENT_MASK_MOUSE_DOWN |
                              ECORE_X_EVENT_MASK_MOUSE_UP |
                              ECORE_X_EVENT_MASK_MOUSE_MOVE, 0, 1);
   ecore_x_window_button_grab(e_client_util_win_get(ec), 3,
                              ECORE_X_EVENT_MASK_MOUSE_DOWN |
                              ECORE_X_EVENT_MASK_MOUSE_UP |
                              ECORE_X_EVENT_MASK_MOUSE_MOVE, 0, 1);
   _e_comp_x_client_data_get(ec)->button_grabbed = 1;
}

static void
_e_comp_x_focus_init(E_Client *ec)
{
   if (_e_comp_x_client_data_get(ec)->button_grabbed) return;
   if (!((e_client_focus_policy_click(ec)) ||
       (e_config->always_click_to_raise) ||
       (e_config->always_click_to_focus))) return;
   _e_comp_x_focus_grab(ec);
}

static void
_e_comp_x_focus_setup(E_Client *ec)
{
   if (_e_comp_x_client_data_get(ec)->button_grabbed) return;
   if ((!e_client_focus_policy_click(ec)) ||
       (e_config->always_click_to_raise) ||
       (e_config->always_click_to_focus)) return;
   _e_comp_x_focus_grab(ec);
}


static void
_e_comp_x_focus_setdown(E_Client *ec)
{
   Ecore_X_Window win, pwin;

   if (!_e_comp_x_client_data_get(ec)->button_grabbed) return;
   if ((!e_client_focus_policy_click(ec)) ||
       (e_config->always_click_to_raise) ||
       (e_config->always_click_to_focus)) return;
   win = e_client_util_win_get(ec);
   pwin = e_client_util_pwin_get(ec);
   e_bindings_mouse_ungrab(E_BINDING_CONTEXT_WINDOW, pwin);
   e_bindings_wheel_ungrab(E_BINDING_CONTEXT_WINDOW, pwin);
   ecore_x_window_button_ungrab(win, 1, 0, 1);
   ecore_x_window_button_ungrab(win, 2, 0, 1);
   ecore_x_window_button_ungrab(win, 3, 0, 1);
   e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, pwin);
   e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, pwin);
   _e_comp_x_client_data_get(ec)->button_grabbed = 0;
}

static Eina_Bool
_e_comp_x_client_new_helper(E_Client *ec)
{
   Ecore_X_Window win = e_client_util_win_get(ec);
   int at_num = 0, i;
   Ecore_X_Atom *atoms;

   if (!ecore_x_window_attributes_get(win, &ec->comp_data->initial_attributes))
     {
        //CRI("OUCH! FIX THIS!");
        DELD(ec, 1);
        e_object_del(E_OBJECT(ec));
        return EINA_FALSE;
     }
   if (ec->re_manage && (!ec->comp_data->initial_attributes.visible))
     {
        /* ain't gonna be no hidden clients on my watch! */
        DELD(ec, 1);
        e_object_del(E_OBJECT(ec));
        return EINA_FALSE;
     }
   ec->depth = ec->comp_data->initial_attributes.depth;
   ec->override = ec->comp_data->initial_attributes.override;
   ec->placed |= ec->override;
   ec->input_only = ec->comp_data->initial_attributes.input_only;
   ec->border_size = ec->comp_data->initial_attributes.border;
   ec->icccm.accepts_focus = (!ec->override) && (!ec->input_only);
   //INF("NEW CLIENT: %d,%d -> %d,%d", ec->x, ec->y, ec->comp_data->initial_attributes.x, ec->comp_data->initial_attributes.y);
   ec->x = ec->client.x = ec->comp_data->initial_attributes.x;
   ec->y = ec->client.y = ec->comp_data->initial_attributes.y;
   if (ec->override && ((ec->x == -77) && (ec->y == -777)))
     {
        /* this is the ecore-x private window :/ */
        e_comp_ignore_win_add(E_PIXMAP_TYPE_X, win);
        DELD(ec, 1);
        e_object_del(E_OBJECT(ec));
        return EINA_FALSE;
     }
   if ((!e_client_util_ignored_get(ec)) && (!ec->internal) && (!ec->internal_elm_win))
     {
        ec->comp_data->need_reparent = 1;
        EC_CHANGED(ec);
        ec->take_focus = !starting;
     }
   ec->new_client ^= ec->override;
   if (!ec->new_client)
     e_comp->new_clients--;

   ec->w = ec->client.w = ec->comp_data->initial_attributes.w;
   ec->h = ec->client.h = ec->comp_data->initial_attributes.h;
   if (!ec->internal)
     {
        ec->comp_data->pw = ec->w;
        ec->comp_data->ph = ec->h;
     }
   ec->changes.size = 1;


   e_pixmap_visual_cmap_set(ec->pixmap, ec->comp_data->initial_attributes.visual, ec->comp_data->initial_attributes.colormap);
   if (ec->override && (!ec->internal))
     ecore_x_window_shape_events_select(win, 1);
   if (ec->override && (!(ec->comp_data->initial_attributes.event_mask.mine & ECORE_X_EVENT_MASK_WINDOW_PROPERTY)))
     ecore_x_event_mask_set(win, ECORE_X_EVENT_MASK_WINDOW_PROPERTY);

   atoms = ecore_x_window_prop_list(win, &at_num);
   ec->icccm.fetch.command = 1;

   if (atoms)
     {
        Eina_Bool video_parent = EINA_FALSE;
        Eina_Bool video_position = EINA_FALSE;
        Eina_Bool found_desk = EINA_FALSE;
        Eina_Bool found_zone = EINA_FALSE;

        /* icccm */
        for (i = 0; i < at_num; i++)
          {
             if (atoms[i] == ECORE_X_ATOM_WM_NAME)
               ec->icccm.fetch.title = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_CLASS)
               ec->icccm.fetch.name_class = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_ICON_NAME)
               ec->icccm.fetch.icon_name = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_CLIENT_MACHINE)
               ec->icccm.fetch.machine = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_HINTS)
               ec->icccm.fetch.hints = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_NORMAL_HINTS)
               ec->icccm.fetch.size_pos_hints = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_PROTOCOLS)
               ec->icccm.fetch.protocol = 1;
             else if (atoms[i] == ECORE_X_ATOM_MOTIF_WM_HINTS)
               ec->mwm.fetch.hints = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_TRANSIENT_FOR)
               {
                  ec->icccm.fetch.transient_for = 1;
                  ec->netwm.fetch.type = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_WM_CLIENT_LEADER)
               ec->icccm.fetch.client_leader = 1;
             else if (atoms[i] == ECORE_X_ATOM_WM_WINDOW_ROLE)
               ec->icccm.fetch.window_role = 1;
          }
        /* netwm, loop again, netwm will ignore some icccm, so we
         * have to be sure that netwm is checked after */
        for (i = 0; i < at_num; i++)
          {
             if (atoms[i] == ECORE_X_ATOM_NET_WM_NAME)
               {
                  /* Ignore icccm */
                  ec->icccm.fetch.title = 0;
                  ec->netwm.fetch.name = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_ICON_NAME)
               {
                  /* Ignore icccm */
                  ec->icccm.fetch.icon_name = 0;
                  ec->netwm.fetch.icon_name = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_ICON)
               {
                  ec->netwm.fetch.icon = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_USER_TIME)
               {
                  ec->netwm.fetch.user_time = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_STRUT)
               {
                  DBG("ECORE_X_ATOM_NET_WM_STRUT");
                  ec->netwm.fetch.strut = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_STRUT_PARTIAL)
               {
                  DBG("ECORE_X_ATOM_NET_WM_STRUT_PARTIAL");
                  ec->netwm.fetch.strut = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_WINDOW_TYPE)
               {
                  /* Ignore mwm
                     ec->mwm.fetch.hints = 0;
                   */
                  ec->netwm.fetch.type = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_STATE)
               {
                  ec->netwm.fetch.state = 1;
               }
             else if (atoms[i] == ECORE_X_ATOM_NET_WM_WINDOW_OPACITY)
               ec->netwm.fetch.opacity = 1;
          }
        /* other misc atoms */
        for (i = 0; i < at_num; i++)
          {
             /* loop to check for own atoms */
             if (atoms[i] == E_ATOM_WINDOW_STATE)
               {
                  ec->e.fetch.state = 1;
               }
             /* loop to check for qtopia atoms */
             if (atoms[i] == ATM__QTOPIA_SOFT_MENU)
               ec->qtopia.fetch.soft_menu = 1;
             else if (atoms[i] == ATM__QTOPIA_SOFT_MENUS)
               ec->qtopia.fetch.soft_menus = 1;
             /* loop to check for vkbd atoms */
             else if (atoms[i] == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD_STATE)
               ec->vkbd.fetch.state = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD)
               ec->vkbd.fetch.vkbd = 1;
             /* loop to check for illume atoms */
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_CONFORMANT)
               ec->comp_data->illume.conformant.fetch.conformant = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_STATE)
               ec->comp_data->illume.quickpanel.fetch.state = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_QUICKPANEL)
               ec->comp_data->illume.quickpanel.fetch.quickpanel = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_PRIORITY_MAJOR)
               ec->comp_data->illume.quickpanel.fetch.priority.major = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_PRIORITY_MINOR)
               ec->comp_data->illume.quickpanel.fetch.priority.minor = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_ZONE)
               ec->comp_data->illume.quickpanel.fetch.zone = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_DRAG_LOCKED)
               ec->comp_data->illume.drag.fetch.locked = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_DRAG)
               ec->comp_data->illume.drag.fetch.drag = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_ILLUME_WINDOW_STATE)
               ec->comp_data->illume.win_state.fetch.state = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_VIDEO_PARENT)
               video_parent = EINA_TRUE;
             else if (atoms[i] == ECORE_X_ATOM_E_VIDEO_POSITION)
               video_position = EINA_TRUE;
             /* loop to check for window profile list atom */
             else if (atoms[i] == ECORE_X_ATOM_E_WINDOW_PROFILE_SUPPORTED)
               ec->e.fetch.profile = 1;
             else if (atoms[i] == ECORE_X_ATOM_E_STACK_TYPE)
               ec->e.fetch.stack = 1;
             else if (atoms[i] == ATM_GTK_FRAME_EXTENTS)
               ec->comp_data->fetch_gtk_frame_extents = 1;
             else if (atoms[i] == ATM_STEAM_GAME)
               e_hints_window_steam_game_get(ec);
             else if (ec->re_manage)
               {
                  if (atoms[i] == E_ATOM_DESKTOP_FILE)
                    {
                       char *path = ecore_x_window_prop_string_get(win,
                                                             E_ATOM_DESKTOP_FILE);
                       if (path)
                         {
                            ec->desktop = efreet_desktop_get(path);
                            free(path);
                         }
                    }
                  else if (atoms[i] == E_ATOM_DESK)
                    found_desk = 1;
                  else if (atoms[i] == E_ATOM_ZONE)
                    found_zone = 1;
               }
          }
        if (video_position && video_parent)
          {
             ec->e.state.video = 1;
             ec->e.fetch.video_parent = 1;
             ec->e.fetch.video_position = 1;
             fprintf(stderr, "We found a video window \\o/ %x\n", win);
          }
        free(atoms);

        if (ec->re_manage && found_desk && found_zone)
          {
             E_Zone *zone = NULL;
             E_Desk *desk = NULL;
             unsigned int id, deskxy[2];
             int ret;

             /* get all information from window before it is
              * reset by e_client_new */
             ret = ecore_x_window_prop_card32_get(win,
                                                  E_ATOM_ZONE,
                                                  &id, 1);
             if (ret == 1)
               zone = e_comp_zone_number_get(id);
             if (!zone)
               zone = e_zone_current_get();
             ret = ecore_x_window_prop_card32_get(win,
                                                  E_ATOM_DESK,
                                                  deskxy, 2);
             if (ret == 2)
               desk = e_desk_at_xy_get(zone,
                                       deskxy[0],
                                       deskxy[1]);

             if (desk) e_client_desk_set(ec, desk);

          }
     }

   return EINA_TRUE;
}

static E_Client *
_e_comp_x_client_find_by_damage(Ecore_X_Damage damage)
{
   return eina_hash_find(damages_hash, &damage);
}

static E_Client *
_e_comp_x_client_find_by_window(Ecore_X_Window win)
{
   E_Client *ec;

   ec = eina_hash_find(clients_win_hash, &win);
   if (ec && e_object_is_del(E_OBJECT(ec))) ec = NULL;
   return ec;
}

/*
static E_Client *
_e_comp_x_client_find_all_by_window(Ecore_X_Window win)
{
   return eina_hash_find(clients_win_hash, &win);
}
*/

static void
_e_comp_x_add_fail_job(void *d EINA_UNUSED)
{
   e_util_dialog_internal
     (_("Compositor Warning"),
      _("Your display driver does not support OpenGL, GLSL<ps/>"
        "shaders or no OpenGL engines were compiled or installed<ps/>"
        "for Evas or Ecore-Evas. Falling back to software engine.<ps/>"
        "<ps/>"
        "You will need an OpenGL 2.0 (or OpenGL ES 2.0) capable<ps/>"
        "GPU to use OpenGL with compositing."));
}

static E_Client *
_e_comp_x_client_find_by_alarm(Ecore_X_Sync_Alarm al)
{
   return eina_hash_find(alarm_hash, &al);
}

static void
_e_comp_x_client_move_resize_send(E_Client *ec)
{
   if (ec->internal_elm_win)
     ecore_evas_managed_move(e_win_ee_get(ec->internal_elm_win), ec->client.x - ec->x, ec->client.y - ec->y);

   ecore_x_icccm_move_resize_send(e_client_util_win_get(ec), ec->client.x, ec->client.y, ec->client.w, ec->client.h);
}

static Eina_Bool
_e_comp_x_post_client_idler_cb(void *d EINA_UNUSED)
{
   E_Client *ec;

   //INF("POST IDLER");
   EINA_LIST_FREE(post_clients, ec)
     {
        Ecore_X_Window win, twin;

        if (e_object_is_del(E_OBJECT(ec)) || (!_e_comp_x_client_data_get(ec))) continue;
        win = _e_comp_x_client_window_get(ec);
        if (ec->post_move)
          {
             E_Client *tmp;
             Eina_List *l;

             EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
               {
                  twin = _e_comp_x_client_window_get(tmp);
                  ecore_x_window_move(twin,
                                      ec->client.x +
                                      tmp->e.state.video_position.x,
                                      ec->client.y +
                                      tmp->e.state.video_position.y);
               }
          }
        if (ec->e.state.video)
          {
             E_Client *parent;

             parent = ec->e.state.video_parent_client;
             twin = _e_comp_x_client_window_get(parent);
             ecore_x_window_move(twin,
                                 parent->client.x +
                                 ec->e.state.video_position.x,
                                 parent->client.y +
                                 ec->e.state.video_position.y);
          }
        else if (ec->shading)
          {
             // do nothing
          }
        else if ((ec->post_move) && (ec->post_resize))
          {
             //INF("X MVRSZ");
             ecore_x_window_move_resize(win,
                                        ec->client.x,
                                        ec->client.y,
                                        ec->client.w,
                                        ec->client.h);
             if (_e_comp_x_client_data_get(ec)->reparented)
               ecore_x_window_move_resize(e_client_util_win_get(ec), 0, 0,
                                          ec->client.w,
                                          ec->client.h);
          }
        else if (ec->post_move)
          {
             //INF("X MV");
             ecore_x_window_move(win, ec->client.x, ec->client.y);
          }
        else if (ec->post_resize)
          {
             //INF("X RSZ: %dx%d (REAL: %dx%d)", ec->client.w, ec->client.h, ec->w, ec->h);
             ecore_x_window_resize(win,
                                   ec->client.w, ec->client.h);
             if (_e_comp_x_client_data_get(ec)->reparented)
               ecore_x_window_move_resize(e_client_util_win_get(ec), 0, 0,
                                          ec->client.w, ec->client.h);
          }
        if ((!ec->shading) && (!ec->shaded))
          {
             if (ec->post_resize)
               {
                  if (ec->netwm.sync.request && (e_comp->comp_type == E_PIXMAP_TYPE_X))
                    {
                       //INF("NETWM SYNC: %p", ec);
                       ec->netwm.sync.wait++;
                       ecore_x_netwm_sync_request_send(e_client_util_win_get(ec),
                                                       ec->netwm.sync.serial++);
                    }
               }

             if (ec->post_move || ec->post_resize)
               _e_comp_x_client_move_resize_send(ec);
          }

        if (ec->e.state.video)
          {
             fprintf(stderr, "VIDEO %p: [%i, %i] [%i, %i]\n",
                     ec,
                     ec->e.state.video_parent_client->client.x +
                     ec->e.state.video_position.x,
                     ec->e.state.video_parent_client->client.y +
                     ec->e.state.video_position.y,
                     ec->w, ec->h);
          }
        if (ec->netwm.opacity_changed)
          {
             unsigned int opacity;
             int op;

             evas_object_color_get(ec->frame, NULL, NULL, NULL, &op);
             ec->netwm.opacity = op;
             opacity = (op << 24) | (op << 16) | (op << 8) | op;
             ecore_x_window_prop_card32_set(e_client_util_win_get(ec), ECORE_X_ATOM_NET_WM_WINDOW_OPACITY, &opacity, 1);
             /* flag gets unset in property cb to avoid fetching opacity after we just set it */
          }
        if (e_pixmap_is_x(ec->pixmap) && ec->post_resize)
          {
             e_pixmap_dirty(ec->pixmap);
             e_comp_object_render_update_del(ec->frame);
             if (!ec->internal)
               {
                  _e_comp_x_client_data_get(ec)->pw = ec->client.w;
                  _e_comp_x_client_data_get(ec)->ph = ec->client.h;
               }
          }
        ec->post_move = 0;
        ec->post_resize = 0;
     }
   if (e_comp_x->restack && (!e_comp->new_clients))
     {
        e_hints_client_stacking_set();
        e_comp_x->restack = 0;
     }
   _e_comp_x_post_client_idler = NULL;
   return EINA_FALSE;
}

static void
_e_comp_x_post_client_idler_add(E_Client *ec)
{
   if (!_e_comp_x_post_client_idler)
     _e_comp_x_post_client_idler = ecore_idle_enterer_add(_e_comp_x_post_client_idler_cb, NULL);
   if (!ec) CRI("ACK!");
   //INF("POST IDLE: %p", ec);
   if (!eina_list_data_find(post_clients, ec))
     post_clients = eina_list_append(post_clients, ec);
}

static void
_e_comp_x_client_stack(E_Client *ec)
{
   E_Client *ec2;
   Ecore_X_Window_Stack_Mode mode = ECORE_X_WINDOW_STACK_BELOW;
   Ecore_X_Window cwin, win = 0;
   Eina_List *l;

   if (ec->override && (!ec->internal)) return; //can't restack these
   if (e_client_is_stacking(ec)) return;
   if (!_e_comp_x_client_data_get(ec)) return;
   if (_e_comp_x_client_data_get(ec)->unredirected_single) return;

   cwin = _e_comp_x_client_window_get(ec);

   ecore_x_window_shadow_tree_flush();

   /* try stacking below */
   if (e_comp->nocomp_ec && e_comp->nocomp_ec->comp_data && (ec != e_comp->nocomp_ec))
     win = _e_comp_x_client_window_get(e_comp->nocomp_ec);
   else
     {
        ec2 = ec;
        do
          {
             ec2 = e_client_above_get(ec2);
             if (ec2 && e_client_has_xwindow(ec2) && ec2->comp_data && (!ec2->new_client) &&
                 (e_client_is_stacking(ec2) || ((!ec2->override) || ec2->internal)))
               {
                  if (ec2->layer != ec->layer) break;
                  if (_e_comp_x_client_data_get(ec2)->need_reparent && (!_e_comp_x_client_data_get(ec2)->reparented)) continue;
                  win = _e_comp_x_client_window_get(ec2);
               }
          } while (ec2 && (!win));
     }

   /* try stacking above */
   if (!win)
     {
        ec2 = ec;
        do
          {
             ec2 = e_client_below_get(ec2);
             if (ec2 && e_client_has_xwindow(ec2) && ec2->comp_data && (!ec2->new_client) &&
                 (e_client_is_stacking(ec2) || ((!ec2->override) || ec2->internal)))
               {
                  if (ec2->layer != ec->layer) break;
                  if (_e_comp_x_client_data_get(ec2)->need_reparent && (!_e_comp_x_client_data_get(ec2)->reparented)) continue;
                  win = _e_comp_x_client_window_get(ec2);
                  mode = ECORE_X_WINDOW_STACK_ABOVE;
               }
          } while (ec2 && (!win));
     }

   /* just layer stack */
   if (!win)
     {
        win = e_comp->layers[e_comp_canvas_layer_map(ec->layer)].win;
        mode = ECORE_X_WINDOW_STACK_BELOW;
     }
   ecore_x_window_configure(cwin,
                            ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING |
                            ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE,
                            0, 0, 0, 0, 0, win, mode);
   _e_comp_x_post_client_idler_add(ec);
   e_comp_x->restack = 1;
   EINA_LIST_FOREACH(ec->e.state.video_child, l, ec2)
     evas_object_stack_below(ec2->frame, ec->frame);
}

static E_Client *
_e_comp_x_client_new(Ecore_X_Window win, Eina_Bool first)
{
   E_Pixmap *cp;
   E_Client *ec;

   cp = e_pixmap_new(E_PIXMAP_TYPE_X, win);
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, NULL);

   ec = e_client_new(cp, first, 0);
   return ec;
}

static void
_e_comp_x_client_pri_raise(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   e_pid_nice_priority_fg(ec->netwm.pid);
}

static void
_e_comp_x_client_pri_norm(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   e_pid_nice_priority_bg(ec->netwm.pid);
}

static void
_e_comp_x_client_shape_input_rectangle_set(E_Client *ec)
{
   Ecore_X_Window win = e_client_util_pwin_get(ec);

   if (ec->override || (!_e_comp_x_client_data_get(ec)->reparented)) return;

   if (ec->visible && (!ec->comp_hidden))
     ecore_x_composite_window_events_enable(win);
   else
     ecore_x_composite_window_events_disable(win);
}

static void
_e_comp_x_evas_color_set_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;
   int a;

   if (!_e_comp_x_client_data_get(ec)) return;
   evas_object_color_get(obj, NULL, NULL, NULL, &a);
   if (a == ec->netwm.opacity) return;
   ec->netwm.opacity_changed = 1;
   _e_comp_x_post_client_idler_add(ec);
}

static void
_e_comp_x_evas_ping_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   ecore_x_netwm_ping_send(e_client_util_win_get(ec));
}

static void
_e_comp_x_evas_kill_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   ecore_x_kill(e_client_util_win_get(ec));
}

static void
_e_comp_x_evas_delete_request_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   if (ec->netwm.ping)
     e_client_ping(ec);
   ecore_x_window_delete_request_send(e_client_util_win_get(ec));
}

static void
_e_comp_x_evas_comp_hidden_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *tmp, *ec = data;
   Eina_List *l;
   Ecore_X_Window win;

   if (!_e_comp_x_client_data_get(ec)) return;
   if (_e_comp_x_client_data_get(ec)->need_reparent) return;
   win = _e_comp_x_client_window_get(ec);

   EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
     {
        Ecore_X_Window cwin;

        cwin = _e_comp_x_client_window_get(tmp);
        if (ec->comp_hidden)
          ecore_x_window_hide(cwin);
        else
          ecore_x_window_show(cwin);
     }

   if ((ec->internal) && (win == e_client_util_win_get(ec))) return;
   if (!ec->override)
     {
        if (ec->comp_hidden)
          ecore_x_composite_window_events_disable(win);
        else
          ecore_x_composite_window_events_enable(win);
     }
    ecore_x_window_ignore_set(win, ec->comp_hidden);
}

static void
_e_comp_x_evas_shade_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;
   Eina_List *l;
   E_Client *tmp;

   if (!_e_comp_x_client_data_get(ec)) return;
   EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
     ecore_x_window_hide(e_client_util_pwin_get(tmp));

   ecore_x_window_shadow_tree_flush();
}

static void
_e_comp_x_evas_frame_recalc_cb(void *data, Evas_Object *obj, void *event_info)
{
   E_Client *ec = data;
   E_Comp_Object_Frame *fr = event_info;

   if (!_e_comp_x_client_data_get(ec)) return;
   if (evas_object_visible_get(obj))
     _e_comp_x_client_frame_update(ec, fr->l, fr->r, fr->t, fr->b);
   else
     _e_comp_x_client_data_get(ec)->frame_update = 1;
   ec->post_move = ec->post_resize = 1;
   _e_comp_x_post_client_idler_add(ec);
}

static void
_e_comp_x_evas_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   if (_e_comp_x_client_data_get(ec)->moving && (!_e_comp_x_client_data_get(ec)->unredirected_single))
     {
        if (_e_comp_x_client_data_get(ec)->move_counter++ < MOVE_COUNTER_LIMIT) return;
        _e_comp_x_client_data_get(ec)->move_counter = 0;
     }

   ecore_x_window_shadow_tree_flush();
   ec->post_move = 1;
   _e_comp_x_post_client_idler_add(ec);
}

static void
_e_comp_x_evas_resize_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   if (ec->shading || ec->shaded) return;
   if (!e_pixmap_size_changed(ec->pixmap, ec->client.w, ec->client.h)) return;

   ecore_x_window_shadow_tree_flush();

   if (ec->e.state.video)
     {
        if (ec->e.state.video_position.updated)
          {
             ecore_x_window_move(e_client_util_pwin_get(ec),
                                 ec->e.state.video_parent_client->client.x +
                                 ec->e.state.video_position.x,
                                 ec->e.state.video_parent_client->client.y +
                                 ec->e.state.video_position.y);
             ec->e.state.video_position.updated = 0;
          }
     }
   else if (ec->changes.pos)
     {
        E_Client *tmp;
        Eina_List *l;

        EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
          ecore_x_window_move(e_client_util_pwin_get(tmp),
                              ec->client.x + tmp->e.state.video_position.x,
                              ec->client.y + tmp->e.state.video_position.y);
     }

   ec->post_resize = 1;
   if (e_pixmap_is_x(ec->pixmap))
     e_comp_object_render_update_del(ec->frame);
   _e_comp_x_post_client_idler_add(ec);
}

static void
_e_comp_x_client_hide(E_Client *ec)
{
   unsigned int visible = 0;

   ecore_x_window_shadow_tree_flush();
   if ((!ec->iconic) && (!ec->override))
     ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_MAPPED, &visible, 1);

   _e_comp_x_client_data_get(ec)->iconic = ec->iconic
// XXX: if we tell apps they are iconic.. they may stop rendering and this
// means our miniatures we use in ibar, ibox and winlist alt-tab dont udpate
// ... we could i guess setn fake wm state changes to normal when these are
// visible and then toggle iconic on and off while still visually hiding
// the client... but clients that assume they will be iconic when they ask
// are mistaken  ... so for now disable this until we have a debate on it
//   && (!e_comp_object_mirror_visibility_check(ec->frame))
   ;
   ec->netwm.state.hidden = 1;
   e_hints_window_state_set(ec);
   if (ec->unredirected_single || _e_comp_x_client_data_get(ec)->iconic)
     ecore_x_window_hide(_e_comp_x_client_window_get(ec));
   if (_e_comp_x_client_data_get(ec)->iconic)
     e_hints_window_iconic_set(ec);
}

static void
_e_comp_x_evas_hide_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data, *tmp;
   Eina_List *l;

   if (ec == mouse_client)
     {
        mouse_client = NULL;
        E_FREE_FUNC(mouse_in_job, ecore_job_del);
     }

   if (!_e_comp_x_client_data_get(ec)) return; // already deleted, happens with internal wins
   _e_comp_x_client_hide(ec);

   EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
     evas_object_hide(tmp->frame);

   if (e_comp_config_get()->send_flush)
     ecore_x_e_comp_flush_send(e_client_util_win_get(ec));
   if (e_comp_config_get()->send_dump)
     ecore_x_e_comp_dump_send(e_client_util_win_get(ec));
}

static void
_e_comp_x_client_show(E_Client *ec)
{
   unsigned int visible = 1;
   Ecore_X_Window win;

   win = e_client_util_win_get(ec);
   ecore_x_window_shadow_tree_flush();
   if ((!_e_comp_x_client_data_get(ec)->need_reparent) && (!ec->override))
     ecore_x_window_show(win);
   if (ec->unredirected_single || _e_comp_x_client_data_get(ec)->iconic)
     {
        e_pixmap_clear(ec->pixmap);
        ecore_x_window_show(_e_comp_x_client_window_get(ec));
        _e_comp_x_client_data_get(ec)->iconic = 0;
     }
   ec->netwm.state.hidden = 0;
   e_hints_window_state_set(ec);
   if (!ec->override)
     e_hints_window_visible_set(ec);

   ecore_x_window_prop_card32_set(win, E_ATOM_MAPPED, &visible, 1);
   ecore_x_window_prop_card32_set(win, E_ATOM_MANAGED, &visible, 1);
}

static void
_e_comp_x_evas_show_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data, *tmp;
   Eina_List *l;

   if (!_e_comp_x_client_data_get(ec)) return;
   _e_comp_x_client_show(ec);

   if (_e_comp_x_client_data_get(ec)->frame_update)
     {
        int ll, r, t, b;

        e_comp_object_frame_geometry_get(obj, &ll, &r, &t, &b);
        _e_comp_x_client_frame_update(ec, ll, r, t, b);
     }

   EINA_LIST_FOREACH(ec->e.state.video_child, l, tmp)
     {
        evas_object_show(tmp->frame);
        ecore_x_window_show(e_client_util_pwin_get(tmp));
     }
}

static void
_e_comp_x_evas_stack_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   if (evas_object_data_get(obj, "client_restack"))
     _e_comp_x_client_stack(data);
}

static void
_e_comp_x_evas_unfullscreen_zoom_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if ((screen_size.width != -1) && (screen_size.height != -1))
     {
        ecore_x_randr_screen_primary_output_size_set(e_comp->root,
                                                     screen_size_index);
        screen_size.width = -1;
        screen_size.height = -1;
     }
}

static void
_e_comp_x_evas_fullscreen_zoom_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;
   Ecore_X_Randr_Screen_Size_MM *sizes;
   int num_sizes, i, best_size_index = 0;

   ecore_x_randr_screen_primary_output_current_size_get(e_comp->root,
                                                        &screen_size.width,
                                                        &screen_size.height,
                                                        NULL, NULL, NULL);
   sizes = ecore_x_randr_screen_primary_output_sizes_get(e_comp->root,
                                                         &num_sizes);
   if (sizes)
     {
        Ecore_X_Randr_Screen_Size best_size = { -1, -1 };
        int best_dist = INT_MAX, dist;

        for (i = 0; i < num_sizes; i++)
          {
             if ((sizes[i].width > ec->w) && (sizes[i].height > ec->h))
               {
                  dist = (sizes[i].width * sizes[i].height) - (ec->w * ec->h);
                  if (dist < best_dist)
                    {
                       best_size.width = sizes[i].width;
                       best_size.height = sizes[i].height;
                       best_dist = dist;
                       best_size_index = i;
                    }
               }
          }
        if (((best_size.width != -1) && (best_size.height != -1)) &&
            ((best_size.width != screen_size.width) ||
             (best_size.height != screen_size.height)))
          {
             if (ecore_x_randr_screen_primary_output_size_set(e_comp->root,
                                                              best_size_index))
               screen_size_index = best_size_index;
             evas_object_geometry_set(ec->frame, 0, 0, best_size.width, best_size.height);
          }
        else
          {
             screen_size.width = -1;
             screen_size.height = -1;
             evas_object_geometry_set(ec->frame, 0, 0, ec->zone->w, ec->zone->h);
          }
        free(sizes);
     }
   else
     {
        evas_object_geometry_set(ec->frame, ec->zone->x, ec->zone->y, ec->zone->w, ec->zone->h);
     }
}

static Eina_Bool
_e_comp_x_destroy(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Destroy *ev)
{
   E_Client *ec;

   //INF("X DESTROY: %u", ev->win);
   e_comp_ignore_win_del(E_PIXMAP_TYPE_X, ev->win);
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec)
     {
        if (!e_comp_x->retry_clients) return ECORE_CALLBACK_RENEW;
        e_comp_x->retry_clients = eina_list_remove(e_comp_x->retry_clients, (uintptr_t*)(unsigned long)ev->win);
        if (!e_comp_x->retry_clients)
          E_FREE_FUNC(e_comp_x->retry_timer, ecore_timer_del);
        return ECORE_CALLBACK_PASS_ON;
     }
   if (_e_comp_x_client_data_get(ec))
     {
        if (_e_comp_x_client_data_get(ec)->reparented)
          e_client_comp_hidden_set(ec, 1);
        evas_object_pass_events_set(ec->frame, 1);
        evas_object_hide(ec->frame);
        _e_comp_x_client_data_get(ec)->deleted = 1;
        DELD(ec, 2);
        e_object_del(E_OBJECT(ec));
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_resize_request(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Resize_Request *ev)
{
   E_Client *ec;
   int w, h;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec)
     {
        if (!e_comp_find_by_window(ev->win)) ecore_x_window_resize(ev->win, ev->w, ev->h);
        return ECORE_CALLBACK_PASS_ON;
     }
   w = ev->w, h = ev->h;
   if (ec->zone && (e_config->geometry_auto_resize_limit == 1))
     {
        int zx, zy, zw, zh;

        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
        if (w > zw)
          w = zw;

        if (h > zh)
          h = zh;
     }
   if ((w != ec->w) || (h != ec->h))
     {
        evas_object_resize(ec->frame, w, h);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_x_evas_mirror_hidden(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   if ((!ec->iconic) || (!_e_comp_x_client_data_get(ec)->iconic)) return;
   _e_comp_x_client_hide(ec);
}

static void
_e_comp_x_evas_mirror_visible(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec = data;

   if (!_e_comp_x_client_data_get(ec)) return;
   if ((!ec->iconic) || _e_comp_x_client_data_get(ec)->iconic) return;
   _e_comp_x_client_show(ec);
}

static void
_e_comp_x_client_evas_init(E_Client *ec)
{
   if (_e_comp_x_client_data_get(ec)->evas_init) return;
   _e_comp_x_client_data_get(ec)->evas_init = 1;
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_RESTACK, _e_comp_x_evas_stack_cb, ec);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_SHOW, _e_comp_x_evas_show_cb, ec);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_HIDE, _e_comp_x_evas_hide_cb, ec);
   if (!ec->override)
     {
        evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_MOVE, _e_comp_x_evas_move_cb, ec);
        evas_object_smart_callback_add(ec->frame, "client_resize", _e_comp_x_evas_resize_cb, ec);
     }
   evas_object_smart_callback_add(ec->frame, "frame_recalc_done", _e_comp_x_evas_frame_recalc_cb, ec);
   evas_object_smart_callback_add(ec->frame, "shade_done", _e_comp_x_evas_shade_cb, ec);
   evas_object_smart_callback_add(ec->frame, "comp_hidden", _e_comp_x_evas_comp_hidden_cb, ec);
   evas_object_smart_callback_add(ec->frame, "delete_request", _e_comp_x_evas_delete_request_cb, ec);
   evas_object_smart_callback_add(ec->frame, "kill_request", _e_comp_x_evas_kill_cb, ec);
   evas_object_smart_callback_add(ec->frame, "ping", _e_comp_x_evas_ping_cb, ec);
   evas_object_smart_callback_add(ec->frame, "color_set", _e_comp_x_evas_color_set_cb, ec);
   evas_object_smart_callback_add(ec->frame, "fullscreen_zoom", _e_comp_x_evas_fullscreen_zoom_cb, ec);
   evas_object_smart_callback_add(ec->frame, "unfullscreen_zoom", _e_comp_x_evas_unfullscreen_zoom_cb, ec);
   evas_object_smart_callback_add(ec->frame, "visibility_force", _e_comp_x_evas_mirror_visible, ec);
   evas_object_smart_callback_add(ec->frame, "visibility_normal", _e_comp_x_evas_mirror_hidden, ec);
   /* force apply this since we haven't set up our smart cb previously */
   _e_comp_x_evas_comp_hidden_cb(ec, NULL, NULL);
}

static Eina_Bool
_e_comp_x_object_add(void *d EINA_UNUSED, int t EINA_UNUSED, E_Event_Comp_Object *ev)
{
   E_Client *ec;

   ec = e_comp_object_client_get(ev->comp_object);
   if ((!ec) || e_object_is_del(E_OBJECT(ec)) || ec->re_manage) return ECORE_CALLBACK_RENEW;
   E_COMP_X_PIXMAP_CHECK ECORE_CALLBACK_RENEW;
   _e_comp_x_client_evas_init(ec);
   _e_comp_x_client_stack(ec);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_show_request(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Show_Request *ev)
{
   E_Client *ec;

   //INF("X SHOW REQ: %u", ev->win);
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (e_comp_ignore_win_find(ev->win) ||
     (ec && (ec->ignored || ec->override)) ||
     (!e_comp_find_by_window(ev->parent)) ||
     (ev->parent != e_comp->root))
     {
        if ((!ec) && (!eina_hash_find(dead_wins, &ev->parent)))
          {
             ecore_x_window_show(ev->win);
             return ECORE_CALLBACK_RENEW;
          }
     }
   if (!ec)
     ec = _e_comp_x_client_new(ev->win, 0);
   if (!ec)
     {
        ecore_x_window_show(ev->win);
        return ECORE_CALLBACK_RENEW;
     }
   if ((e_comp->comp_type != E_PIXMAP_TYPE_X) && ec->ignored)
     {
        ec->visible = 1;
        if (ec->comp_data->need_reparent)
          _e_comp_x_hook_client_pre_frame_assign(NULL, ec);
        else
          ecore_x_window_show(ev->win);
        return ECORE_CALLBACK_RENEW;
     }

   if ((!_e_comp_x_client_data_get(ec)->reparented) && (!e_client_util_ignored_get(ec)))
     {
        if (!ec->override)
          _e_comp_x_client_data_get(ec)->need_reparent = 1;
        ec->visible = 1;
        EC_CHANGED(ec);
        return ECORE_CALLBACK_RENEW;
     }
   if (ec->iconic)
     {
        if (!ec->lock_client_iconify)
          e_client_uniconify(ec);
     }
   else
     {
        /* FIXME: make client "urgent" for a bit - it wants attention */
        /*	e_client_show(ec); */
        if (!ec->lock_client_stacking)
          evas_object_raise(ec->frame);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_x_show_helper(E_Client *ec)
{
   if ((!ec->override) && (!ec->re_manage) && (!_e_comp_x_client_data_get(ec)->first_map) &&
       (!_e_comp_x_client_data_get(ec)->reparented) && (!_e_comp_x_client_data_get(ec)->need_reparent))
     {
        /* this is most likely an internal window */
        _e_comp_x_client_data_get(ec)->need_reparent = 1;
        ec->visible = 1;
        if (ec->internal_elm_win)
          ec->take_focus = 1;
        EC_CHANGED(ec);
     }
   else if (ec->override)
     {
        if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
          {
             ec->visible = 1;
             return;
          }
        ec->redirected = !ec->input_only;
        ec->changes.visible = ec->visible = 1;
        EC_CHANGED(ec);
        if (!_e_comp_x_client_data_get(ec)->first_map)
          _e_comp_x_client_evas_init(ec);
     }
   if (!ec->input_only)
     _e_comp_x_client_damage_add(ec);
   if (!_e_comp_x_client_data_get(ec)->need_reparent)
     {
        e_pixmap_usable_set(ec->pixmap, 1);
        if (ec->hidden || ec->iconic)
          {
             evas_object_hide(ec->frame);
             e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
          }
        else if (ec->override || (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_NORMAL))
          evas_object_show(ec->frame);
        _e_comp_x_client_data_get(ec)->first_map = 1;
        if (ec->internal_elm_win)
          {
             _e_comp_x_post_client_idler_add(ec);
             ec->post_move = 1;
             ec->post_resize = 1;
             e_comp_x->restack = 1;
          }
     }
}

static Eina_Bool
_e_comp_x_show_retry(void *data EINA_UNUSED)
{
   uintptr_t *win;

   EINA_LIST_FREE(e_comp_x->retry_clients, win)
     {
        E_Client *ec;

        ec = _e_comp_x_client_new((Ecore_X_Window)(uintptr_t)win, 0);
        if (ec) _e_comp_x_show_helper(ec);
     }

   e_comp_x->retry_timer = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_show(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Show *ev)
{
   E_Client *ec;

   //INF("X SHOW: %u", ev->win);
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec)
     {
        E_Comp *c;

        if (e_comp_ignore_win_find(ev->win)) return ECORE_CALLBACK_RENEW;
        c = e_comp_find_by_window(ev->event_win);
        if (!c) return ECORE_CALLBACK_RENEW;
        if (ev->event_win != e_comp->root) return ECORE_CALLBACK_RENEW;
        if ((c->win == ev->win) || (c->ee_win == ev->win) ||
            (c->root == ev->win) || (c->cm_selection == ev->win)) return ECORE_CALLBACK_RENEW;
        /* some window which we haven't made a client for yet but need to */
        ec = _e_comp_x_client_new(ev->win, 0);
        if (!ec)
          {
             if (c->x_comp_data->retry_timer)
               ecore_timer_loop_reset(c->x_comp_data->retry_timer);
             else
               c->x_comp_data->retry_timer = ecore_timer_loop_add(0.02, _e_comp_x_show_retry, c);
             c->x_comp_data->retry_clients = eina_list_append(c->x_comp_data->retry_clients, (uintptr_t*)(unsigned long)ev->win);
             return ECORE_CALLBACK_RENEW;
          }
     }
   else if (e_object_is_del(E_OBJECT(ec)) || ec->already_unparented)
     return ECORE_CALLBACK_RENEW;
   _e_comp_x_show_helper(ec);
   if (ec->internal || (!ec->override))
     _e_comp_x_client_stack(ec);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_hide(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Hide *ev)
{
   E_Client *ec;
   Eina_Bool hid = EINA_FALSE;

   //INF("X HIDE: %u", ev->win);
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec)
     {
        if (!e_comp_x->retry_clients) return ECORE_CALLBACK_RENEW;
        e_comp_x->retry_clients = eina_list_remove(e_comp_x->retry_clients, (uintptr_t*)(unsigned long)ev->win);
        if (!e_comp_x->retry_clients)
          E_FREE_FUNC(e_comp_x->retry_timer, ecore_timer_del);
        return ECORE_CALLBACK_PASS_ON;
     }
   if ((!ec->visible) || (ec->hidden && ec->unredirected_single))
     {
        //INF("IGNORED");
        return ECORE_CALLBACK_PASS_ON;
     }

   if (ev->win != ev->event_win)
     {
        if ((!ec->override) && (!ev->send_event)) return ECORE_CALLBACK_PASS_ON;
     }
   if (ec->ignore_first_unmap > 0)
     {
        ec->ignore_first_unmap--;
        return ECORE_CALLBACK_PASS_ON;
     }
   /* Don't delete hidden or iconified windows */
   if (ec->iconic)
     {
        /* Only hide the border if it is visible */
        hid = EINA_TRUE;
        evas_object_hide(ec->frame);
     }
   else
     {
        hid = EINA_TRUE;
        evas_object_hide(ec->frame);
        if (ec->internal)
          e_hints_window_hidden_set(ec);
        else
          {
             if (ec->exe_inst && ec->exe_inst->exe)
               ec->exe_inst->phony = 0;
             DELD(ec, 3);
             e_object_del(E_OBJECT(ec));
          }
     }
   if (hid)
     _e_comp_x_focus_check();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_reparent(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Reparent *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if ((!ec) || (ev->win == e_client_util_pwin_get(ec))) return ECORE_CALLBACK_PASS_ON;
   DBG("== repar [%u] to [%u]", ev->win, ev->parent);
   /* FIXME: this is almost definitely wrong */
   if (ev->parent != e_client_util_pwin_get(ec))
     {
        e_pixmap_parent_window_set(ec->pixmap, ev->parent);
        if (!e_object_is_del(E_OBJECT(ec)))
          e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_configure(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Configure *ev)
{
   E_Client *ec;
   Eina_Bool move, resize;

   if (e_comp_find_by_window(ev->win))
     {
        // do not handle this here - use randr events
        //e_comp_canvas_resize(ev->w, ev->h);
        return ECORE_CALLBACK_RENEW;
     }
   ec = _e_comp_x_client_find_by_window(ev->win);
   if ((!ec) || (!ec->override) || (!ev->override) || e_client_is_stacking(ec)) return ECORE_CALLBACK_PASS_ON;

   //INF("configure %p: %d,%d %dx%d", ec, ev->x, ev->y, ev->w, ev->h);
   if (ev->abovewin == 0)
     {
        // forcibly lower the window as it's not above anything
        if (e_client_below_get(ec)) evas_object_lower(ec->frame);
     }
   else
     {
        E_Client *ec_above = _e_comp_x_client_find_by_window(ev->abovewin);

        // if we find the client the window is above
        if (ec_above)
          {
             // if the window to stack above is a layer marker window/client
             // then we need to find the next one up and stack below that
             // because the ec_above will be one layer below as layer marker
             // is at the top of a layer
             if (e_client_is_stacking(ec_above))
               {
                  E_Client *ec_next_up = e_client_above_get(ec_above);

                  // stacking under the next win up == above ec_above
                  if (ec_next_up)
                    {
                       evas_object_layer_set(ec->frame, ec_next_up->layer);
                       evas_object_stack_below(ec->frame, ec_next_up->frame);
                    }
                  else // no marker above, so live on the same layer - on top
                    {
                       evas_object_layer_set(ec->frame, ec_above->layer);
                    }
               }
             // if it's above is a regular win
             else
               {
                  // if the layer obj is not right below already...
                  evas_object_layer_set(ec->frame, ec_above->layer);
                  evas_object_stack_above(ec->frame, ec_above->frame);
               }
          }
        // we didnt find the client it's above - so stuff this on top
        else
          {
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_PRIO);
          }
     }
   move = (ec->client.x != ev->x) || (ec->client.y != ev->y);
   resize = (ec->client.w != ev->w) || (ec->client.h != ev->h);
   if (!ec->internal)
     _e_comp_x_client_data_get(ec)->pw = ev->w, _e_comp_x_client_data_get(ec)->ph = ev->h;
   EINA_RECTANGLE_SET(&ec->client, ev->x, ev->y, ev->w, ev->h);
   if (move)
     evas_object_move(ec->frame, ev->x, ev->y);
   if (resize && e_pixmap_is_x(ec->pixmap))
     {
        e_pixmap_dirty(ec->pixmap);
        evas_object_resize(ec->frame, ev->w, ev->h);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_configure_request_timer(void *d)
{
   Pending_Configure *pc = d;
   eina_hash_list_remove(pending_configures, &pc->win, pc);
   free(pc);
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_configure_request(void *data  EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Configure_Request *ev)
{
   E_Client *ec;
   Eina_Bool move = EINA_FALSE, resize = EINA_FALSE;
   int ox, oy, ow, oh;
   int x, y, w, h;

//   printf("configure request {0x%08x}  %4i,%4i %4ix%4i b=%i [%c%c%c%c]\n",
//          ev->win, ev->x, ev->y, ev->w, ev->h, ev->border,
//          ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X ? 'x' : ' ',
//          ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y ? 'y' : ' ',
//          ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_W ? 'w' : ' ',
//          ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_H ? 'h' : ' ');
   if (e_comp_find_by_window(ev->win)) return ECORE_CALLBACK_RENEW;
   ec = _e_comp_x_client_find_by_window(ev->win);

   /* pass through requests for windows we haven't/won't reparent yet */
   if (ec && (!_e_comp_x_client_data_get(ec)->need_reparent) && (!_e_comp_x_client_data_get(ec)->reparented))
     {
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X)
          _e_comp_x_client_data_get(ec)->initial_attributes.x = ev->x;
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y)
          _e_comp_x_client_data_get(ec)->initial_attributes.y = ev->y;
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_W)
          ec->w = ec->client.w = _e_comp_x_client_data_get(ec)->initial_attributes.w = ev->w;
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_H)
          ec->h = ec->client.h = _e_comp_x_client_data_get(ec)->initial_attributes.h = ev->h;
        ec->border_size = ev->border;
        ec->changes.size = 1;
        ec = NULL;
     }
   if (!ec)
     {
        if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X) ||
            (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y))
          {
             Pending_Configure *pc = E_NEW(Pending_Configure, 1);
             pc->point.x = ev->x;
             pc->point.y = ev->y;
             pc->timer = ecore_timer_loop_add(5.0, _e_comp_x_configure_request_timer, pc);
             pc->win = ev->win;
             eina_hash_list_append(pending_configures, &ev->win, pc);
          }
        ecore_x_window_configure(ev->win, ev->value_mask,
                                 ev->x, ev->y, ev->w, ev->h, ev->border,
                                 ev->abovewin, ev->detail);
        return ECORE_CALLBACK_PASS_ON;
     }

   x = ox = ec->client.x;
   y = oy = ec->client.y;
   w = ow = ec->client.w;
   h = oh = ec->client.h;

   if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X) ||
       (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y))
     {
        int zx, zy, zw, zh;

        e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X)
          {
             _e_comp_x_client_data_get(ec)->initial_attributes.x = ev->x;
             if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
               x = E_CLAMP(ev->x, zx, zx + zw - ec->w);
             else
               x = ev->x;
          }
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y)
          {
             _e_comp_x_client_data_get(ec)->initial_attributes.y = ev->y;
             if (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE)
               y = E_CLAMP(ev->y, zy, zy + zh - ec->h);
             else
               y = ev->y;
          }
     }

   if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_W) ||
       (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_H))
     {
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_W)
          w = ev->w, _e_comp_x_client_data_get(ec)->initial_attributes.w = ev->w;
        if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_H)
          h = ev->h, _e_comp_x_client_data_get(ec)->initial_attributes.h = ev->h;
     }

   e_comp_object_frame_xy_adjust(ec->frame, x, y, &x, &y);
   e_comp_object_frame_wh_adjust(ec->frame, w, h, &w, &h);

   if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_X) ||
       (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_Y))
     move = (x != ec->x) || (y != ec->y);
   if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_W) ||
       (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_H))
     resize = (w != ec->w) || (h != ec->h);

   if (move && (!ec->lock_client_location))
     {
        if ((ec->maximized & E_MAXIMIZE_TYPE) != E_MAXIMIZE_NONE)
          {
             E_Zone *zone;

             e_comp_object_frame_xy_unadjust(ec->frame, x, y, &ec->saved.x, &ec->saved.y);

             zone = e_comp_zone_xy_get(x, y);
             if (zone && (zone->x || zone->y))
               {
                  ec->saved.x -= zone->x;
                  ec->saved.y -= zone->y;
               }
          }
        else if (!ec->maximize_override)
          {
             /* client is completely outside the screen, policy does not allow */
             if (((!E_INTERSECTS(x, y, ec->w, ec->h, 0, 0, e_comp->w - 5, e_comp->h - 5)) &&
                 (e_config->screen_limits != E_CLIENT_OFFSCREEN_LIMIT_ALLOW_FULL)) ||
                 /* client is partly outside the zone, policy does not allow */
                 (((!E_INSIDE(x, y, 0, 0, e_comp->w - 5, e_comp->h - 5)) &&
                  (!E_INSIDE(x + ec->w, y, 0, 0, e_comp->w - 5, e_comp->h - 5)) &&
                  (!E_INSIDE(x, y + ec->h, 0, 0, e_comp->w - 5, e_comp->h - 5)) &&
                  (!E_INSIDE(x + ec->w, y + ec->h, 0, 0, e_comp->w - 5, e_comp->h - 5))) &&
                  (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_NONE))
                )
               e_comp_object_util_center(ec->frame);
             else
               {
                  evas_object_move(ec->frame, x, y);
               }
          }
     }

   if (resize && (!ec->lock_client_size) && (move || ((!ec->maximized) && (!ec->fullscreen))))
     {
        if (ec->shaded || ((ec->maximized & E_MAXIMIZE_TYPE) != E_MAXIMIZE_NONE))
          e_comp_object_frame_wh_unadjust(ec->frame, w, h, &ec->saved.w, &ec->saved.h);
        else if (!ec->maximize_override)
          {
             evas_object_resize(ec->frame, w, h);
          }
     }
   if (!ec->lock_client_stacking)
     {
        if ((ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE) &&
            (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING))
          {
             E_Client *oec;

             if (ev->detail == ECORE_X_WINDOW_STACK_ABOVE)
               {
                  oec = _e_comp_x_client_find_by_window(ev->abovewin);
                  if (oec)
                    {
                       evas_object_stack_above(ec->frame, oec->frame);
                    }
                  else
                    {
                       ecore_x_window_configure(e_client_util_pwin_get(ec),
                                                ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING |
                                                ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE,
                                                0, 0, 0, 0, 0,
                                                ev->abovewin, ECORE_X_WINDOW_STACK_ABOVE);
                    }
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_BELOW)
               {
                  oec = _e_comp_x_client_find_by_window(ev->abovewin);
                  if (oec)
                    {
                       evas_object_stack_below(ec->frame, oec->frame);
                    }
                  else
                    {
                       ecore_x_window_configure(e_client_util_pwin_get(ec),
                                                ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING |
                                                ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE,
                                                0, 0, 0, 0, 0,
                                                ev->abovewin, ECORE_X_WINDOW_STACK_BELOW);
                    }
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_TOP_IF)
               {
                  /* FIXME: do */
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_BOTTOM_IF)
               {
                  /* FIXME: do */
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_OPPOSITE)
               {
                  /* FIXME: do */
               }
          }
        else if (ev->value_mask & ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE)
          {
             if (ev->detail == ECORE_X_WINDOW_STACK_ABOVE)
               {
                  evas_object_raise(ec->frame);
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_BELOW)
               {
                  evas_object_lower(ec->frame);
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_TOP_IF)
               {
                  /* FIXME: do */
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_BOTTOM_IF)
               {
                  /* FIXME: do */
               }
             else if (ev->detail == ECORE_X_WINDOW_STACK_OPPOSITE)
               {
                  /* FIXME: do */
               }
          }
     }

   /* FIXME: need to send synthetic stacking event to, as well as move/resize */
   if ((((ec->maximized & E_MAXIMIZE_TYPE) != E_MAXIMIZE_NONE) && (move || resize)) ||
       ((!move) && (!resize)))
     {
        _e_comp_x_client_move_resize_send(ec);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_stack_request(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Stack_Request *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;
   if (ev->detail == ECORE_X_WINDOW_STACK_ABOVE)
     evas_object_raise(ec->frame);
   else if (ev->detail == ECORE_X_WINDOW_STACK_BELOW)
     evas_object_lower(ec->frame);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_stack(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Stack *ev)
{
   E_Client *ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_PASS_ON;
   if (ev->detail == ECORE_X_WINDOW_STACK_ABOVE) evas_object_raise(ec->frame);
   else evas_object_lower(ec->frame);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_property(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Property *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_PASS_ON;

   if (ev->atom == ECORE_X_ATOM_WM_NAME)
     {
        if ((!ec->netwm.name) &&
            (!ec->netwm.fetch.name))
          {
             ec->icccm.fetch.title = 1;
             EC_CHANGED(ec);
          }
     }
   else if (ev->atom == ECORE_X_ATOM_NET_WM_NAME)
     {
        ec->netwm.fetch.name = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_CLASS)
     {
        ec->icccm.fetch.name_class = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_ICON_NAME)
     {
        if ((!ec->netwm.icon_name) &&
            (!ec->netwm.fetch.icon_name))
          {
             ec->icccm.fetch.icon_name = 1;
             EC_CHANGED(ec);
          }
     }
   else if (ev->atom == ECORE_X_ATOM_NET_WM_ICON_NAME)
     {
        ec->netwm.fetch.icon_name = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_CLIENT_MACHINE)
     {
        ec->icccm.fetch.machine = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_PROTOCOLS)
     {
        ec->icccm.fetch.protocol = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_HINTS)
     {
        ec->icccm.fetch.hints = 1;
        EC_CHANGED(ec);
        if (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
          ec->ignored = 0;
     }
   else if (ev->atom == ECORE_X_ATOM_WM_NORMAL_HINTS)
     {
        if (_e_comp_x_client_data_get(ec)->internal_props_set)
          _e_comp_x_client_data_get(ec)->internal_props_set--;
        else
          {
             ec->icccm.fetch.size_pos_hints = 1;
             EC_CHANGED(ec);
          }
     }
   else if (ev->atom == ECORE_X_ATOM_MOTIF_WM_HINTS)
     {
        /*
           if ((ec->netwm.type == E_WINDOW_TYPE_UNKNOWN) &&
            (!ec->netwm.fetch.type))
           {
         */
        ec->mwm.fetch.hints = 1;
        EC_CHANGED(ec);
        /*
           }
         */
     }
   else if (ev->atom == ECORE_X_ATOM_WM_TRANSIENT_FOR)
     {
        ec->icccm.fetch.transient_for = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_CLIENT_LEADER)
     {
        ec->icccm.fetch.client_leader = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_WM_WINDOW_ROLE)
     {
        ec->icccm.fetch.window_role = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_NET_WM_ICON)
     {
        ec->netwm.fetch.icon = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ATM__QTOPIA_SOFT_MENU)
     {
        ec->qtopia.fetch.soft_menu = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ATM__QTOPIA_SOFT_MENUS)
     {
        ec->qtopia.fetch.soft_menus = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD_STATE)
     {
        ec->vkbd.fetch.state = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_VIRTUAL_KEYBOARD)
     {
        ec->vkbd.fetch.vkbd = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_CONFORMANT)
     {
        _e_comp_x_client_data_get(ec)->illume.conformant.fetch.conformant = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_STATE)
     {
        _e_comp_x_client_data_get(ec)->illume.quickpanel.fetch.state = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_QUICKPANEL)
     {
        _e_comp_x_client_data_get(ec)->illume.quickpanel.fetch.quickpanel = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_PRIORITY_MAJOR)
     {
        _e_comp_x_client_data_get(ec)->illume.quickpanel.fetch.priority.major = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_PRIORITY_MINOR)
     {
        _e_comp_x_client_data_get(ec)->illume.quickpanel.fetch.priority.minor = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_QUICKPANEL_ZONE)
     {
        _e_comp_x_client_data_get(ec)->illume.quickpanel.fetch.zone = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_DRAG_LOCKED)
     {
        _e_comp_x_client_data_get(ec)->illume.drag.fetch.locked = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_DRAG)
     {
        _e_comp_x_client_data_get(ec)->illume.drag.fetch.drag = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_ILLUME_WINDOW_STATE)
     {
        _e_comp_x_client_data_get(ec)->illume.win_state.fetch.state = 1;
        EC_CHANGED(ec);
     }
   /*
      else if (ev->atom == ECORE_X_ATOM_NET_WM_USER_TIME)
      {
        ec->netwm.fetch.user_time = 1;
        EC_CHANGED(ec);
      }
      else if (ev->atom == ECORE_X_ATOM_NET_WM_STRUT)
      {
        ec->netwm.fetch.strut = 1;
        EC_CHANGED(ec);
      }
      else if (ev->atom == ECORE_X_ATOM_NET_WM_STRUT_PARTIAL)
      {
        ec->netwm.fetch.strut = 1;
        EC_CHANGED(ec);
      }
    */
   else if (ev->atom == ECORE_X_ATOM_NET_WM_SYNC_REQUEST_COUNTER)
     {
        //printf("ECORE_X_ATOM_NET_WM_SYNC_REQUEST_COUNTER\n");
     }
   else if (ev->atom == ECORE_X_ATOM_E_VIDEO_POSITION)
     {
        ec->e.fetch.video_position = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_VIDEO_PARENT)
     {
        ec->e.fetch.video_parent = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_NET_WM_STATE)
     {
        ec->netwm.fetch.state = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_NET_WM_WINDOW_OPACITY)
     {
        if (ec->netwm.opacity_changed)
          ec->netwm.opacity_changed = 0;
        else
          {
             ec->netwm.fetch.opacity = 1;
             EC_CHANGED(ec);
          }
     }
   else if (ev->atom == ECORE_X_ATOM_E_WINDOW_PROFILE_SUPPORTED)
     {
        ec->e.fetch.profile = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ECORE_X_ATOM_E_WINDOW_PROFILE_AVAILABLE_LIST)
     {
        ec->e.fetch.profile = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ATM_GTK_FRAME_EXTENTS)
     {
        _e_comp_x_client_data_get(ec)->fetch_gtk_frame_extents = 1;
        EC_CHANGED(ec);
     }
   else if (ev->atom == ATM_STEAM_GAME)
     {
        e_hints_window_steam_game_get(ec);
        ec->changes.icon = 1;
        EC_CHANGED(ec);
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_message(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Client_Message *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec)
     {
        char *name;

        if (!eina_log_domain_level_check(e_log_dom, EINA_LOG_LEVEL_DBG)) return ECORE_CALLBACK_RENEW;
        name = ecore_x_atom_name_get(ev->message_type);
        DBG("missed client message '%s' for %u", name, ev->win);
        free(name);
        return ECORE_CALLBACK_RENEW;
     }
   if (ev->message_type == ECORE_X_ATOM_NET_WM_WINDOW_OPACITY)
     {
        ec->netwm.fetch.opacity = 1;
        EC_CHANGED(ec);
     }
   else if (ev->message_type == ECORE_X_ATOM_E_WINDOW_PROFILE_CHANGE)
     {
        if (ec->e.state.profile.use)
          {
             char *p = ecore_x_atom_name_get(ev->data.l[1]);
             if (e_client_desk_window_profile_available_check(ec, p))
               {
                  if (e_util_strcmp(p, ec->desk->window_profile))
                    {
                       E_Desk *old_desk = ec->desk;
                       E_Desk *desk = e_comp_desk_window_profile_get(p);
                       Eina_Bool was_focused = e_client_stack_focused_get(ec);

                       if ((desk) && (desk != old_desk))
                         {
                            e_client_desk_set(ec, desk);
                            if (was_focused)
                              e_desk_last_focused_focus(old_desk);
                         }
                    }
               }
             free(p);
          }
     }
   else if (ev->message_type == ECORE_X_ATOM_NET_ACTIVE_WINDOW)
     {
#if 0 /* notes */
        if (e->data.l[0] == 0 /* 0 == old, 1 == client, 2 == pager */)
          {
          }
        timestamp = e->data.l[1];
        requestor_id e->data.l[2];
#endif
        switch (e_config->window_activehint_policy)
          {
           case 0: break;

           case 1:
             e_client_urgent_set(ec, 1);
             break;

           default:
             if (e_client_action_get() || e_comp->nocomp)
               {
                  /* be helpful and ignore activates during window actions, but still set urgent */
                  e_client_urgent_set(ec, 1);
                  break;
               }
             if ((!starting) && (!ec->focused))
               {
                  if ((e_config->window_activehint_policy == E_ACTIVEHINT_POLICY_ACTIVATE_EXCLUDE) &&
                      (!ec->desk->visible))
                    e_client_urgent_set(ec, 1);
                  else
                    {
                       /* some apps, eg. libreoffice, create "modal" windows which
                        * do not have the netwm modal state set. instead, attempting to
                        * focus the parent window results in the app immediately trying to
                        * activate the "modal" window, triggering an infinite loop.
                        *
                        * by treating this "modal" window as a genuine modal window,
                        * we (eventually) can ignore this type of bad behavior and
                        * give the application/user the expected behavior
                        */
                       if (ec->parent && (!ec->parent->modal) && (e_client_focused_get() == ec->parent) &&
                           (ev->time - focus_time < PARENT_ACTIVATE_TIME))
                         {
                            E_Comp_X_Client_Data *cd;

                            cd = _e_comp_x_client_data_get(ec);
                            if (cd)
                              {
                                 cd->parent_activate_count++;
                                 if (cd->parent_activate_count >= PARENT_ACTIVATE_LIMIT)
                                   _e_comp_x_client_modal_setup(ec);
                              }
                         }
                       e_client_activate(ec, EINA_TRUE);
                    }
               }
             else
               evas_object_raise(ec->frame);
          }
     }
   else if (ev->message_type == ECORE_X_ATOM_E_WINDOW_PROFILE_CHANGE_DONE)
     {
        if ((ec->e.state.profile.use) &&
            (ec->e.state.profile.wait_for_done))
          {
             char *p = ecore_x_atom_name_get(ev->data.l[1]);
             if (!e_util_strcmp(ec->e.state.profile.wait, p))
               {
                  E_Desk *old_desk = ec->desk;
                  E_Desk *desk = ec->e.state.profile.wait_desk;
                  Eina_Bool was_focused = e_client_stack_focused_get(ec);

                  eina_stringshare_replace(&ec->e.state.profile.wait, NULL);
                  ec->e.state.profile.wait_for_done = 0;
                  if ((desk) && (desk != old_desk))
                    {
                       eina_stringshare_replace(&ec->e.state.profile.name,
                                                desk->window_profile);
                       e_client_desk_set(ec, desk);
                       if (was_focused)
                         e_desk_last_focused_focus(old_desk);
                    }
                  e_client_desk_window_profile_wait_desk_set(ec, NULL);
               }
             free(p);
          }
     }
   else if (ev->message_type == ATM_NETWM_SHOW_WINDOW_MENU)
     {
       /*
         message_type = _NET_WM_SHOW_WINDOW_MENU
         window = window for which the menu should be shown
         format = 32
         data.l[0] = xinput2_device_id
         data.l[1] = root_x
         data.l[2] = root_y
         other data.l[] elements = 0
        */

        int x, y;

        x = ev->data.l[1];
        y = ev->data.l[2];
        e_int_client_menu_show(ec,
          e_comp_canvas_x_root_adjust(x),
          e_comp_canvas_y_root_adjust(y),
          0, 0);
     }
   else if (ev->message_type == ATM_NETWM_PERFORM_BUTTON_ACTION)
     {
        char emission[128];
       /*
         message_type = _NET_WM_PERFORM_BUTTON_ACTION
         window = window for which the action should be performed
         format = 32
         data.l[0] = xinput2_device_id
         data.l[1] = root_x
         data.l[2] = root_y
         data.l[3] = button
         data.l[4] = timestamp
        */
        if (e_dnd_active() || ec->iconic) return ECORE_CALLBACK_RENEW;
        switch (ev->data.l[3])
          {
           case 1:
           case 2:
           case 3:
             snprintf(emission, sizeof(emission), "mouse,down,%d", (int)ev->data.l[3]);
             e_bindings_signal_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(ec), emission, "e.event.titlebar");
             break;
           case 4:
             e_bindings_signal_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(ec), "mouse,wheel,?,-1", "e.event.titlebar");
             break;
           case 5:
             e_bindings_signal_handle(E_BINDING_CONTEXT_WINDOW, E_OBJECT(ec), "mouse,wheel,?,1", "e.event.titlebar");
             break;
          }
     }
#ifdef HAVE_WAYLAND
   else if (ev->message_type == WL_SURFACE_ID)
     {
        void *res;
        E_Client *wc = NULL;

        if (e_comp->comp_type != E_PIXMAP_TYPE_WL) return ECORE_CALLBACK_RENEW;
        res = wl_client_get_object(e_comp_wl->xwl_client, ev->data.l[0]);
        if (res)
          wc = wl_resource_get_user_data(res);
        if (wc)
          {
             if (wc->internal)
               {
                  e_object_del(E_OBJECT(wc));
                  e_object_del(E_OBJECT(ec));
                  /* this is an xwayland bug. somehow the wrong surface is being passed
                   * and it's an internal surface--internal surfaces are NEVER
                   * xwayland clients.
                   *
                   * ever.
                   */
                  return ECORE_CALLBACK_RENEW;
               }
             e_comp_x_xwayland_client_setup(ec, wc);
          }
        else
          {
             ec->comp_data->surface_id = ev->data.l[0];
             e_comp_wl_xwayland_client_queue(ec);
          }
     }
#endif
   else
     {
        char *name;

        if (!eina_log_domain_level_check(e_log_dom, EINA_LOG_LEVEL_DBG)) return ECORE_CALLBACK_RENEW;
        name = ecore_x_atom_name_get(ev->message_type);
        DBG("missed client message '%s' for %u", name, ev->win);
        free(name);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_state_request(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_State_Request *ev)
{
   int i;
   E_Client *ec;
   int max;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;

   max = (1 << ev->state[0]) | (1 << ev->state[1]);
   if ((max & (1 << ECORE_X_WINDOW_STATE_MAXIMIZED_VERT)) &&
       (max & (1 << ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ)))
     e_hints_window_state_update(ec, INT_MAX, ev->action);
   else
     {
        for (i = 0; i < 2; i++)
          e_hints_window_state_update(ec, ev->state[i], ev->action);
     }

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_mapping_change(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Mapping_Change *ev EINA_UNUSED)
{
   const Eina_List *l;
   E_Client *ec;

   if (_e_comp_x_mapping_change_disabled) return ECORE_CALLBACK_RENEW;
   printf("COMPX: _e_comp_x_mapping_change\n");
   e_comp_canvas_keys_ungrab();
   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_X) continue;
        if ((!_e_comp_x_client_data_get(ec)->first_map) || (!_e_comp_x_client_data_get(ec)->reparented)) continue;
        if (ec->focused)
          {
             _e_comp_x_focus_setup(ec);
             _e_comp_x_focus_setdown(ec);
          }
        else
          {
             _e_comp_x_focus_setdown(ec);
             _e_comp_x_focus_setup(ec);
             e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
             e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
          }
     }
   e_comp_canvas_keys_grab();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_powersave(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Powersave_Update *ev = event;
   Eina_List *l;
   E_Client *ec;

   if (e_comp->comp_type == E_PIXMAP_TYPE_WL) return ECORE_CALLBACK_PASS_ON;
   switch (ev->mode)
     {
      case E_POWERSAVE_MODE_NONE:
      case E_POWERSAVE_MODE_LOW:
      case E_POWERSAVE_MODE_MEDIUM:
      case E_POWERSAVE_MODE_HIGH:
      case E_POWERSAVE_MODE_EXTREME:
        if (e_comp->frozen)
          {
             e_comp->frozen = EINA_FALSE;
             EINA_LIST_FOREACH(e_comp->clients, l, ec)
               {
                  if (ec->frozen)
                    {
                       ec->frozen = EINA_FALSE;
                       // XXX: update props
                       if (e_client_has_xwindow(ec))
                         {
                            if (!ec->iconic)
                              {
                                 ecore_x_icccm_state_set(e_client_util_win_get(ec),
                                                         ECORE_X_WINDOW_STATE_HINT_NORMAL);
                              }
                            e_hints_window_state_set(ec);
                         }
                    }
               }
          }
        break;
      case E_POWERSAVE_MODE_FREEZE:
        if (!e_comp->frozen)
          {
             e_comp->frozen = EINA_TRUE;
             EINA_LIST_FOREACH(e_comp->clients, l, ec)
               {
                  if (!ec->frozen)
                    {
                       ec->frozen = EINA_TRUE;
                       // XXX: update props
                       if (e_client_has_xwindow(ec))
                         {
                            if (!ec->iconic)
                              {
                                 ecore_x_icccm_state_set(e_client_util_win_get(ec),
                                                         ECORE_X_WINDOW_STATE_HINT_ICONIC);
                              }
                            e_hints_window_state_set(ec);
                         }
                    }
               }
          }
        break;
      default:
        break;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_comp_x_mouse_in_job(void *d EINA_UNUSED)
{
   E_Client *ecf = e_client_focused_get();

   // if we're fullscreen and see a mout out within a short time of having
   // set this client ot fullscreen - it's a "false flag" that is a bi-product
   // of things moving around while becoming fullscreen, so ignore it
   if ((ecf) && (ecf != mouse_client))
     {
        if ((ecf->fullscreen) &&
            (ecore_time_get() - ecf->fullscreen_time) < 0.2) // 0.2sec enough
          goto done;
     }
   if (mouse_client)
     e_client_mouse_in(mouse_client, e_comp_canvas_x_root_adjust(mouse_in_coords.x), e_comp_canvas_y_root_adjust(mouse_in_coords.y));
done:
   mouse_in_job = NULL;
}

static E_Client *
_e_comp_x_e_client_obj_get(Evas_Object *o)
{
   Evas_Object *par;

   if (evas_object_data_get(o, "comp_object"))
     return e_comp_object_client_get(o);
   par = evas_object_smart_parent_get(o);
   if (!par) return NULL;
   return _e_comp_x_e_client_obj_get(par);
}

static Eina_Bool
_e_comp_x_mouse_in_fix_check_timer_cb(void *data EINA_UNUSED)
{
   E_Client *ec = NULL;
   Eina_List *l, *in_list;
   Evas_Object *o;
   int x, y;

   mouse_in_fix_check_timer = NULL;
   if (e_grabinput_key_win_get() || e_grabinput_mouse_win_get())
     return EINA_FALSE;
   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);

   in_list = evas_tree_objects_at_xy_get(e_comp->evas, NULL, x, y);
   EINA_LIST_FOREACH(in_list, l, o)
     {
        ec = _e_comp_x_e_client_obj_get(o);
        if (ec)
          {
             if ((!e_client_util_desk_visible
                  (ec, e_desk_current_get(e_zone_current_get())))) continue;
             break;
          }
     }
   eina_list_free(in_list);
   if (ec)
     {
        mouse_client = ec;
        if (mouse_in_job) ecore_job_del(mouse_in_job);
        mouse_in_job = ecore_job_add(_e_comp_x_mouse_in_job, NULL);
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_mouse_in(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Mouse_In *ev)
{
   E_Client *ec;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) goto done;
   if (ev->mode == ECORE_X_EVENT_MODE_NORMAL)
     {
        if (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR)
          {
             if (ev->win != e_client_util_pwin_get(ec)) goto done;
             if (ev->event_win != e_client_util_win_get(ec)) goto done;
          }
        if (ev->detail == ECORE_X_EVENT_DETAIL_VIRTUAL)
          {
             if (ev->win != e_client_util_win_get(ec)) goto done;
             if (ev->event_win != e_client_util_pwin_get(ec)) goto done;
          }
        if (!evas_object_visible_get(ec->frame)) goto done;
     }
   if (_e_comp_x_client_data_get(ec)->deleted) goto done;
   mouse_client = ec;
   if (mouse_in_job) ecore_job_del(mouse_in_job);
   mouse_in_job = ecore_job_add(_e_comp_x_mouse_in_job, NULL);
   mouse_in_coords.x = ev->root.x;
   mouse_in_coords.y = ev->root.y;
done:
   E_FREE_FUNC(mouse_in_fix_check_timer, ecore_timer_del);
   if ((!e_grabinput_mouse_win_get()) && (!e_grabinput_key_win_get()))
     mouse_in_fix_check_timer =
       ecore_timer_add(0.1, _e_comp_x_mouse_in_fix_check_timer_cb, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_mouse_out(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Mouse_In *ev)
{
   E_Client *ec;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   if ((ev->mode == ECORE_X_EVENT_MODE_UNGRAB) &&
       (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR))
     return ECORE_CALLBACK_PASS_ON;
   if (ev->mode == ECORE_X_EVENT_MODE_GRAB)
     return ECORE_CALLBACK_PASS_ON;
   if ((ev->mode == ECORE_X_EVENT_MODE_NORMAL) &&
       (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR))
     return ECORE_CALLBACK_PASS_ON;
   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;
   // if we're fullscreen and see a mout out within a short time of having
   // set this client ot fullscreen - it's a "false flag" that is a bi-product
   // of things moving around while becoming fullscreen, so ignore it
   if ((ec->fullscreen) &&
       (ecore_time_get() - ec->fullscreen_time) < 0.2) // 0.2sec enough
     return ECORE_CALLBACK_RENEW;
   if (_e_comp_x_client_data_get(ec)->deleted) return ECORE_CALLBACK_RENEW;
   if (mouse_client == ec)
     {
        mouse_client = NULL;
        E_FREE_FUNC(mouse_in_job, ecore_job_del);
     }
   if (ec->mouse.in)
     e_client_mouse_out(ec, e_comp_canvas_x_root_adjust(ev->root.x), e_comp_canvas_y_root_adjust(ev->root.y));
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_mouse_wheel(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Wheel *ev)
{
   E_Client *ec;
   E_Binding_Event_Wheel ev2;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   //if (action_input_win)
     //ec = action_border;
   //else
     {
        ec = _e_comp_x_client_find_by_window(ev->window);
        if ((!ec) && (ev->window != ev->event_window))
          ec = _e_comp_x_client_find_by_window(ev->event_window);
        if (!ec) return ECORE_CALLBACK_RENEW;
     }
   if (_e_comp_x_client_data_get(ec)->deleted) return ECORE_CALLBACK_RENEW;
   e_bindings_ecore_event_mouse_wheel_convert(ev, &ev2);
   e_client_mouse_wheel(ec, (Evas_Point*)&ev->root, &ev2);
   return ECORE_CALLBACK_RENEW;
}


static Eina_Bool
_e_comp_x_mouse_up(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
   E_Client *ec;
   E_Binding_Event_Mouse_Button ev2;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   //if (action_input_win)
     //ec = action_border;
   //else
     {
        ec = _e_comp_x_client_find_by_window(ev->window);
        if ((!ec) && (ev->window != ev->event_window))
          ec = _e_comp_x_client_find_by_window(ev->event_window);
        if (!ec)
          {
             if (e_client_comp_grabbed_get())
               ec = e_client_action_get();
             if (!ec) return ECORE_CALLBACK_RENEW;
          }
        if ((!ec) || e_client_util_ignored_get(ec)) return ECORE_CALLBACK_RENEW;
     }
   if (_e_comp_x_client_data_get(ec)->deleted) return ECORE_CALLBACK_RENEW;
   e_bindings_ecore_event_mouse_button_convert(ev, &ev2);
   e_client_mouse_up(ec, ev->buttons, (Evas_Point*)&ev->root, &ev2);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_mouse_down(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
   E_Client *ec;
   E_Binding_Event_Mouse_Button ev2;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   if (e_client_action_get()) return ECORE_CALLBACK_RENEW; //block extra mouse buttons during action
   //if (action_input_win)
     //ec = action_border;
   //else
     {
        ec = _e_comp_x_client_find_by_window(ev->window);
        if ((!ec) && (ev->window != ev->event_window))
          ec = _e_comp_x_client_find_by_window(ev->event_window);
        if ((!ec) || e_client_util_ignored_get(ec)) return ECORE_CALLBACK_RENEW;
     }
   if (_e_comp_x_client_data_get(ec)->deleted) return ECORE_CALLBACK_RENEW;
   e_bindings_ecore_event_mouse_button_convert(ev, &ev2);
   e_client_mouse_down(ec, ev->buttons, (Evas_Point*)&ev->root, &ev2);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_mouse_move(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Mouse_Move *ev)
{
   E_Client *ec;

   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;
   ec = e_client_action_get();
   if (!ec)
     {
        ec = _e_comp_x_client_find_by_window(ev->window);
        if ((!ec) && (ev->window != ev->event_window))
          ec = _e_comp_x_client_find_by_window(ev->event_window);
        if ((!ec) || e_client_util_ignored_get(ec)) return ECORE_CALLBACK_RENEW;
        if ((!ec->mouse.in) && evas_object_visible_get(ec->frame) && (!ec->desk->animate_count))
          {
             E_Client *tec;
             Ecore_Window top = e_comp_top_window_at_xy_get(ev->root.x, ev->root.y);
             int x, y;
             if (top == e_comp->ee_win) return ECORE_CALLBACK_RENEW;

             x = e_comp_canvas_x_root_adjust(ev->root.x);
             y = e_comp_canvas_y_root_adjust(ev->root.y);
             for (tec = e_client_above_get(ec); tec; tec = e_client_above_get(tec))
               {
                  if (!evas_object_visible_get(tec->frame)) continue;
                  if (E_INSIDE(x, y, tec->x, tec->y, tec->w, tec->h)) return ECORE_CALLBACK_RENEW;
               }
             if (!mouse_in_job)
               e_client_mouse_in(ec, x, y);
          }
        return ECORE_CALLBACK_RENEW;
     }
   E_COMP_X_PIXMAP_CHECK ECORE_CALLBACK_RENEW;
   if (_e_comp_x_client_data_get(ec)->deleted || e_client_util_ignored_get(ec)) return ECORE_CALLBACK_RENEW;
   if (e_client_util_resizing_get(ec) && (e_comp->comp_type == E_PIXMAP_TYPE_X) &&
       ec->netwm.sync.request &&
       _e_comp_x_client_data_get(ec)->alarm
      )
     {
        if ((ecore_loop_time_get() - ec->netwm.sync.send_time) > 0.5)
          {
             if (ec->pending_resize)
               {
                  ec->changes.pos = 1;
                  ec->changes.size = 1;
                  EC_CHANGED(ec);
                  _e_comp_x_client_move_resize_send(ec);
               }
             E_FREE_LIST(ec->pending_resize, free);

             ec->netwm.sync.wait = 0;
             /* sync.wait is incremented when resize_handle sends
              * sync-request and decremented by sync-alarm cb. so
              * we resize here either on initial resize, timeout or
              * when no new resize-request was added by sync-alarm cb.
              */
          }
     }
   e_client_mouse_move(ec, (Evas_Point*)&ev->root);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_focus_out(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Focus_Out *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;

   _e_comp_x_client_pri_norm(ec);
   if (ev->mode == ECORE_X_EVENT_MODE_NORMAL)
     {
        if (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_NON_LINEAR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_NON_LINEAR_VIRTUAL)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_POINTER)
          return ECORE_CALLBACK_PASS_ON;
     }
   else if (ev->mode == ECORE_X_EVENT_MODE_GRAB)
     {
        if (ev->detail == ECORE_X_EVENT_DETAIL_NON_LINEAR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_NON_LINEAR_VIRTUAL)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_ANCESTOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_VIRTUAL)
          return ECORE_CALLBACK_PASS_ON;
     }
   else if (ev->mode == ECORE_X_EVENT_MODE_UNGRAB)
     {
        /* for firefox/thunderbird (xul) menu walking */
        /* NB: why did i disable this before? */
        return ECORE_CALLBACK_PASS_ON;
        /* why only filter these out for an ungrab? skip all ungrabs...
        if (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_POINTER)
          return ECORE_CALLBACK_PASS_ON; */
     }
   else if (ev->mode == ECORE_X_EVENT_MODE_WHILE_GRABBED)
     {
        if (ev->detail == ECORE_X_EVENT_DETAIL_ANCESTOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_INFERIOR)
          return ECORE_CALLBACK_PASS_ON;
        else if (ev->detail == ECORE_X_EVENT_DETAIL_NON_LINEAR)
          return ECORE_CALLBACK_PASS_ON;
     }
   e_client_revert_focus(ec);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_client_property(void *d EINA_UNUSED, int t EINA_UNUSED, E_Event_Client_Property *ev)
{
   if ((ev->property & E_CLIENT_PROPERTY_ICON) && ev->ec->desktop)
     ecore_x_window_prop_string_set(e_client_util_win_get(ev->ec), E_ATOM_DESKTOP_FILE,
                                    ev->ec->desktop->orig_path);
   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_x_client_zone_geometry_set(E_Client *ec)
{
   unsigned int zgeom[4];

   E_COMP_X_PIXMAP_CHECK;
   zgeom[0] = ec->zone->x;
   zgeom[1] = ec->zone->y;
   zgeom[2] = ec->zone->w;
   zgeom[3] = ec->zone->h;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_ZONE_GEOMETRY, zgeom, 4);
}

static Eina_Bool
_e_comp_x_client_zone_set(void *data EINA_UNUSED, int type EINA_UNUSED, E_Event_Client *ev)
{
   E_Client *ec = ev->ec;

   if (e_object_is_del(E_OBJECT(ec))) return ECORE_CALLBACK_RENEW;
   E_COMP_X_PIXMAP_CHECK ECORE_CALLBACK_RENEW;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ev->ec), E_ATOM_ZONE, &ev->ec->zone->num, 1);
   _e_comp_x_client_zone_geometry_set(ev->ec);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_sync_alarm(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Sync_Alarm *ev)
{
   unsigned int serial;
   E_Client *ec;
//   Eina_Bool resize = EINA_FALSE;

   ec = _e_comp_x_client_find_by_alarm(ev->alarm);
   if ((!ec) || e_object_is_del(E_OBJECT(ec))) return ECORE_CALLBACK_RENEW;

   if (ec->netwm.sync.wait)
     ec->netwm.sync.wait--;

   if (ecore_x_sync_counter_query(_e_comp_x_client_data_get(ec)->sync_counter, &serial))
     {
        E_Client_Pending_Resize *pnd = NULL;

        /* skip pending for which we didn't get a reply */
        while (ec->pending_resize)
          {
             pnd = ec->pending_resize->data;
             ec->pending_resize = eina_list_remove(ec->pending_resize, pnd);

             if (serial == pnd->serial)
               break;

             E_FREE(pnd);
          }

        if (pnd)
          {
// disable this as it just seems to cause mis-sizing and issues...
//             resize = ((ec->w != pnd->w) || (ec->h != pnd->h));
//             e_comp_object_frame_wh_adjust(ec->frame, pnd->w, pnd->h, &ec->w, &ec->h);
             E_FREE(pnd);
          }
     }

// this too... as above
//   if (resize)
//     {
//        evas_object_resize(ec->frame, ec->w, ec->h);
//        if (ec->internal_elm_win)
//          evas_object_resize(ec->internal_elm_win, ec->client.w, ec->client.h);
//     }

   ec->netwm.sync.send_time = ecore_loop_time_get();
// this too - as above
//   if (resize)
//     {
//        ecore_x_pointer_xy_get(e_comp->root,
//                               &ec->mouse.current.mx,
//                               &ec->mouse.current.my);
//        e_client_mouse_move(ec, &(Evas_Point){ec->mouse.current.mx, ec->mouse.current.my});
//     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_desktop_change(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Desktop_Change *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (ec)
     {
        if (ev->desk == 0xffffffff)
          e_client_stick(ec);
        else if ((int)ev->desk < (ec->zone->desk_x_count * ec->zone->desk_y_count))
          {
             E_Desk *desk;

             desk = e_desk_at_pos_get(ec->zone, ev->desk);
             if (desk)
               e_client_desk_set(ec, desk);
          }
     }
   else
     ecore_x_netwm_desktop_set(ev->win, ev->desk);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_move_resize_request(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Move_Resize_Request *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;

   if ((ec->shaded) || (ec->shading) ||
       (ec->fullscreen) || (ec->moving) ||
       (ec->resize_mode != E_POINTER_RESIZE_NONE))
     return ECORE_CALLBACK_PASS_ON;

   if ((ev->button >= 1) && (ev->button <= 3))
     {
        ec->mouse.last_down[ev->button - 1].mx = ev->x;
        ec->mouse.last_down[ev->button - 1].my = ev->y;
        ec->mouse.last_down[ev->button - 1].x = ec->x;
        ec->mouse.last_down[ev->button - 1].y = ec->y;
        ec->mouse.last_down[ev->button - 1].w = ec->w;
        ec->mouse.last_down[ev->button - 1].h = ec->h;
     }
   else
     {
        ec->moveinfo.down.x = ec->x;
        ec->moveinfo.down.y = ec->y;
        ec->moveinfo.down.w = ec->w;
        ec->moveinfo.down.h = ec->h;
     }
   ec->mouse.current.mx = ev->x;
   ec->mouse.current.my = ev->y;
   ec->moveinfo.down.button = ev->button;
   ec->moveinfo.down.mx = ev->x;
   ec->moveinfo.down.my = ev->y;

   if (!ec->lock_user_stacking)
     evas_object_raise(ec->frame);

   if (ev->direction == E_POINTER_MOVE)
     {
        ec->cur_mouse_action = e_action_find("window_move");
        if (ec->cur_mouse_action)
          {
             if ((!ec->cur_mouse_action->func.end_mouse) &&
                 (!ec->cur_mouse_action->func.end))
               ec->cur_mouse_action = NULL;
             if (ec->cur_mouse_action)
               {
                  e_object_ref(E_OBJECT(ec->cur_mouse_action));
                  ec->cur_mouse_action->func.go(E_OBJECT(ec), NULL);
               }
          }
        return ECORE_CALLBACK_PASS_ON;
     }

   ec->cur_mouse_action = e_action_find("window_resize");
   if (ec->cur_mouse_action)
     {
        if ((!ec->cur_mouse_action->func.end_mouse) &&
            (!ec->cur_mouse_action->func.end))
          ec->cur_mouse_action = NULL;
     }
   if (!ec->cur_mouse_action) return ECORE_CALLBACK_RENEW;
   e_object_ref(E_OBJECT(ec->cur_mouse_action));
   ec->cur_mouse_action->func.go(E_OBJECT(ec), NULL);

   if (ev->direction != E_POINTER_RESIZE_NONE)
     {
        e_pointer_mode_pop(ec, ec->resize_mode);
        ec->resize_mode = ev->direction;
        e_pointer_mode_push(ec, ec->resize_mode);
     }
   switch (ev->direction)
     {
      case E_POINTER_RESIZE_TL:
        GRAV_SET(ec, ECORE_X_GRAVITY_SE);
        break;

      case E_POINTER_RESIZE_T:
        GRAV_SET(ec, ECORE_X_GRAVITY_S);
        break;

      case E_POINTER_RESIZE_TR:
        GRAV_SET(ec, ECORE_X_GRAVITY_SW);
        break;

      case E_POINTER_RESIZE_R:
        GRAV_SET(ec, ECORE_X_GRAVITY_W);
        break;

      case E_POINTER_RESIZE_BR:
        GRAV_SET(ec, ECORE_X_GRAVITY_NW);
        break;

      case E_POINTER_RESIZE_B:
        GRAV_SET(ec, ECORE_X_GRAVITY_N);
        break;

      case E_POINTER_RESIZE_BL:
        GRAV_SET(ec, ECORE_X_GRAVITY_NE);
        break;

      case E_POINTER_RESIZE_L:
        GRAV_SET(ec, ECORE_X_GRAVITY_E);
        break;

      default:
        return ECORE_CALLBACK_PASS_ON;
     }

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_focus_timer_cb(void *d EINA_UNUSED)
{
   E_Client *focused;

   /* if mouse-based focus policy clients exist for [focused] and [mouse_client],
    * [mouse_client] should have focus here.
    * race conditions in x11 focus setting can result in a scenario such that:
    * -> set focus on A
    * -> set focus on B
    * -> receive focus event on A
    * -> re-set focus on A
    * -> receive focus event on B
    * -> receive focus event on A
    * ...
    * where the end state is that the cursor is over a client which does not have focus.
    * this timer is triggered only when such eventing occurs in order to adjust the focused
    * client as necessary
    */
   focused = e_client_focused_get();
   if (mouse_client && focused && (!e_client_focus_policy_click(focused)) && (mouse_client != focused))
     {
        int x, y;

        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        if (E_INSIDE(x, y, mouse_client->x, mouse_client->y, mouse_client->w, mouse_client->h))
          {
             if (!_e_comp_x_client_data_get(mouse_client)->deleted)
               e_client_mouse_in(mouse_client, x, y);
          }
     }
   focus_timer = NULL;
   return EINA_FALSE;
}

static void
_e_comp_x_focus_timer(void)
{
   if (focus_timer)
     ecore_timer_loop_reset(focus_timer);
   else /* focus has changed twice in .002 seconds; .01 seconds should be more than enough delay */
     focus_timer = ecore_timer_loop_add(0.01, _e_comp_x_focus_timer_cb, NULL);
}

static Eina_Bool
_e_comp_x_focus_in(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Focus_In *ev)
{
   E_Client *ec, *focused;

   ec = _e_comp_x_client_find_by_window(ev->win);

   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        focused = e_client_focused_get();
        if (ec && focused)
          {
             if ((!ec->override) && (ec != focused))
               {
                  ecore_x_window_focus(e_client_util_win_get(focused));
                  ecore_x_icccm_take_focus_send(e_client_util_win_get(focused), ecore_x_current_time_get());
               }
          }
        else
          ecore_x_window_focus(e_comp->root);
        return ECORE_CALLBACK_RENEW;
     }
   if (!ec)
     {
        if ((ev->win == e_comp->ee_win) && (ev->time >= focus_canvas_time) && (!focus_time))
          {
             focused = e_client_focused_get();
             if (focused)
               evas_object_focus_set(focused->frame, 0);
             focus_canvas_time = 0;
          }
        return ECORE_CALLBACK_RENEW;
     }

   /* block refocus attempts on iconic clients
    * these result from iconifying a client during a grab */
   if (ec->iconic) return ECORE_CALLBACK_RENEW;

   focused = e_client_focused_get();

   _e_comp_x_client_pri_raise(ec);
   if (ev->mode == ECORE_X_EVENT_MODE_GRAB)
     {
        if (ev->detail == ECORE_X_EVENT_DETAIL_POINTER) return ECORE_CALLBACK_PASS_ON;
     }
   else if (ev->mode == ECORE_X_EVENT_MODE_UNGRAB)
     {
        /* this seems to break winlist...
        if (ev->detail == ECORE_X_EVENT_DETAIL_POINTER)
        */

        /* we're getting a non-linear focus re-set on a visible
         * window on current desk when no other client is focused;
         * this is probably when we're trying to reapply focus after
         * unfocusing some comp canvas widget
         */
        if ((ev->detail != ECORE_X_EVENT_DETAIL_NON_LINEAR) ||
            focused ||
            (!e_client_util_desk_visible(ec, e_desk_current_get(ec->zone))))
        return ECORE_CALLBACK_PASS_ON;
     }

   /* ignore focus in from !take_focus windows, we just gave it em */
   /* if (!ec->icccm.take_focus)
    *   return ECORE_CALLBACK_PASS_ON; */

   /* should be equal, maybe some clients don't reply with the proper timestamp ? */
   if (ev->time >= focus_time)
     evas_object_focus_set(ec->frame, 1);
   /* handle case of someone trying to benchmark focus handling */
   if ((!e_client_focus_policy_click(ec)) && (focused && (!e_client_focus_policy_click(focused))) &&
       (ev->time - focus_time <= 2))
     _e_comp_x_focus_timer();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_shape(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Shape *ev)
{
   E_Client *ec;
   Eina_Bool pshaped;
   //const char *txt[] =
   //{
      //[ECORE_X_SHAPE_BOUNDING] = "BOUNDING",
      //[ECORE_X_SHAPE_INPUT] = "INPUT",
      //[ECORE_X_SHAPE_CLIP] = "CLIP",
   //};

   ec = _e_comp_x_client_find_by_window(ev->win);
   if (!ec) return ECORE_CALLBACK_RENEW;
   pshaped = ec->shaped;
   //INF("%p(%s): %d,%d %dx%d || %d", ec, txt[ev->type], ev->x, ev->y, ev->w, ev->h, ev->shaped);
   if (ev->win == e_client_util_win_get(ec))
     {
        if (ev->type == ECORE_X_SHAPE_INPUT)
          {
             ec->changes.shape_input = 1;
             ec->need_shape_merge = 1;
          }
        else
          {
             if ((_e_comp_x_client_data_get(ec)->shape.x != ev->x) ||
                 (_e_comp_x_client_data_get(ec)->shape.y != ev->y) ||
                 (_e_comp_x_client_data_get(ec)->shape.w != ev->w) ||
                 (_e_comp_x_client_data_get(ec)->shape.h != ev->h))
               {
                  /* bounding box changed, need export for rendering */
                  EINA_RECTANGLE_SET(&_e_comp_x_client_data_get(ec)->shape, ev->x, ev->y, ev->w, ev->h);
                  ec->need_shape_export = !ec->override;
               }
             ec->changes.shape = 1;
          }
     }
   else if (ec->shaped) //shape change on parent window only valid if window is known to be shaped
     ec->need_shape_export = 1;
   if (ec->changes.shape)
     {
        if ((ev->type == ECORE_X_SHAPE_BOUNDING) && (ec->shaped || (pshaped != ec->shaped)))
          {
             if (ec->shape_rects || (!ec->shaped))
               e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
          }
     }
   EC_CHANGED(ec);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_damage(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Damage *ev)
{
   E_Client *ec;
   Ecore_X_Rectangle *rects = NULL;
   int n = 0;
   Eina_Bool skip;

   ec = _e_comp_x_client_find_by_damage(ev->damage);
   if ((!ec) || e_object_is_del(E_OBJECT(ec))) return ECORE_CALLBACK_PASS_ON;
   skip = ec->shape_rects_num > 50;
   if (_e_comp_x_client_data_get(ec)->damage)
     {
        Ecore_X_Region parts;
        Ecore_X_Rectangle bounds;

        parts = ecore_x_region_new(NULL, 0);
        ecore_x_damage_subtract(_e_comp_x_client_data_get(ec)->damage, 0, parts);
        if (!skip)
          rects = ecore_x_region_fetch(parts, &n, &bounds);
        ecore_x_region_free(parts);
     }
   //WRN("DAMAGE %p: %dx%d", ec, ev->area.width, ev->area.height);

   if (e_comp->nocomp)
     e_pixmap_dirty(ec->pixmap);
   else if (skip)
     e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
   else
     {
        int i;

        for (i = 0; i < n; i++)
          e_comp_object_damage(ec->frame, rects[i].x, rects[i].y, rects[i].width, rects[i].height);
     }
   free(rects);
   if ((!ec->re_manage) && (!ec->override) && (!_e_comp_x_client_data_get(ec)->first_damage))
     e_comp_object_render_update_del(ec->frame);
   else
     E_FREE_FUNC(_e_comp_x_client_data_get(ec)->first_draw_delay, ecore_timer_del);
   _e_comp_x_client_data_get(ec)->first_damage = 1;
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_damage_win(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Damage *ev)
{
   if (e_comp_find_by_window(ev->win)) e_comp_render_queue();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_grab_replay(void *data EINA_UNUSED, int type, void *event)
{
   Ecore_Event_Mouse_Button *ev = event;
   E_Binding_Event_Mouse_Button ev2;
   E_Client *ec;

   if (type != ECORE_EVENT_MOUSE_BUTTON_DOWN) return ECORE_CALLBACK_DONE;
   if ((!e_config->pass_click_on) &&
       (!e_config->always_click_to_raise) && // this works even if not on click-to-focus
       (!e_config->always_click_to_focus) // this works even if not on click-to-focus
       )
     return ECORE_CALLBACK_DONE;
   ec = _e_comp_x_client_find_by_window(ev->event_window);
   if (!ec) return ECORE_CALLBACK_DONE;
   if (ec->cur_mouse_action) return ECORE_CALLBACK_DONE;
   if (ev->event_window != e_client_util_win_get(ec)) return ECORE_CALLBACK_DONE;
   e_bindings_ecore_event_mouse_button_convert(ev, &ev2);
   return !e_bindings_mouse_button_find(E_BINDING_CONTEXT_WINDOW,
                                        &ev2, NULL);
}

static void
_e_comp_x_hook_client_eval_end(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   if (e_comp_x->restack && (!e_comp->new_clients))
     {
        e_hints_client_stacking_set();
        e_comp_x->restack = 0;
     }
}

static Eina_Bool
_e_comp_x_client_shape_rects_check(E_Client *ec, Ecore_X_Rectangle *rects, int num)
{
   Eina_Bool changed = 1;
   Ecore_X_Rectangle *orects;

   if (((unsigned int)num == ec->shape_rects_num) && (ec->shape_rects))
     {
        int i;

        orects = (Ecore_X_Rectangle*)ec->shape_rects;
        changed = 0;
        for (i = 0; i < num; i++)
          {
             E_RECTS_CLIP_TO_RECT(rects[i].x, rects[i].y,
               rects[i].width, rects[i].height, 0, 0, ec->client.w, ec->client.h);

             if ((orects[i].x != rects[i].x) ||
                 (orects[i].y != rects[i].y) ||
                 (orects[i].width != rects[i].width) ||
                 (orects[i].height != rects[i].height))
               {
                  changed = 1;
                  break;
               }
          }
     }
   if ((changed) && (rects))
     {
        E_FREE(ec->shape_rects);
        ec->shape_rects = (Eina_Rectangle*)rects;
        ec->shape_rects_num = num;
        ec->shape_changed = 1;
        e_comp_shape_queue();
     }
   else
     free(rects);
   return changed;
}

static void
_e_comp_x_hook_client_post_new_client(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   if (ec->changes.reset_gravity)
     {
        GRAV_SET(ec, ECORE_X_GRAVITY_NW);
     }

   if (ec->need_shape_merge)
     {
        _e_comp_x_client_shape_input_rectangle_set(ec);
        if ((!ec->shaped) && _e_comp_x_client_data_get(ec)->reparented)
          ecore_x_window_shape_mask_set(e_client_util_pwin_get(ec), 0);
        ec->need_shape_merge = 0;
     }

   if (ec->changes.internal_state)
     {
        Ecore_X_Atom state[1];
        int num = 0;

        if (e_win_centered_get(ec->internal_elm_win))
          state[num++] = E_ATOM_WINDOW_STATE_CENTERED;

        if (num)
          ecore_x_window_prop_card32_set(elm_win_window_id_get(ec->internal_elm_win), E_ATOM_WINDOW_STATE, state, num);
        else
          ecore_x_window_prop_property_del(elm_win_window_id_get(ec->internal_elm_win), E_ATOM_WINDOW_STATE);
        ec->changes.internal_state = 0;
     }

   if (ec->need_shape_export)
     {
        Ecore_X_Rectangle *rects;
        int num;

        rects = ecore_x_window_shape_rectangles_get(e_client_util_win_get(ec), &num);
        if (rects)
          _e_comp_x_client_shape_rects_check(ec, rects, num);
        else
          {
             E_FREE(ec->shape_rects);
             ec->shape_rects_num = 0;
          }
        ec->need_shape_export = 0;
     }
}

static void
_e_comp_x_frame_extents_adjust(E_Client *ec, int exl, int exr, int ext, int exb)
{
   unsigned int extentscurrent;
   unsigned int extentsall = exl | exr | ext | exb;
   int insl = 0, insr = 0, inst = 0, insb = 0;

   e_comp_object_frame_geometry_get(ec->frame, &insl, &insr, &inst, &insb);
   extentscurrent = insl | insr | inst | insb;

   extentsall = !!extentsall;
   extentscurrent = !!extentscurrent;
   if ((!e_comp_object_frame_exists(ec->frame)) &&
       (( extentsall &&  extentscurrent) ||
        (!extentsall &&  extentscurrent) ||
        ( extentsall && !extentscurrent)))
     {
        e_comp_object_frame_geometry_set(ec->frame, -exl, -exr, -ext, -exb);
        if (ec->override &&
            (ec->x == ec->comp_data->initial_attributes.x) &&
            (ec->y == ec->comp_data->initial_attributes.y))
          e_comp_object_frame_xy_adjust(ec->frame, ec->x, ec->y, &ec->x, &ec->y);
     }
}

static void
_e_comp_x_hook_client_pre_frame_assign(void *d EINA_UNUSED, E_Client *ec)
{
   Ecore_X_Window win, pwin;
   int w, h;
   E_Pixmap *ep;
   E_Comp_X_Client_Data *cd;

   E_COMP_X_PIXMAP_CHECK;
   ep = e_comp_x_client_pixmap_get(ec);
   win = e_client_util_win_get(ec);
   cd = _e_comp_x_client_data_get(ec);
   if (!cd->need_reparent) return;
   w = MAX(ec->client.w, 1);
   h = MAX(ec->client.h, 1);
   /* match ec parent argbness */
   if (ec->argb)
     pwin = ecore_x_window_manager_argb_new(e_comp->root, ec->client.x, ec->client.y, w, h);
   else
     {
        pwin = ecore_x_window_override_new(e_comp->root, ec->client.x, ec->client.y, w, h);
        ecore_x_window_shape_events_select(pwin, !ec->internal); //let's just agree never to do this with our own windows...
     }

   if (ec->client.w && ec->client.h)
     /* force a resize here (no-op most of the time)
      * prevents mismatch between remembered window size and current size
      */
     ecore_x_window_resize(win, ec->client.w, ec->client.h);
   ecore_x_window_container_manage(pwin);
   if (!ec->internal) ecore_x_window_client_manage(win);
   ecore_x_window_configure(pwin,
                            ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING |
                            ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE,
                            0, 0, 0, 0, 0,
                            win, ECORE_X_WINDOW_STACK_ABOVE);
   ecore_x_event_mask_set(pwin, ECORE_X_EVENT_MASK_KEY_DOWN | ECORE_X_EVENT_MASK_KEY_UP |
                                ECORE_X_EVENT_MASK_MOUSE_MOVE | ECORE_X_EVENT_MASK_MOUSE_DOWN |
                                ECORE_X_EVENT_MASK_MOUSE_UP |
                                ECORE_X_EVENT_MASK_MOUSE_IN | ECORE_X_EVENT_MASK_MOUSE_OUT);
   ecore_x_window_border_width_set(win, 0);
   ec->border_size = 0;

   if (ec->e.state.video)
     {
        ecore_x_window_lower(pwin);
        ecore_x_composite_window_events_disable(pwin);
        ecore_x_window_ignore_set(pwin, EINA_TRUE);
     }
   if (!ec->internal)
     ecore_x_window_save_set_add(win);
   ec->ignore_first_unmap++;
   ecore_x_window_reparent(win, pwin, 0, 0);
   e_pixmap_alias(ep, E_PIXMAP_TYPE_X, pwin);

   {
      unsigned int managed = 1;
      ecore_x_window_prop_card32_set(win, E_ATOM_MANAGED, &managed, 1);
   }
   e_pixmap_parent_window_set(ep, pwin);
   ec->border.changed = 1;
   if (!ec->shaped)
     ecore_x_window_shape_mask_set(pwin, 0);
   if (ec->shaped_input)
     ecore_x_window_shape_input_rectangles_set(pwin, (Ecore_X_Rectangle*)ec->shape_input_rects, ec->shape_input_rects_num);
   ec->changes.shape = 1;
   ec->changes.shape_input = 1;
   if (ec->internal_elm_win)
     {
        ecore_x_window_gravity_set(win, ECORE_X_GRAVITY_NW);
        ec->changes.reset_gravity = 1;
     }
   EC_CHANGED(ec);

   eina_hash_add(clients_win_hash, &pwin, ec);

   if (ec->visible)
     {
        if (cd->set_win_type && ec->internal_elm_win)
          {
             if (ec->dialog)
               ecore_x_netwm_window_type_set(win, ECORE_X_WINDOW_TYPE_DIALOG);
             else
               ecore_x_netwm_window_type_set(win, ECORE_X_WINDOW_TYPE_NORMAL);
             cd->set_win_type = 0;
          }
     }
   ecore_x_window_show(win);
   if (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
     ecore_x_window_show(pwin);

   _e_comp_x_focus_init(ec);
   e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, pwin);
   e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, pwin);
   _e_comp_x_client_evas_init(ec);
   if (ec->netwm.ping && (!ec->ping_poller))
     e_client_ping(ec);
   if (ec->visible && (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN))
     evas_object_show(ec->frame);
   cd->need_reparent = 0;
   ec->redirected = 1;
   if (cd->change_icon)
     {
        ec->changes.icon = 1;
        EC_CHANGED(ec);
     }
   cd->change_icon = 0;
   cd->reparented = 1;
   _e_comp_x_evas_comp_hidden_cb(ec, NULL, NULL);
}

static void
_e_comp_x_hook_client_fetch(void *d EINA_UNUSED, E_Client *ec)
{
   Ecore_X_Window win, pwin;
   int rem_change = 0;
   Eina_Bool need_desk_set = EINA_FALSE;
   E_Comp_X_Client_Data *cd;

   E_COMP_X_PIXMAP_CHECK;
   win = e_client_util_win_get(ec);
   pwin = e_client_util_pwin_get(ec);
   cd = _e_comp_x_client_data_get(ec);
   if (ec->changes.visible)
     _e_comp_x_client_shape_input_rectangle_set(ec);
   /* fetch any info queued to be fetched */
   if (ec->changes.prop || ec->netwm.fetch.state)
     {
        e_hints_window_state_get(ec);
        ec->netwm.fetch.state = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.client_leader)
     {
        /* TODO: What do to if the client leader isn't mapped yet? */
        E_Client *ec_leader = NULL;

        ec->icccm.client_leader = ecore_x_icccm_client_leader_get(win);
        if (ec->icccm.client_leader)
          ec_leader = _e_comp_x_client_find_by_window(ec->icccm.client_leader);
        if (ec->leader)
          {
             if (ec->leader != ec_leader)
               {
                  ec->leader->group = eina_list_remove(ec->leader->group, ec);
                  if (ec->leader->modal == ec) ec->leader->modal = NULL;
                  ec->leader = NULL;
               }
             else
               ec_leader = NULL;
          }
        /* If this client is the leader of the group, don't register itself */
        if ((ec_leader) && (ec_leader != ec))
          {
             ec_leader->group = eina_list_append(ec_leader->group, ec);
             ec->leader = ec_leader;
             /* Only set the window modal to the leader it there is no parent */
             if ((ec->netwm.state.modal) &&
                 ((!ec->parent) || (ec->parent->modal != ec)))
               {
                  ec->leader->modal = ec;
                  if (ec->leader->focused)
                    evas_object_focus_set(ec->frame, 1);
                  else
                    {
                       Eina_List *l;
                       E_Client *child;

                       EINA_LIST_FOREACH(ec->leader->group, l, child)
                         {
                            if ((child != ec) && (child->focused))
                              evas_object_focus_set(ec->frame, 1);
                         }
                    }
               }
          }
        ec->icccm.fetch.client_leader = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.title)
     {
        char *title = ecore_x_icccm_title_get(win);
        eina_stringshare_replace(&ec->icccm.title, title);
        free(title);

        e_comp_object_frame_title_set(ec->frame, ec->icccm.title);

        ec->icccm.fetch.title = 0;
        rem_change = 1;
     }
   if (ec->netwm.fetch.name)
     {
        char *name;
        ecore_x_netwm_name_get(win, &name);
        eina_stringshare_replace(&ec->netwm.name, name);
        free(name);

        ec->hacks.iconic_shading =
          ((ec->netwm.icon_name == ec->netwm.name) &&
           (!e_util_strcmp(ec->netwm.name, "QEMU")));

        e_comp_object_frame_title_set(ec->frame, ec->netwm.name);

        ec->netwm.fetch.name = 0;
        rem_change = 1;
     }

   if (ec->icccm.fetch.name_class)
     {
        const char *pname, *pclass;
        char *nname, *nclass;

        ecore_x_icccm_name_class_get(win, &nname, &nclass);
        pname = ec->icccm.name;
        pclass = ec->icccm.class;
        ec->icccm.name = eina_stringshare_add(nname);
        ec->icccm.class = eina_stringshare_add(nclass);
        ec->hacks.mapping_change =
          ((!e_util_strcasecmp(ec->icccm.class, "vmplayer")) ||
           (!e_util_strcasecmp(ec->icccm.class, "vmware")));
        if (ec->hacks.mapping_change)
          _e_comp_x_mapping_change_disabled++;
        _e_comp_x_mapping_change_disabled = MIN(_e_comp_x_mapping_change_disabled, 0);
        free(nname);
        free(nclass);

        if (!((ec->icccm.name == pname) &&
              (ec->icccm.class == pclass)))
          {
             ec->changes.icon = 1;
             rem_change = 1;
          }

        eina_stringshare_del(pname);
        eina_stringshare_del(pclass);
        ec->icccm.fetch.name_class = 0;
     }
   if (ec->changes.prop || ec->e.fetch.state)
     {
        e_hints_window_e_state_get(ec);
        ec->e.fetch.state = 0;
        rem_change = 1;
     }
   if (ec->e.fetch.stack)
     {
        cd->stack = ecore_x_e_stack_type_get(win);
        ec->e.state.stack = !!cd->stack;
        ec->e.fetch.stack = 0;
     }
   if (ec->e.fetch.profile)
     {
        const char **list = NULL;
        int n, i, res;
        unsigned int use;

        ec->e.state.profile.use = 0;

        if (ec->e.state.profile.name)
          {
             eina_stringshare_del(ec->e.state.profile.name);
             ec->e.state.profile.name = NULL;
          }

        if (ec->e.state.profile.available_list)
          {
             for (i = 0; i < ec->e.state.profile.num; i++)
               {
                  if (ec->e.state.profile.available_list[i])
                    {
                       eina_stringshare_del(ec->e.state.profile.available_list[i]);
                       ec->e.state.profile.available_list[i] = NULL;
                    }
               }
             E_FREE(ec->e.state.profile.available_list);
             ec->e.state.profile.available_list = NULL;
          }
        ec->e.state.profile.num = 0;

        res = ecore_x_window_prop_card32_get(win,
                                             ECORE_X_ATOM_E_WINDOW_PROFILE_SUPPORTED,
                                             &use,
                                             1);
        if ((res == 1) && (use == 1))
          {
             Ecore_X_Atom val;
             res = ecore_x_window_prop_atom_get(win,
                                                ECORE_X_ATOM_E_WINDOW_PROFILE_CHANGE,
                                                &val, 1);
             if (res == 1)
               {
                  char *name = ecore_x_atom_name_get(val);
                  if (name)
                    {
                       ec->e.state.profile.set = eina_stringshare_add(name);
                       free(name);
                    }
               }

             if (ecore_x_e_window_available_profiles_get(win, &list, &n))
               {
                  ec->e.state.profile.available_list = E_NEW(const char *, n);
                  for (i = 0; i < n; i++)
                    ec->e.state.profile.available_list[i] = eina_stringshare_add(list[i]);
                  ec->e.state.profile.num = n;
               }
             need_desk_set = EINA_TRUE;
             ec->e.state.profile.use = 1;
             free(list);
          }

        ec->e.fetch.profile = 0;
     }
   if (ec->changes.prop || ec->netwm.fetch.type)
     {
        unsigned int type = ec->netwm.type;
        e_hints_window_type_get(ec);
        if (((!ec->lock_border) || (!ec->border.name) || (type != ec->netwm.type)) && (cd->reparented || ec->internal))
          {
             ec->border.changed = 1;
             EC_CHANGED(ec);
          }

        if ((ec->netwm.type == E_WINDOW_TYPE_DOCK) || ec->tooltip)
          {
             if (!ec->netwm.state.skip_pager)
               {
                  ec->netwm.state.skip_pager = 1;
                  ec->netwm.update.state = 1;
               }
             if (!ec->netwm.state.skip_taskbar)
               {
                  ec->netwm.state.skip_taskbar = 1;
                  ec->netwm.update.state = 1;
               }
          }
        else if (ec->netwm.type == E_WINDOW_TYPE_DESKTOP)
          {
             ec->focus_policy_override = E_FOCUS_CLICK;
             _e_comp_x_focus_setdown(ec);
             _e_comp_x_focus_setup(ec);
             e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, pwin);
             e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, pwin);
             if (!ec->netwm.state.skip_pager)
               {
                  ec->netwm.state.skip_pager = 1;
                  ec->netwm.update.state = 1;
               }
             if (!ec->netwm.state.skip_taskbar)
               {
                  ec->netwm.state.skip_taskbar = 1;
                  ec->netwm.update.state = 1;
               }
             if (!e_client_util_ignored_get(ec))
               ec->border.changed = ec->borderless = 1;
          }
        if (ec->tooltip)
          {
             ec->icccm.accepts_focus = 0;
             eina_stringshare_replace(&ec->bordername, "borderless");
          }
        {
           E_Event_Client_Property *ev;

           ev = E_NEW(E_Event_Client_Property, 1);
           ev->ec = ec;
           REFD(ec, 1);
           e_object_ref(E_OBJECT(ec));
           ev->property = E_CLIENT_PROPERTY_NETWM_STATE;
           ecore_event_add(E_EVENT_CLIENT_PROPERTY, ev, _e_comp_x_client_event_free, NULL);
        }
        ec->netwm.fetch.type = 0;
     }
   if (ec->icccm.fetch.machine)
     {
        char *machine = ecore_x_icccm_client_machine_get(win);

        if ((!machine) && (ec->icccm.client_leader))
          machine = ecore_x_icccm_client_machine_get(ec->icccm.client_leader);

        eina_stringshare_replace(&ec->icccm.machine, machine);
        free(machine);

        ec->icccm.fetch.machine = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.command)
     {
        if ((ec->icccm.command.argc > 0) && (ec->icccm.command.argv))
          {
             int i;

             for (i = 0; i < ec->icccm.command.argc; i++)
               free(ec->icccm.command.argv[i]);
             free(ec->icccm.command.argv);
          }
        ec->icccm.command.argc = 0;
        ec->icccm.command.argv = NULL;
        ecore_x_icccm_command_get(win,
                                  &(ec->icccm.command.argc),
                                  &(ec->icccm.command.argv));
        if ((ec->icccm.client_leader) &&
            (!ec->icccm.command.argv))
          ecore_x_icccm_command_get(ec->icccm.client_leader,
                                    &(ec->icccm.command.argc),
                                    &(ec->icccm.command.argv));
        ec->icccm.fetch.command = 0;
        rem_change = 1;
     }
   if (ec->changes.prop || ec->icccm.fetch.hints)
     {
        Eina_Bool accepts_focus, is_urgent;
        Ecore_X_Window_State_Hint state = ec->icccm.state;

        accepts_focus = EINA_TRUE;
        is_urgent = EINA_FALSE;
        ec->icccm.state = ECORE_X_WINDOW_STATE_HINT_NORMAL;
        if (ecore_x_icccm_hints_get(win,
                                    &accepts_focus,
                                    &ec->icccm.state,
                                    &ec->icccm.icon_pixmap,
                                    &ec->icccm.icon_mask,
                                    (Ecore_X_Window*)&ec->icccm.icon_window,
                                    (Ecore_X_Window*)&ec->icccm.window_group,
                                    &is_urgent))
          {
             unsigned int was_here = 0;

             if (ecore_x_window_prop_card32_get(win,
                                                E_ATOM_E_WAS_HERE,
                                                &was_here, 1) != 1)
               was_here = 0;
             // if we managed this before, then use WM_STATE not initial state
             if (was_here) ec->icccm.state = ecore_x_icccm_state_get(win);
             if (ec->new_client)
               {
                  /* clients may unset iconic state when no wm is present */
                  if (ec->netwm.state.hidden && (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_NORMAL))
                    ec->icccm.initial_state = ECORE_X_WINDOW_STATE_HINT_ICONIC;
                  else
                    ec->icccm.initial_state = ec->icccm.state;
               }
             if (state != ec->icccm.state)
               {
                  if (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
                    ec->ignored = 1, ec->visible = 0;
                  else
                    {
                       ec->visible = 1;
                       ec->changes.visible = ec->new_client;
                       if ((!ec->new_client) &&
                         (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_NORMAL))
                         evas_object_show(ec->frame);
                    }
               }
             ec->icccm.accepts_focus = accepts_focus;
             ec->icccm.urgent = is_urgent;
             e_client_urgent_set(ec, is_urgent);

             /* If this is a new window, set the state as requested. */
             if ((ec->new_client) &&
                 (ec->icccm.initial_state == ECORE_X_WINDOW_STATE_HINT_ICONIC))
               e_client_iconify(ec);
          }
        ec->icccm.fetch.hints = 0;
        rem_change = 1;
     }
   if (ec->override) ec->placed = EINA_TRUE;
   if (ec->changes.prop || ec->icccm.fetch.size_pos_hints)
     {
        Eina_Bool request_pos;

        request_pos = EINA_FALSE;
        if (ecore_x_icccm_size_pos_hints_get(win,
                                             &request_pos,
                                             &ec->icccm.gravity,
                                             &ec->icccm.min_w,
                                             &ec->icccm.min_h,
                                             &ec->icccm.max_w,
                                             &ec->icccm.max_h,
                                             &ec->icccm.base_w,
                                             &ec->icccm.base_h,
                                             &ec->icccm.step_w,
                                             &ec->icccm.step_h,
                                             &ec->icccm.min_aspect,
                                             &ec->icccm.max_aspect))
          {
             ec->icccm.request_pos = request_pos;
             if (request_pos && (!ec->placed) && (!ec->re_manage))
               {
                  Ecore_X_Window_Attributes *att;
                  int bw;
                  int l, r, t, b;
                  int zx = 0, zy = 0, zw = 0, zh = 0;

                  if (ec->zone)
                    e_zone_useful_geometry_get(ec->zone, &zx, &zy, &zw, &zh);
                  e_comp_object_frame_geometry_get(ec->frame, &l, &r, &t, &b);
                  att = &cd->initial_attributes;
                  bw = att->border * 2;
                  switch (ec->icccm.gravity)
                    {
                     case ECORE_X_GRAVITY_N:
                       ec->x = (att->x - (bw / 2)) - (l / 2);
                       ec->y = att->y;
                       break;

                     case ECORE_X_GRAVITY_NE:
                       ec->x = (att->x - (bw)) - (l);
                       ec->y = att->y;
                       break;

                     case ECORE_X_GRAVITY_E:
                       ec->x = (att->x - (bw)) - (l);
                       ec->y = (att->y - (bw / 2)) - (t / 2);
                       break;

                     case ECORE_X_GRAVITY_SE:
                       ec->x = (att->x - (bw)) - (l);
                       ec->y = (att->y - (bw)) - (t);
                       break;

                     case ECORE_X_GRAVITY_S:
                       ec->x = (att->x - (bw / 2)) - (l / 2);
                       ec->y = (att->y - (bw)) - (t);
                       break;

                     case ECORE_X_GRAVITY_SW:
                       ec->x = att->x;
                       ec->y = (att->y - (bw)) - (t);
                       break;

                     case ECORE_X_GRAVITY_W:
                       ec->x = att->x;
                       ec->y = (att->y - (bw)) - (t);
                       break;

                     case ECORE_X_GRAVITY_CENTER:
                       ec->x = (att->x - (bw / 2)) - (l / 2);
                       ec->y = (att->y - (bw / 2)) - (t / 2);
                       break;

                     case ECORE_X_GRAVITY_NW:
                     default:
                       ec->x = att->x;
                       ec->y = att->y;
                    }

                  /*
                   * This ensures that windows that like to open with a x/y
                   * position smaller than returned by e_zone_useful_geometry_get()
                   * are moved to useful positions.
                   */
                  // ->
                  if (e_config->geometry_auto_move)
                    {
                       if (ec->x < zx)
                         ec->x = zx;

                       if (ec->y < zy)
                         ec->y = zy;

                       /* ensure we account for windows which already have client_inset;
                        * fixes lots of wine placement issues
                        */
                       if (ec->x - l >= zx)
                         ec->x -= l;
                       if (ec->y - t >= zy)
                         ec->y -= t;

                       if (ec->x + ec->w > zx + zw)
                         ec->x = zx + zw - ec->w;

                       if (ec->y + ec->h > zy + zh)
                         ec->y = zy + zh - ec->h;

                       // <--
                       if (e_comp_zone_xy_get(ec->x, ec->y))
                         {
                            if (!E_INSIDE(ec->x, ec->y, ec->zone->x, ec->zone->y, ec->zone->w, ec->zone->h))
                              {
                                 ec->x = E_CLAMP(ec->x, ec->zone->x, ec->zone->x + ec->zone->w);
                                 ec->y = E_CLAMP(ec->y, ec->zone->y, ec->zone->y + ec->zone->h);
                              }
                            /* some application failing to correctly center a window */
                            if (eina_list_count(e_comp->zones) > 1)
                              {
                                 if (abs((e_comp->w / 2) - ec->x - (ec->w / 2)) < 3)
                                   ec->x = ((ec->zone->x + ec->zone->w) / 2) - (ec->w / 2);
                                 if (abs((e_comp->h / 2) - ec->y - (ec->h / 2)) < 3)
                                   ec->y = ((ec->zone->y + ec->zone->h) / 2) - (ec->h / 2);
                              }
                            ec->changes.pos = 1;
                            ec->placed = 1;
                         }
                    }
                  else
                    {
                       ec->changes.pos = 1;
                       ec->placed = 1;
                    }
               }
             if (ec->placed && (!e_client_util_resizing_get(ec)) && (!ec->override))
               {
                  if (ec->fullscreen || ec->maximized)
                    e_client_rescale(ec);
                  else
                    {
                       int rw = ec->w, rh = ec->h;

                       e_client_resize_limit(ec, &rw, &rh);
                       evas_object_resize(ec->frame, rw, rh);
                    }
               }
          }
        if (ec->icccm.min_w > 32767) ec->icccm.min_w = 32767;
        if (ec->icccm.min_h > 32767) ec->icccm.min_h = 32767;
        if (ec->icccm.max_w > 32767) ec->icccm.max_w = 32767;
        if (ec->icccm.max_h > 32767) ec->icccm.max_h = 32767;
        if (ec->icccm.base_w > 32767) ec->icccm.base_w = 32767;
        if (ec->icccm.base_h > 32767) ec->icccm.base_h = 32767;
        //	if (ec->icccm.step_w < 1) ec->icccm.step_w = 1;
        //	if (ec->icccm.step_h < 1) ec->icccm.step_h = 1;
        // if doing a resize, fix it up
        if (e_client_util_resizing_get(ec))
          {
             int x, y, w, h, new_w, new_h;

             x = ec->x;
             y = ec->y;
             w = ec->w;
             h = ec->h;
             new_w = w;
             new_h = h;
             e_client_resize_limit(ec, &new_w, &new_h);
             if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
                 (ec->resize_mode == E_POINTER_RESIZE_L) ||
                 (ec->resize_mode == E_POINTER_RESIZE_BL))
               x += (w - new_w);
             if ((ec->resize_mode == E_POINTER_RESIZE_TL) ||
                 (ec->resize_mode == E_POINTER_RESIZE_T) ||
                 (ec->resize_mode == E_POINTER_RESIZE_TR))
               y += (h - new_h);
             evas_object_geometry_set(ec->frame, x, y, new_w, new_h);
          }
        ec->icccm.fetch.size_pos_hints = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.protocol)
     {
        int i, num;
        Ecore_X_WM_Protocol *proto;

        proto = ecore_x_window_prop_protocol_list_get(win, &num);
        if (proto)
          {
             for (i = 0; i < num; i++)
               {
                  if (proto[i] == ECORE_X_WM_PROTOCOL_DELETE_REQUEST)
                    ec->icccm.delete_request = 1;
                  else if (proto[i] == ECORE_X_WM_PROTOCOL_TAKE_FOCUS)
                    ec->icccm.take_focus = 1;
                  else if (proto[i] == ECORE_X_NET_WM_PROTOCOL_PING)
                    ec->netwm.ping = 1;
                  else if (proto[i] == ECORE_X_NET_WM_PROTOCOL_SYNC_REQUEST)
                    {
                       ec->netwm.sync.request = 1;
                       if (!ecore_x_netwm_sync_counter_get(win,
                                                           &cd->sync_counter))
                         ec->netwm.sync.request = 0;
                    }
               }
             free(proto);
          }
        if (ec->netwm.ping && cd->evas_init)
          e_client_ping(ec);
        else
          E_FREE_FUNC(ec->ping_poller, ecore_poller_del);
        ec->icccm.fetch.protocol = 0;
     }
   if (ec->icccm.fetch.transient_for)
     {
        /* TODO: What do to if the transient for isn't mapped yet? */
        E_Client *ec_parent = NULL;

        ec->icccm.transient_for = ecore_x_icccm_transient_for_get(win);
        if (ec->icccm.transient_for)
          ec_parent = _e_comp_x_client_find_by_window(ec->icccm.transient_for);

        e_client_parent_set(ec, ec_parent);
        if (ec->parent && (!e_client_util_ignored_get(ec)) && ec->netwm.state.modal)
          _e_comp_x_client_modal_setup(ec);
        if ((ec_parent) && (cd->stack != ECORE_X_STACK_NONE))
          {
             E_Client *ec2;

             // find last one
             for (ec2 = ec_parent; ec2->stack.next; ec2 = ec2->stack.next);
             ec->stack.prev = ec2;
             ec->stack.next = NULL;
             ec->stack.prev->stack.next = ec;
          }
        ec->icccm.fetch.transient_for = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.window_role)
     {
        char *role = ecore_x_icccm_window_role_get(win);
        eina_stringshare_replace(&ec->icccm.window_role, role);
        free(role);

        ec->icccm.fetch.window_role = 0;
        rem_change = 1;
     }
   if (ec->icccm.fetch.icon_name)
     {
        char *icon_name = ecore_x_icccm_icon_name_get(win);
        eina_stringshare_replace(&ec->icccm.icon_name, icon_name);
        free(icon_name);

        ec->hacks.iconic_shading =
          ((ec->netwm.icon_name == ec->netwm.name) &&
           (!e_util_strcmp(ec->netwm.icon_name, "QEMU")));

        ec->icccm.fetch.icon_name = 0;
        rem_change = 1;
     }
   if (ec->netwm.fetch.icon_name)
     {
        char *icon_name;
        ecore_x_netwm_icon_name_get(win, &icon_name);
        eina_stringshare_replace(&ec->netwm.icon_name, icon_name);
        free(icon_name);

        ec->netwm.fetch.icon_name = 0;
        rem_change = 1;
     }
   if (ec->netwm.fetch.opacity)
     {
        unsigned int val;

        if (ecore_x_netwm_opacity_get(win, &val))
          {
             val >>= 24;
             if (ec->netwm.opacity != val)
               {
                  ec->netwm.opacity = val;
                  evas_object_color_set(ec->frame, val, val, val, val);
                  rem_change = 1;
               }
             ec->netwm.fetch.opacity = !ec->netwm.opacity;
          }
        else
          ec->netwm.fetch.opacity = 0;
     }
   if (ec->netwm.fetch.icon)
     {
        _e_comp_x_client_icon_free(ec->netwm.icons, ec->netwm.num_icons);
        ec->netwm.icons = NULL;
        ec->netwm.num_icons = 0;
        if (ecore_x_netwm_icons_get(win,
                                    &ec->netwm.icons,
                                    &ec->netwm.num_icons))
          {
             if (ec->netwm.icons)
               ec->netwm.icons = _e_comp_x_client_icon_deduplicate
                 (ec->netwm.icons, ec->netwm.num_icons);
             ec->changes.icon = 1;
          }
        ec->netwm.fetch.icon = 0;
     }
   if (ec->netwm.fetch.user_time)
     {
        ecore_x_netwm_user_time_get(win, &ec->netwm.user_time);
        ec->netwm.fetch.user_time = 0;
     }
   if (ec->netwm.fetch.strut)
     {
        if (!ecore_x_netwm_strut_partial_get(win,
                                             &ec->netwm.strut.left,
                                             &ec->netwm.strut.right,
                                             &ec->netwm.strut.top,
                                             &ec->netwm.strut.bottom,
                                             &ec->netwm.strut.left_start_y,
                                             &ec->netwm.strut.left_end_y,
                                             &ec->netwm.strut.right_start_y,
                                             &ec->netwm.strut.right_end_y,
                                             &ec->netwm.strut.top_start_x,
                                             &ec->netwm.strut.top_end_x,
                                             &ec->netwm.strut.bottom_start_x,
                                             &ec->netwm.strut.bottom_end_x))
          {
             ecore_x_netwm_strut_get(win,
                                     &ec->netwm.strut.left, &ec->netwm.strut.right,
                                     &ec->netwm.strut.top, &ec->netwm.strut.bottom);

             ec->netwm.strut.left_start_y = 0;
             ec->netwm.strut.left_end_y = 0;
             ec->netwm.strut.right_start_y = 0;
             ec->netwm.strut.right_end_y = 0;
             ec->netwm.strut.top_start_x = 0;
             ec->netwm.strut.top_end_x = 0;
             ec->netwm.strut.bottom_start_x = 0;
             ec->netwm.strut.bottom_end_x = 0;
          }
        ec->netwm.fetch.strut = 0;
     }
   if (ec->qtopia.fetch.soft_menu)
     {
        e_hints_window_qtopia_soft_menu_get(ec);
        ec->qtopia.fetch.soft_menu = 0;
        rem_change = 1;
     }
   if (ec->qtopia.fetch.soft_menus)
     {
        e_hints_window_qtopia_soft_menus_get(ec);
        ec->qtopia.fetch.soft_menus = 0;
        rem_change = 1;
     }
   if (ec->vkbd.fetch.state)
     {
        e_hints_window_virtual_keyboard_state_get(ec);
        ec->vkbd.fetch.state = 0;
        rem_change = 1;
     }
   if (ec->vkbd.fetch.vkbd)
     {
        e_hints_window_virtual_keyboard_get(ec);
        ec->vkbd.fetch.vkbd = 0;
        rem_change = 1;
     }
   if (cd->illume.conformant.fetch.conformant)
     {
        cd->illume.conformant.conformant =
          ecore_x_e_illume_conformant_get(win);
        cd->illume.conformant.fetch.conformant = 0;
     }
   if (cd->illume.quickpanel.fetch.state)
     {
        cd->illume.quickpanel.state =
          ecore_x_e_illume_quickpanel_state_get(win);
        cd->illume.quickpanel.fetch.state = 0;
     }
   if (cd->illume.quickpanel.fetch.quickpanel)
     {
        cd->illume.quickpanel.quickpanel =
          ecore_x_e_illume_quickpanel_get(win);
        cd->illume.quickpanel.fetch.quickpanel = 0;
     }
   if (cd->illume.quickpanel.fetch.priority.major)
     {
        cd->illume.quickpanel.priority.major =
          ecore_x_e_illume_quickpanel_priority_major_get(win);
        cd->illume.quickpanel.fetch.priority.major = 0;
     }
   if (cd->illume.quickpanel.fetch.priority.minor)
     {
        cd->illume.quickpanel.priority.minor =
          ecore_x_e_illume_quickpanel_priority_minor_get(win);
        cd->illume.quickpanel.fetch.priority.minor = 0;
     }
   if (cd->illume.quickpanel.fetch.zone)
     {
        cd->illume.quickpanel.zone =
          ecore_x_e_illume_quickpanel_zone_get(win);
        cd->illume.quickpanel.fetch.zone = 0;
     }
   if (cd->illume.drag.fetch.drag)
     {
        cd->illume.drag.drag =
          ecore_x_e_illume_drag_get(win);
        cd->illume.drag.fetch.drag = 0;
     }
   if (cd->illume.drag.fetch.locked)
     {
        cd->illume.drag.locked =
          ecore_x_e_illume_drag_locked_get(win);
        cd->illume.drag.fetch.locked = 0;
     }
   if (cd->illume.win_state.fetch.state)
     {
        cd->illume.win_state.state =
          ecore_x_e_illume_window_state_get(win);
        cd->illume.win_state.fetch.state = 0;
     }
   if (ec->changes.shape)
     {
        Ecore_X_Rectangle *rects;
        int num;
        Eina_Bool pshaped = ec->shaped;

        ec->changes.shape = 0;
        rects = ecore_x_window_shape_rectangles_get(win, &num);
        if (rects && num)
          {
             int cw = 0, ch = 0;

             /* This doesn't fix the race, but makes it smaller. we detect
              * this and if cw and ch != client w/h then mark this as needing
              * a shape change again to fixup next event loop.
              */
             ecore_x_window_size_get(win, &cw, &ch);
             /* normalize for client window dead zone */
             if (ec->border_size && (num == 1))
               {
                  rects[0].x += ec->border_size;
                  rects[0].y += ec->border_size;
                  rects[0].width -= ec->border_size;
                  rects[0].height -= ec->border_size;
               }
             if ((cw != ec->client.w) || (ch != ec->client.h))
               {
                  ec->changes.shape = 1;
                  EC_CHANGED(ec);
               }
             if ((num == 1) &&
                 (rects[0].x == 0) &&
                 (rects[0].y == 0) &&
                 ((int)rects[0].width == cw) &&
                 ((int)rects[0].height == ch))
               {
                  if (ec->shaped)
                    {
                       ec->shaped = 0;
                       if (cd->reparented && (!ec->bordername))
                         {
                            ec->border.changed = 1;
                            EC_CHANGED(ec);
                         }
                    }
               }
             else
               {
                  if (cd->reparented)
                    {
                       ecore_x_window_shape_rectangles_set(pwin, rects, num);
                       if ((!ec->shaped) && (!ec->bordername))
                         {
                            ec->border.changed = 1;
                            EC_CHANGED(ec);
                         }
                    }
                  else
                    {
                       if (_e_comp_x_client_shape_rects_check(ec, rects, num))
                         e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
                    }
                  ec->shaped = 1;
                  ec->changes.shape_input = 0;
                  E_FREE(ec->shape_input_rects);
                  ec->shape_input_rects_num = 0;
               }
             if (cd->reparented || (!ec->shaped))
               free(rects);
             if (ec->shape_changed)
               e_comp_object_frame_theme_set(ec->frame, E_COMP_OBJECT_FRAME_RESHADOW);
             evas_object_pass_events_set(ec->frame, ec->netwm.type == E_WINDOW_TYPE_DND);
          }
        else
          {
             ec->shaped = 1;
             E_FREE(ec->shape_rects);
             ec->shape_rects_num = 0;
             E_FREE(ec->shape_input_rects);
             ec->shape_input_rects_num = 0;
             e_comp_object_frame_theme_set(ec->frame, E_COMP_OBJECT_FRAME_RESHADOW);
             if (ec->shaped && cd->reparented && (!ec->bordername))
               {
                  ec->border.changed = 1;
                  EC_CHANGED(ec);
               }
             evas_object_pass_events_set(ec->frame, 1);
          }
        if (ec->shaped != pshaped)
          {
             _e_comp_x_client_shape_input_rectangle_set(ec);
             if ((!ec->shaped) && cd->reparented)
               ecore_x_window_shape_mask_set(pwin, 0);
          }
        ec->need_shape_merge = 1;
     }
   if (ec->changes.shape_input)
     {
        Ecore_X_Rectangle *rects;
        Eina_Bool pshaped = ec->shaped_input, changed = EINA_FALSE;
        int num;

        ec->changes.shape_input = 0;
        rects = ecore_x_window_shape_input_rectangles_get(win, &num);
        if (rects && num)
          {
             int cw = 0, ch = 0;

             /* This doesn't fix the race, but makes it smaller. we detect
              * this and if cw and ch != client w/h then mark this as needing
              * a shape change again to fixup next event loop.
              */
             ecore_x_window_size_get(win, &cw, &ch);
             /* normalize for client window dead zone */
             if (ec->border_size && (num == 1))
               {
                  rects[0].x += ec->border_size;
                  rects[0].y += ec->border_size;
                  rects[0].width -= ec->border_size;
                  rects[0].height -= ec->border_size;
               }
             if ((cw != ec->client.w) || (ch != ec->client.h))
               ec->changes.shape_input = 1;
             if (num == 1)
               E_RECTS_CLIP_TO_RECT(rects[0].x, rects[0].y, rects[0].width, rects[0].height, 0, 0, cw, ch);
             if ((num == 1) &&
                 (rects[0].x == 0) &&
                 (rects[0].y == 0) &&
                 ((int)rects[0].width == cw) &&
                 ((int)rects[0].height == ch))
               {
                  ec->shaped_input = 0;
                  free(rects);
               }
             else
               {
                  ec->shaped_input = 1;
                  if (cd->reparented)
                    ecore_x_window_shape_input_rectangles_set(pwin, rects, num);
                  changed = EINA_TRUE;
                  free(ec->shape_input_rects);
                  ec->shape_input_rects = (Eina_Rectangle*)rects;
                  ec->shape_input_rects_num = num;
               }
             evas_object_pass_events_set(ec->frame, ec->netwm.type == E_WINDOW_TYPE_DND);
          }
        else
          {
             ec->shaped_input = 1;
             if (cd->reparented)
               ecore_x_window_shape_input_rectangles_set(pwin, rects, num);
             changed = EINA_TRUE;
             evas_object_pass_events_set(ec->frame, 1);
             free(ec->shape_input_rects);
             ec->shape_input_rects = NULL;
             ec->shape_input_rects_num = 0;
          }
        if (changed || (pshaped != ec->shaped_input))
          {
             ec->need_shape_merge = 1;
             e_comp_shape_queue();
          }
     }
   if (ec->changes.prop || ec->mwm.fetch.hints)
     {
        int pb;

        ec->mwm.exists =
          ecore_x_mwm_hints_get(win,
                                &ec->mwm.func,
                                &ec->mwm.decor,
                                &ec->mwm.input);
        pb = ec->mwm.borderless;
        ec->mwm.borderless = 0;
        if (ec->mwm.exists)
          {
             if ((!(ec->mwm.decor & ECORE_X_MWM_HINT_DECOR_ALL)) &&
                 (!(ec->mwm.decor & ECORE_X_MWM_HINT_DECOR_TITLE)) &&
                 (!(ec->mwm.decor & ECORE_X_MWM_HINT_DECOR_BORDER)))
               ec->mwm.borderless = 1;
          }
        if (ec->mwm.borderless != pb)
          {
             if ((ec->internal || cd->reparented) && ((!ec->lock_border) || (!ec->border.name)))
               {
                  ec->border.changed = 1;
                  EC_CHANGED(ec);
               }
          }
        ec->mwm.fetch.hints = 0;
        rem_change = 1;
     }
   if (ec->e.fetch.video_parent)
     {
        /* unlinking child/parent */
        if (ec->e.state.video_parent_client != NULL)
          {
             ec->e.state.video_parent_client->e.state.video_child =
               eina_list_remove(ec->e.state.video_parent_client->e.state.video_child, ec);
          }

        ecore_x_window_prop_card32_get(win,
                                       ECORE_X_ATOM_E_VIDEO_PARENT,
                                       (Ecore_X_Window*)&ec->e.state.video_parent, 1);

        /* linking child/parent */
        if (ec->e.state.video_parent != 0)
          {
             E_Client *tmp;

             tmp = _e_comp_x_client_find_by_window(ec->e.state.video_parent);
             if (tmp)
               {
                  /* fprintf(stderr, "child added to parent \\o/\n"); */
                  ec->e.state.video_parent_client = tmp;
                  tmp->e.state.video_child = eina_list_append(tmp->e.state.video_child, ec);
                  if (ec->desk != tmp->desk)
                    e_client_desk_set(ec, tmp->desk);
               }
          }

        /* fprintf(stderr, "new parent %x => %p\n", ec->e.state.video_parent, ec->e.state.video_parent_client); */

        if (ec->e.state.video_parent_client) ec->e.fetch.video_parent = 0;
        rem_change = 1;
     }
   if (ec->e.fetch.video_position && ec->e.fetch.video_parent == 0)
     {
        unsigned int xy[2];

        ecore_x_window_prop_card32_get(win,
                                       ECORE_X_ATOM_E_VIDEO_POSITION,
                                       xy, 2);
        ec->e.state.video_position.x = xy[0];
        ec->e.state.video_position.y = xy[1];
        ec->e.state.video_position.updated = 1;
        ec->e.fetch.video_position = 0;
        ec->x = ec->e.state.video_position.x;
        ec->y = ec->e.state.video_position.y;

        fprintf(stderr, "internal position has been updated [%i, %i]\n", ec->e.state.video_position.x, ec->e.state.video_position.y);
     }
   if (ec->changes.prop || ec->netwm.update.state)
     {
        e_hints_window_state_set(ec);
        /* Some stats might change the border, like modal */
        if (((!ec->lock_border) || (!ec->border.name)) &&
            (!(((ec->maximized & E_MAXIMIZE_TYPE) == E_MAXIMIZE_FULLSCREEN))) &&
            (ec->internal || cd->reparented))
          {
             ec->border.changed = 1;
             EC_CHANGED(ec);
          }
        if (ec->parent)
          {
             if (ec->netwm.state.modal)
               {
                  ec->parent->modal = ec;
                  if (ec->parent->focused)
                    evas_object_focus_set(ec->frame, 1);
               }
          }
        else if (ec->leader)
          {
             if (ec->netwm.state.modal)
               {
                  ec->leader->modal = ec;
                  if (ec->leader->focused)
                    evas_object_focus_set(ec->frame, 1);
                  else
                    {
                       Eina_List *l;
                       E_Client *child;

                       EINA_LIST_FOREACH(ec->leader->group, l, child)
                         {
                            if ((child != ec) && (child->focused))
                              evas_object_focus_set(ec->frame, 1);
                         }
                    }
               }
          }
        ec->netwm.update.state = 0;
     }

   if (cd->fetch_exe)
     {
        E_Exec_Instance *inst;

        if (((!ec->lock_border) || (!ec->border.name)) &&
            (ec->internal || cd->reparented))
          {
             ec->border.changed = 1;
             EC_CHANGED(ec);
          }

        {
           char *str = NULL;

           if ((!ec->internal) &&
               ((ecore_x_netwm_startup_id_get(win, &str) && (str)) ||
               ((ec->icccm.client_leader > 0) &&
                ecore_x_netwm_startup_id_get(ec->icccm.client_leader, &str) && (str)))
               )
             {
                if (!strncmp(str, "E_START|", 8))
                  {
                     int id;

                     id = atoi(str + 8);
                     if (id > 0) ec->netwm.startup_id = id;
                  }
                free(str);
             }
        }
        /* It's ok not to have fetch flag, should only be set on startup
         * and not changed. */
        if (!ecore_x_netwm_pid_get(win, &ec->netwm.pid))
          {
             if (ec->icccm.client_leader)
               {
                  if (!ecore_x_netwm_pid_get(ec->icccm.client_leader, &ec->netwm.pid))
                    ec->netwm.pid = -1;
               }
             else
               ec->netwm.pid = -1;
          }

        if (!e_client_util_ignored_get(ec))
          {
             inst = e_exec_startup_id_pid_instance_find(ec->netwm.startup_id,
                                                        ec->netwm.pid);
             if (inst)
               {
                  Eina_Bool found;

                  if (inst->used == 0)
                    {
                       inst->used++;
                       if ((!ec->remember) || !(ec->remember->apply & E_REMEMBER_APPLY_DESKTOP))
                         {
                            E_Zone *zone;
                            E_Desk *desk;

                            zone = e_comp_zone_number_get(inst->screen);
                            if (zone) e_client_zone_set(ec, zone);
                            desk = e_desk_at_xy_get(ec->zone, inst->desk_x,
                                                    inst->desk_y);
                            if (desk) e_client_desk_set(ec, desk);
                         }
                       if (ec->netwm.pid != ecore_exe_pid_get(inst->exe))
                         {
                            /* most likely what has happened here is that the .desktop launcher
                             * has spawned a process which then created this border, meaning the
                             * E_Exec instance will be deleted in a moment, and we will be unable to track it.
                             * to prevent this, we convert our instance to a phony
                             */
                             inst->phony = 1;
                         }
                    }
                  found = !!inst->clients;
                  e_exec_instance_client_add(inst, ec);
                  if (!found)
                    e_exec_instance_found(inst);
               }

             if (e_config->window_grouping) // FIXME: We may want to make the border "urgent" so that the user knows it appeared.
               {
                  E_Client *ecl = NULL;

                  ecl = ec->parent;
                  if (!ecl)
                    ecl = ec->leader;
                  if (!ecl)
                    {
                       E_Client *child;

                       E_CLIENT_FOREACH(child)
                         {
                            if (child == ec) continue;
                            if (e_object_is_del(E_OBJECT(child))) continue;
                            if ((ec->icccm.client_leader) &&
                                (child->icccm.client_leader ==
                                 ec->icccm.client_leader))
                              {
                                 ecl = child;
                                 break;
                              }
                         }
                    }
                  if (ecl)
                    {
                       if (ecl->zone)
                         e_client_zone_set(ec, ecl->zone);
                       if (ecl->desk)
                         e_client_desk_set(ec, ecl->desk);
                       else
                         e_client_stick(ec);
                    }
               }
          }
        cd->fetch_exe = 0;
     }

   if (need_desk_set)
     {
        E_Desk *desk = NULL;
        const char *p, *p2;
        Eina_Bool set = EINA_FALSE;
        int i;

        /* set desktop using given initial profile */
        p = ec->e.state.profile.set;
        if (p)
          {
             if (!e_util_strcmp(p, ec->desk->window_profile))
               {
                  e_client_desk_window_profile_wait_desk_set(ec, ec->desk);
                  set = EINA_TRUE;
               }
             else
               {
                  desk = e_comp_desk_window_profile_get(p);
                  if (desk)
                    {
                       e_client_desk_set(ec, desk);
                       set = EINA_TRUE;
                    }
               }
          }

        if (!set)
          {
             /* try to find desktop using available profile list */
             for (i = 0; i < ec->e.state.profile.num; i++)
               {
                  p2 = ec->e.state.profile.available_list[i];
                  if (!e_util_strcmp(p2, ec->desk->window_profile))
                    {
                       eina_stringshare_replace(&ec->e.state.profile.set, p2);
                       e_client_desk_window_profile_wait_desk_set(ec, ec->desk);
                       set = EINA_TRUE;
                       break;
                    }
               }
             if (!set)
               {
                  for (i = 0; i < ec->e.state.profile.num; i++)
                    {
                       p2 = ec->e.state.profile.available_list[i];
                       desk = e_comp_desk_window_profile_get(p2);
                       if (desk)
                         {
                            e_client_desk_set(ec, desk);
                            set = EINA_TRUE;
                            break;
                         }
                    }
               }
          }

        if ((!set) && ec->desk)
          {
             eina_stringshare_replace(&ec->e.state.profile.set, ec->desk->window_profile);
             e_client_desk_window_profile_wait_desk_set(ec, ec->desk);
          }
     }

   if (ec->e.state.profile.set)
     {
        printf("E: sent profile set req to 0x%x [%s]\n", win, ec->e.state.profile.set);
        ecore_x_e_window_profile_change_request_send(win,
                                                     ec->e.state.profile.set);
        eina_stringshare_replace(&ec->e.state.profile.wait, ec->e.state.profile.set);
        ec->e.state.profile.wait_for_done = 1;
        eina_stringshare_replace(&ec->e.state.profile.set, NULL);
     }
   if (cd->fetch_gtk_frame_extents)
     {
        unsigned int *extents;
        int count;

        if (ecore_x_window_prop_property_get(win,
                                             ATM_GTK_FRAME_EXTENTS,
                                             ECORE_X_ATOM_CARDINAL, 32,
                                             (void *)(&extents), &count))
          {
             /* _GTK_FRAME_EXTENTS describes a region l/r/t/b pixels
              * from the "window" object in which shadows will be drawn.
              * this area should not be accounted for in sizing or
              * placement calculations.
              */
             if (count >= 4)
               {
                  printf("GTK-FRM: get %i %i %i %i\n",
                         extents[0], extents[1], extents[2], extents[3]);
                  _e_comp_x_frame_extents_adjust
                    (ec, extents[0], extents[1], extents[2], extents[3]);
               }
             free(extents);
          }
        else
          {
             printf("GTK-FRM: get fail\n");
             _e_comp_x_frame_extents_adjust(ec, 0, 0, 0, 0);
          }
        cd->fetch_gtk_frame_extents = 0;
     }
   ec->changes.prop = 0;
   if (rem_change) e_remember_update(ec);
   if ((!cd->reparented) && (!ec->internal)) ec->changes.border = 0;
   if (ec->changes.icon)
     {
        /* don't create an icon until we need it */
        if (cd->reparented) return;
        cd->change_icon = 1;
        ec->changes.icon = 0;
     }
}

static Eina_Bool
_e_comp_x_first_draw_delay_cb(void *data)
{
   E_Client *ec = data;

   _e_comp_x_client_data_get(ec)->first_draw_delay = NULL;
   if (ec->shape_rects || (!ec->shaped))
     e_comp_object_damage(ec->frame, 0, 0, ec->w, ec->h);
   return EINA_FALSE;
}

static void
_e_comp_x_hook_client_new(void *d EINA_UNUSED, E_Client *ec)
{
   Ecore_X_Window win;
   Eina_List *pending;
   Pending_Configure *pc;

   E_COMP_X_PIXMAP_CHECK;
   win = e_pixmap_window_get(ec->pixmap);
   ec->ignored = e_comp_ignore_win_find(win);

   ec->comp_data = E_NEW(E_Comp_Client_Data, 1);
   ec->comp_data->set_win_type = ec->comp_data->fetch_exe = 1;

   /* FIXME: ewww - round trip */
   ec->argb = ecore_x_window_argb_get(win);
   ec->changes.shape = 1;
   ec->changes.shape_input = 1;

   ec->netwm.type = E_WINDOW_TYPE_UNKNOWN;
   ec->icccm.state = ec->icccm.initial_state = ECORE_X_WINDOW_STATE_HINT_NONE;

   if (!_e_comp_x_client_new_helper(ec)) return;
   ec->ignored |= e_comp->comp_type == E_PIXMAP_TYPE_WL;
   pending = eina_hash_set(pending_configures, &win, NULL);
   if (pending)
     {
        Eina_Bool request_pos = EINA_FALSE;

        /* UGLY: round trip, but necessary to work around bad clients
         * positioning windows anyway AND libreoffice trying to hack
         * getting its windows across multiple screens this way
         * which isn't really right either... */
        ecore_x_icccm_size_pos_hints_get(win,
                                         &request_pos,
                                         &ec->icccm.gravity,
                                         &ec->icccm.min_w,
                                         &ec->icccm.min_h,
                                         &ec->icccm.max_w,
                                         &ec->icccm.max_h,
                                         &ec->icccm.base_w,
                                         &ec->icccm.base_h,
                                         &ec->icccm.step_w,
                                         &ec->icccm.step_h,
                                         &ec->icccm.min_aspect,
                                         &ec->icccm.max_aspect);
        if (request_pos)
          {
             Eina_List *l;
             E_Zone *zone;
             pc = eina_list_last_data_get(pending);
             EINA_LIST_FOREACH(e_comp->zones, l, zone)
               {
                  if (E_INTERSECTS(pc->point.x, pc->point.y, ec->w, ec->h,
                                   zone->x, zone->y, zone->w, zone->h))
                    {
                       e_client_zone_set(ec, zone);
                       break;
                    }
               }
          }
     }
   EINA_LIST_FREE(pending, pc)
     {
        ecore_timer_del(pc->timer);
        free(pc);
     }

   ec->comp_data->first_damage = ec->internal;

   eina_hash_add(clients_win_hash, &win, ec);
   if (!ec->input_only)
     ec->comp_data->first_draw_delay = ecore_timer_loop_add(e_comp_config_get()->first_draw_delay, _e_comp_x_first_draw_delay_cb, ec);
}

static void
_e_comp_x_hook_client_focus_unset_job(void *d EINA_UNUSED)
{
   E_Client *ec = unfocus_job_client;
   unfocus_job = NULL;
   if (!unfocus_job_client) return;
   unfocus_job_client = NULL;
   E_COMP_X_PIXMAP_CHECK;
   _e_comp_x_focus_setup(ec);
   _e_comp_x_focus_check();
}

static void
_e_comp_x_hook_client_focus_unset(void *d EINA_UNUSED, E_Client *ec)
{
   if (focus_job_client == ec)
     {
        focus_job_client = NULL;
        E_FREE_FUNC(focus_job, ecore_job_del);
     }
   if (ec->override) return;
   unfocus_job_client = ec;
   if (!unfocus_job)
     unfocus_job = ecore_job_add(_e_comp_x_hook_client_focus_unset_job, NULL);
}

static void
_e_comp_x_hook_client_focus_set_job(void *d EINA_UNUSED)
{
   E_Client *ec = focus_job_client;
   focus_job = NULL;
   if (!focus_job_client) return;
   focus_job_client = NULL;
   focus_time = ecore_x_current_time_get();
   focus_canvas_time = 0;
   if (!e_client_has_xwindow(ec))
     {
        e_grabinput_focus(e_comp->root, E_FOCUS_METHOD_PASSIVE);
        return;
     }
   _e_comp_x_focus_setdown(ec);

   if ((ec->icccm.take_focus) && (ec->icccm.accepts_focus))
     {
        e_grabinput_focus(e_client_util_win_get(ec), E_FOCUS_METHOD_LOCALLY_ACTIVE);
        /* TODO what if the client didn't take focus ? */
     }
   else if (!ec->icccm.accepts_focus)
     {
        e_grabinput_focus(e_client_util_win_get(ec), E_FOCUS_METHOD_GLOBALLY_ACTIVE);
     }
   else if (!ec->icccm.take_focus)
     {
        e_grabinput_focus(e_client_util_win_get(ec), E_FOCUS_METHOD_PASSIVE);
     }
}

static void
_e_comp_x_hook_client_focus_set(void *d EINA_UNUSED, E_Client *ec)
{
   if (unfocus_job_client == ec)
     {
        unfocus_job_client = NULL;
        E_FREE_FUNC(unfocus_job, ecore_job_del);
     }
   if (ec->override) return;
   focus_job_client = ec;
   if (!focus_job)
     focus_job = ecore_job_add(_e_comp_x_hook_client_focus_set_job, NULL);
}

static void
_e_comp_x_hook_client_redirect(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   if (_e_comp_x_client_data_get(ec)->unredirected_single)
     {
        ecore_x_composite_redirect_window(_e_comp_x_client_window_get(ec), ECORE_X_COMPOSITE_UPDATE_MANUAL);
        _e_comp_x_client_data_get(ec)->unredirected_single = 0;
     }
   else if (e_comp->nocomp)
     {
        /* first window */
        e_comp_x_nocomp_end();
        ecore_x_window_reparent(_e_comp_x_client_window_get(ec), e_comp->root, ec->client.x, ec->client.y);
        _e_comp_x_client_stack(ec);
     }
   if (!_e_comp_x_client_data_get(ec)->damage)
     _e_comp_x_client_damage_add(ec);
}

static void
_e_comp_x_hook_client_unredirect(void *d EINA_UNUSED, E_Client *ec)
{
   Ecore_X_Region parts;

   E_COMP_X_PIXMAP_CHECK;
   eina_hash_del(damages_hash, &_e_comp_x_client_data_get(ec)->damage, ec);
   parts = ecore_x_region_new(NULL, 0);
   ecore_x_damage_subtract(_e_comp_x_client_data_get(ec)->damage, 0, parts);
   ecore_x_region_free(parts);
   ecore_x_damage_free(_e_comp_x_client_data_get(ec)->damage);
   _e_comp_x_client_data_get(ec)->damage = 0;

   if (ec->unredirected_single && (!_e_comp_x_client_data_get(ec)->unredirected_single))
     {
        ecore_x_composite_unredirect_window(_e_comp_x_client_window_get(ec), ECORE_X_COMPOSITE_UPDATE_MANUAL);
        ecore_x_window_reparent(_e_comp_x_client_window_get(ec), e_comp->win, ec->client.x, ec->client.y);
        ecore_x_window_raise(_e_comp_x_client_window_get(ec));
        _e_comp_x_client_data_get(ec)->unredirected_single = 1;
     }
   if (!e_comp->nocomp) return; //wait for it...
   ecore_x_composite_unredirect_subwindows(e_comp->root, ECORE_X_COMPOSITE_UPDATE_MANUAL);
   ecore_x_window_hide(e_comp->win);
}

static Eina_Bool
_e_comp_x_dead_win_timer(void *d)
{
   uint32_t pwin = (uintptr_t)(uintptr_t*)d;

   eina_hash_del_by_key(dead_wins, &pwin);
   return EINA_FALSE;
}

static void
_e_comp_x_hook_client_del(void *d EINA_UNUSED, E_Client *ec)
{
   Ecore_X_Window win, pwin;
   unsigned int visible = 0;
   E_Comp_X_Client_Data *cd;

   E_COMP_X_PIXMAP_CHECK;
   win = e_client_util_win_get(ec);
   pwin = e_client_util_pwin_get(ec);
   cd = _e_comp_x_client_data_get(ec);

   if (mouse_client == ec) mouse_client = NULL;
   if (focus_job_client == ec) focus_job_client = NULL;
   if (unfocus_job_client == ec) unfocus_job_client = NULL;
   if ((!stopping) && cd && (!cd->deleted))
     ecore_x_window_prop_card32_set(win, E_ATOM_MANAGED, &visible, 1);
   if (stopping && ec->iconic)
     {
        e_hints_window_iconic_set(ec);
        e_hints_window_state_set(ec);
     }
   if ((!ec->already_unparented) && cd && cd->reparented)
     {
        _e_comp_x_focus_setdown(ec);
        e_bindings_mouse_ungrab(E_BINDING_CONTEXT_WINDOW, pwin);
        e_bindings_wheel_ungrab(E_BINDING_CONTEXT_WINDOW, pwin);
        if (!cd->deleted)
          {
             if (stopping)
               {
                  ecore_x_window_reparent(win, e_comp->root,
                                          ec->client.x, ec->client.y);
                  ecore_x_window_configure(win,
                                           ECORE_X_WINDOW_CONFIGURE_MASK_SIBLING |
                                           ECORE_X_WINDOW_CONFIGURE_MASK_STACK_MODE,
                                           0, 0, 0, 0, 0, pwin,
                                           ECORE_X_WINDOW_STACK_BELOW);
               }

             else
               /* put the window back where we found it to prevent annoying dancing windows */
               ecore_x_window_reparent(win, e_comp->root,
                                       cd->initial_attributes.x,
                                       cd->initial_attributes.y);
             if (!ec->internal)
               ecore_x_window_save_set_del(win);
          }
        e_pixmap_alias(NULL, E_PIXMAP_TYPE_X, pwin);
     }
   ec->already_unparented = 1;
   if (cd)
     eina_hash_del_by_key(clients_win_hash, &win);
   if (cd && cd->damage)
     {
        eina_hash_del(damages_hash, &cd->damage, ec);
        ecore_x_damage_free(cd->damage);
        cd->damage = 0;
     }
   if (cd && cd->reparented)
     {
        eina_hash_del_by_key(clients_win_hash, &pwin);
        e_pixmap_parent_window_set(e_comp_x_client_pixmap_get(ec), 0);
        ecore_x_window_free(pwin);
        eina_hash_add(dead_wins, &pwin, (void*)1);
        ecore_timer_add(0.5, _e_comp_x_dead_win_timer, (uintptr_t*)(uintptr_t)pwin);
     }

   if (ec->hacks.mapping_change)
     _e_comp_x_mapping_change_disabled--;
   if (ec->parent && (ec->parent->modal == ec))
     {
        E_Comp_X_Client_Data *pcd;

        pcd = _e_comp_x_client_data_get(ec->parent);
        if (pcd->lock_win)
          {
             eina_hash_del_by_key(clients_win_hash, &pcd->lock_win);
             ecore_x_window_hide(pcd->lock_win);
             ecore_x_window_free(pcd->lock_win);
             pcd->lock_win = 0;
          }
        ec->parent->lock_close = 0;
        ec->parent->modal = NULL;
     }
   if (cd)
     E_FREE_FUNC(cd->first_draw_delay, ecore_timer_del);
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        if (e_pixmap_is_x(ec->pixmap))
          e_comp_wl_client_xwayland_setup(ec, NULL, NULL);
        else
          {
             e_pixmap_alias(NULL, E_PIXMAP_TYPE_X, win);
             free(cd);
             e_pixmap_free(e_comp_x_client_pixmap_get(ec));
          }
     }
   else
#endif
     {
        // work around broken clients that withdraw windows and don't
        // remove WM_STATE themselves... like qt6 + telegram and media
        // window popups.
        ecore_x_window_prop_property_del(e_client_util_win_get(ec),
                                         ECORE_X_ATOM_WM_STATE);
        if (e_pixmap_free(ec->pixmap))
          e_pixmap_client_set(ec->pixmap, NULL);
        ec->pixmap = NULL;
     }
   if (ec->netwm.icons)
     {
        _e_comp_x_client_icon_free(ec->netwm.icons, ec->netwm.num_icons);
        ec->netwm.icons = NULL;
        ec->netwm.num_icons = 0;
     }

   if (post_clients)
     post_clients = eina_list_remove(post_clients, ec);

   _e_comp_x_focus_check();
}

static void
_e_comp_x_hook_client_move_end(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   _e_comp_x_client_data_get(ec)->moving = 0;
   if (!_e_comp_x_client_data_get(ec)->move_counter) return;
   _e_comp_x_client_data_get(ec)->move_counter = 0;
   ec->post_move = 1;
   _e_comp_x_post_client_idler_add(ec);
}

static void
_e_comp_x_hook_client_move_begin(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   _e_comp_x_client_data_get(ec)->moving = 1;
}

static void
_e_comp_x_hook_client_resize_end(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   if (_e_comp_x_client_data_get(ec)->alarm)
     {
        eina_hash_del_by_key(alarm_hash, &_e_comp_x_client_data_get(ec)->alarm);
        ecore_x_sync_alarm_free(_e_comp_x_client_data_get(ec)->alarm);
        _e_comp_x_client_data_get(ec)->alarm = 0;
     }
   ec->netwm.sync.alarm = 0;
   /* resize to last geometry if sync alarm for it was not yet handled */
   if (ec->pending_resize)
     {
        EC_CHANGED(ec);
        ec->changes.pos = 1;
        ec->changes.size = 1;
     }

   E_FREE_LIST(ec->pending_resize, free);
}

static void
_e_comp_x_hook_client_resize_begin(void *d EINA_UNUSED, E_Client *ec)
{
   E_COMP_X_PIXMAP_CHECK;
   if (!ec->netwm.sync.request) return;
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _e_comp_x_client_data_get(ec)->alarm = ecore_x_sync_alarm_new(_e_comp_x_client_data_get(ec)->sync_counter);
        eina_hash_add(alarm_hash, &_e_comp_x_client_data_get(ec)->alarm, ec);
     }
   ec->netwm.sync.alarm = ec->netwm.sync.serial = 1;
   ec->netwm.sync.wait = 0;
   ec->netwm.sync.send_time = ecore_loop_time_get();
}

static void
_e_comp_x_hook_client_desk_set(void *d EINA_UNUSED, E_Client *ec)
{
   unsigned int num[2];
   int x, y;

   E_COMP_X_PIXMAP_CHECK;
   e_desk_xy_get(ec->desk, &x, &y);
   num[0] = x, num[1] = y;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_DESK, num, 2);
}

static Eina_Bool
_e_comp_x_cb_ping(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_X_Event_Ping *ev)
{
   E_Client *ec;

   ec = _e_comp_x_client_find_by_window(ev->event_win);
   if (ec) ec->ping_ok = 1;
   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_comp_pointer_grab(void)
{
   if (_e_comp_x_suspend_grabbed) ecore_x_window_free(_e_comp_x_suspend_grabbed);
   _e_comp_x_suspend_grabbed = ecore_x_window_input_new(e_comp->root, 0, 0, 1, 1);
   ecore_x_window_show(_e_comp_x_suspend_grabbed);
   if (!e_grabinput_get(_e_comp_x_suspend_grabbed, 0, 0))
     {
        ecore_x_window_free(_e_comp_x_suspend_grabbed);
        _e_comp_x_suspend_grabbed = 0;
     }
}

static void
_e_comp_pointer_ungrab(void)
{
   if (!_e_comp_x_suspend_grabbed) return;
   e_grabinput_release(_e_comp_x_suspend_grabbed, 0);
   ecore_x_window_free(_e_comp_x_suspend_grabbed);
   _e_comp_x_suspend_grabbed = 0;
}

static void
_e_comp_cb_pointer_suspend_resume_done(void *data, Evas_Object *obj, const char *emission, const char *source)
{
   edje_object_signal_callback_del(obj, emission, source,
                                   _e_comp_cb_pointer_suspend_resume_done);
   if (!data)
     {
        _e_comp_pointer_ungrab();
        e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
     }
   e_screensaver_update();
}

EINTERN Eina_Bool
_e_comp_x_screensaver_on()
{
   const char *s;

   if ((!e_comp->pointer) || (!e_comp->pointer->o_ptr)) return ECORE_CALLBACK_RENEW;
   s = edje_object_data_get(e_comp->pointer->o_ptr, "can_suspend");

   if ((s) && (atoi(s) == 1))
     {
        if (e_menu_is_active())
          {
             E_Menu *m = e_menu_active_get();

             if (m)
               {
                  E_Menu *m2 = e_menu_root_get(m);

                  if (m2) e_menu_deactivate(m2);
               }
          }
        if (!e_desklock_state_get())
          {
             _e_comp_pointer_ungrab();
             e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
             e_pointer_grab_set(e_comp->pointer, EINA_TRUE);
             _e_comp_pointer_grab();
             if (!_e_comp_x_suspend_grabbed) return ECORE_CALLBACK_RENEW;
          }
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_add(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done",
                                        "e",
                                        _e_comp_cb_pointer_suspend_resume_done,
                                        e_comp);
        edje_object_signal_emit(e_comp->pointer->o_ptr,
                                "e,state,mouse,suspend", "e");
     }
   return ECORE_CALLBACK_RENEW;
}

EINTERN Eina_Bool
_e_comp_x_screensaver_off()
{
   const char *s;

   e_screensaver_update();
   _e_comp_pointer_ungrab();
   e_pointer_grab_set(e_comp->pointer, EINA_FALSE);
   if ((!e_comp->pointer) || (!e_comp->pointer->o_ptr)) return ECORE_CALLBACK_RENEW;
   s = edje_object_data_get(e_comp->pointer->o_ptr, "can_suspend");

   if ((s) && (atoi(s) == 1))
     {
        if (!e_desklock_state_get())
          {
             e_pointer_grab_set(e_comp->pointer, EINA_TRUE);
             _e_comp_pointer_grab();
             if (!_e_comp_x_suspend_grabbed) return ECORE_CALLBACK_RENEW;
          }
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,suspend,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_del(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done", "e",
                                        _e_comp_cb_pointer_suspend_resume_done);
        edje_object_signal_callback_add(e_comp->pointer->o_ptr,
                                        "e,state,mouse,resume,done",
                                        "e",
                                        _e_comp_cb_pointer_suspend_resume_done,
                                        NULL);
        edje_object_signal_emit(e_comp->pointer->o_ptr,
                                "e,state,mouse,resume", "e");
     }
   return ECORE_CALLBACK_RENEW;
}

static Ecore_Timer *screensaver_eval_timer = NULL;
static Ecore_Animator *screensaver_eval_animator = NULL;
static Eina_Bool saver_on = EINA_FALSE;

static Eina_Bool
_e_comp_x_screensaver_eval_cb(void *d EINA_UNUSED)
{
   E_FREE_FUNC(screensaver_eval_animator, ecore_animator_del);
   screensaver_eval_timer = NULL;
   e_screensaver_eval(saver_on);
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_screensaver_pre_eval_cb(void *d EINA_UNUSED)
{
   E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
   screensaver_eval_animator = NULL;
   screensaver_eval_timer = ecore_timer_loop_add(0.3, _e_comp_x_screensaver_eval_cb, NULL);
   e_screensaver_eval(saver_on);
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_screensaver_notify_cb(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Screensaver_Notify *ev)
{
   if (e_screensaver_ignore_get()) return ECORE_CALLBACK_PASS_ON;
   if ((ev->on) && (!saver_on))
     {
        saver_on = EINA_TRUE;
        E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
        E_FREE_FUNC(screensaver_eval_animator, ecore_animator_del);
        screensaver_eval_timer = ecore_timer_loop_add(0.3, _e_comp_x_screensaver_eval_cb, NULL);
     }
   else if ((!ev->on) && (saver_on))
     {
        saver_on = EINA_FALSE;
        E_FREE_FUNC(screensaver_eval_timer, ecore_timer_del);
        E_FREE_FUNC(screensaver_eval_animator, ecore_animator_del);
        screensaver_eval_animator = ecore_animator_add(_e_comp_x_screensaver_pre_eval_cb, NULL);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_comp_x_backlight_notify_cb(void *data EINA_UNUSED, int t EINA_UNUSED, Ecore_X_Event_Randr_Output_Property_Notify *ev)
{
   if (ev->property != backlight_atom) return ECORE_CALLBACK_RENEW;
   e_backlight_update();
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_cb_frame_extents_request(void *data EINA_UNUSED, int ev_type EINA_UNUSED, Ecore_X_Event_Frame_Extents_Request *ev)
{
   Ecore_X_Window_Type type;
   Ecore_X_MWM_Hint_Decor decor;
   Ecore_X_Window_State *state;
   Ecore_X_Window win;
   Frame_Extents *extents;
   const char *border, *sig, *key;
   int ok;
   unsigned int i, num;

   win = ecore_x_window_parent_get(ev->win);
   if (win != e_comp->root) return ECORE_CALLBACK_PASS_ON;

   /* TODO:
    * * We need to check if we remember this window, and border locking is set
    */
   border = "default";
   key = border;
   ok = ecore_x_mwm_hints_get(ev->win, NULL, &decor, NULL);
   if ((ok) &&
       (!(decor & ECORE_X_MWM_HINT_DECOR_ALL)) &&
       (!(decor & ECORE_X_MWM_HINT_DECOR_TITLE)) &&
       (!(decor & ECORE_X_MWM_HINT_DECOR_BORDER)))
     {
        border = "borderless";
        key = border;
     }

   ok = ecore_x_netwm_window_type_get(ev->win, &type);
   if ((ok) &&
       ((type == ECORE_X_WINDOW_TYPE_DESKTOP) ||
        (type == ECORE_X_WINDOW_TYPE_DOCK)))
     {
        border = "borderless";
        key = border;
     }

   sig = NULL;
   ecore_x_netwm_window_state_get(ev->win, &state, &num);
   if (state)
     {
        int maximized = 0;

        for (i = 0; i < num; i++)
          {
             switch (state[i])
               {
                case ECORE_X_WINDOW_STATE_MAXIMIZED_VERT:
                  maximized++;
                  break;

                case ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ:
                  maximized++;
                  break;

                case ECORE_X_WINDOW_STATE_FULLSCREEN:
                  border = "borderless";
                  key = border;
                  break;

                case ECORE_X_WINDOW_STATE_SHADED:
                case ECORE_X_WINDOW_STATE_SKIP_TASKBAR:
                case ECORE_X_WINDOW_STATE_SKIP_PAGER:
                case ECORE_X_WINDOW_STATE_HIDDEN:
                case ECORE_X_WINDOW_STATE_ICONIFIED:
                case ECORE_X_WINDOW_STATE_MODAL:
                case ECORE_X_WINDOW_STATE_STICKY:
                case ECORE_X_WINDOW_STATE_ABOVE:
                case ECORE_X_WINDOW_STATE_BELOW:
                case ECORE_X_WINDOW_STATE_DEMANDS_ATTENTION:
                case ECORE_X_WINDOW_STATE_UNKNOWN:
                  break;
               }
          }
        if ((maximized == 2) &&
            (e_config->maximize_policy == E_MAXIMIZE_FULLSCREEN))
          {
             sig = "ev,action,maximize,fullscreen";
             key = "maximize,fullscreen";
          }
        free(state);
     }

   extents = eina_hash_find(frame_extents, key);
   if (!extents)
     {
        Evas_Object *o;
        char buf[1024];

        extents = E_NEW(Frame_Extents, 1);
        if (!extents) return ECORE_CALLBACK_RENEW;

        o = edje_object_add(e_comp->evas);
        snprintf(buf, sizeof(buf), "ev/widgets/border/%s/border", border);
        ok = e_theme_edje_object_set(o, "base/theme/borders", buf);
        if (ok)
          {
             Evas_Coord x, y, w, h;

             if (sig)
               {
                  edje_object_signal_emit(o, sig, "ev");
                  edje_object_message_signal_process(o);
               }

             evas_object_resize(o, 1000, 1000);
             edje_object_calc_force(o);
             edje_object_part_geometry_get(o, "ev.swallow.client",
                                           &x, &y, &w, &h);
             extents->l = x;
             extents->r = 1000 - (x + w);
             extents->t = y;
             extents->b = 1000 - (y + h);
          }
        else
          {
             extents->l = 0;
             extents->r = 0;
             extents->t = 0;
             extents->b = 0;
          }
        evas_object_del(o);
        eina_hash_add(frame_extents, key, extents);
     }

   if (extents)
     ecore_x_netwm_frame_size_set(ev->win, extents->l, extents->r, extents->t, extents->b);

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_randr_change(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Client *ec;

   ecore_x_netwm_desk_size_set(e_comp->root, e_comp->w, e_comp->h);
   E_CLIENT_FOREACH(ec)
     {
        if (!e_client_util_ignored_get(ec))
          _e_comp_x_client_zone_geometry_set(ec);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_comp_x_devices_change_cb(void *data EINA_UNUSED, int t EINA_UNUSED, void *ev EINA_UNUSED)
{
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        // do nothing here
     }
   else
#endif
     {
        e_comp_x_devices_config_apply(EINA_FALSE);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_comp_x_del(E_Comp *c)
{
   unsigned int i;

   if (!e_comp_wl)
     ecore_x_window_key_ungrab(c->root, "F", ECORE_EVENT_MODIFIER_SHIFT |
                               ECORE_EVENT_MODIFIER_CTRL |
                               ECORE_EVENT_MODIFIER_ALT, 0);
   if (ecore_x_display_get())
     {
        if (c->grabbed)
          {
             c->grabbed = 0;
             ecore_x_ungrab();
             ecore_x_sync();
          }

        if (restart)
          {
             Ecore_X_Atom a;
             Ecore_X_Window blackwin;

             a = ecore_x_atom_get("E_COMP_BLACK_WIN");
             blackwin = ecore_x_window_permanent_new(c->root, a);
             ecore_x_window_ignore_set(blackwin, 1);
             ecore_x_window_override_set(blackwin, EINA_TRUE);
             ecore_x_window_move_resize(blackwin, 0, 0, e_comp->w, e_comp->h);
             ecore_x_window_background_color_set(blackwin, 0, 0, 0);
             ecore_x_window_show(blackwin);
             ecore_x_window_prop_window_set(c->root, a, &blackwin, 1);
             ecore_x_sync();
          }

        for (i = e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); i <= e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); i++)
          ecore_x_window_free(c->layers[i].win);

        ecore_x_composite_unredirect_subwindows
          (c->root, ECORE_X_COMPOSITE_UPDATE_MANUAL);
        if (c->block_win) ecore_x_window_free(c->block_win);
        ecore_x_composite_render_window_disable(c->win);
        e_alert_composite_win(c->root, 0);

        ecore_x_window_free(c->cm_selection);
        ecore_x_screen_is_composited_set(0, 0);
     }

   eina_list_free(c->x_comp_data->retry_clients);
   ecore_timer_del(c->x_comp_data->retry_timer);
   E_FREE_FUNC(mouse_in_job, ecore_job_del);
   E_FREE_FUNC(focus_job, ecore_job_del);
   E_FREE_FUNC(unfocus_job, ecore_job_del);
   free(c->x_comp_data);
   c->x_comp_data = e_comp_x = NULL;
   c->root = 0;
}

static void
_e_comp_x_manage_windows(void)
{
   Ecore_X_Window *windows;
   int wnum;
   int i;
   const char *atom_names[] =
   {
      "_XEMBED_INFO",
      "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR",
      "KWM_DOCKWINDOW"
   };
   Ecore_X_Atom atoms[3];
   Ecore_X_Atom atom_xmbed, atom_kde_netwm_systray, atom_kwm_dockwindow;
   int count;

   /* a manager is designated for each root. lets get all the windows in
      the managers root */
   windows = ecore_x_window_children_get(e_comp->root, &wnum);
   if (!windows) return;

   ecore_x_atoms_get(atom_names, 3, atoms);
   atom_xmbed = atoms[0];
   atom_kde_netwm_systray = atoms[1];
   atom_kwm_dockwindow = atoms[2];
   for (i = 0; i < wnum; i++)
     {
        unsigned int ret_val;
        unsigned char *data = NULL;
        int ret;
        E_Client *ec = NULL;

        if ((e_comp->win == windows[i]) || (e_comp->ee_win == windows[i]) ||
            (e_comp->root == windows[i]) || (e_comp->cm_selection == windows[i]))
          continue;
        if (_e_comp_x_client_find_by_window(windows[i]))
          continue;
        if (!ecore_x_window_prop_property_get(windows[i],
                                              atom_kde_netwm_systray,
                                              atom_xmbed, 32,
                                              &data, &count))
          data = NULL;

        if (!data)
          {
             if (!ecore_x_window_prop_property_get(windows[i],
                                                   atom_kwm_dockwindow,
                                                   atom_kwm_dockwindow, 32,
                                                   &data, &count))
               data = NULL;
          }
        if (data)
          {
             E_FREE(data);
             continue;
          }
        ret = ecore_x_window_prop_card32_get(windows[i],
                                             E_ATOM_MANAGED,
                                             &ret_val, 1);

        ec = _e_comp_x_client_new(windows[i], (ret > -1) && (ret_val == 1));
        if (ec && (!_e_comp_x_client_data_get(ec)->initial_attributes.visible))
          {
             DELD(ec, 3);
             E_FREE_FUNC(ec, e_object_del);
          }
        if (ec)
          {
             if (ec->override || (!ec->icccm.fetch.hints))
               {
                  _e_comp_x_client_evas_init(ec);
                  if (!ec->input_only)
                    _e_comp_x_client_damage_add(ec);
                  e_pixmap_usable_set(ec->pixmap, 1);
                  _e_comp_x_client_data_get(ec)->first_map = 1;
                  evas_object_geometry_set(ec->frame, ec->client.x, ec->client.y, ec->client.w, ec->client.h);
               }
             ec->ignore_first_unmap = 1;
             if (ec->override || (!ec->icccm.fetch.hints))
               evas_object_show(ec->frame);
             _e_comp_x_client_stack(ec);
          }
     }
   free(windows);
}

static void
_e_comp_x_bindings_grab_cb(void)
{
   Eina_List *l;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if (e_client_util_ignored_get(ec)) continue;
        _e_comp_x_focus_init(ec);
        if (ec->focused)
          _e_comp_x_focus_setdown(ec);
        else
          {
             _e_comp_x_focus_setup(ec);
             e_bindings_mouse_grab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
             e_bindings_wheel_grab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
          }
     }
}

static void
_e_comp_x_bindings_ungrab_cb(void)
{
   Eina_List *l;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        Ecore_X_Window win;

        if (e_client_util_ignored_get(ec)) continue;
        win = e_client_util_win_get(ec);
        ecore_x_window_button_ungrab(win, 1, 0, 1);
        ecore_x_window_button_ungrab(win, 2, 0, 1);
        ecore_x_window_button_ungrab(win, 3, 0, 1);
        e_bindings_mouse_ungrab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
        e_bindings_wheel_ungrab(E_BINDING_CONTEXT_WINDOW, e_client_util_pwin_get(ec));
        _e_comp_x_client_data_get(ec)->button_grabbed = 0;
     }
}

static void
_e_comp_x_desklock_hide(void)
{
   e_comp_ungrab_input(1, 1);

   if (e_comp_x->lock_grab_break_wnd)
     ecore_x_window_show(e_comp_x->lock_grab_break_wnd);
   e_comp_x->lock_grab_break_wnd = 0;
   e_comp_override_del();
}

static Eina_Bool
_e_comp_x_desklock_show(void)
{
   if (!e_comp_grab_input(1, 1))
     {
        Ecore_X_Window *windows;
        int wnum, i;

        windows = ecore_x_window_children_get(e_comp->root, &wnum);
        if (!windows) goto fail;
        for (i = 0; i < wnum; i++)
          {
             Ecore_X_Window_Attributes att;

             memset(&att, 0, sizeof(Ecore_X_Window_Attributes));
             ecore_x_window_attributes_get(windows[i], &att);
             if (att.visible)
               {
                  ecore_x_window_hide(windows[i]);
                  if (e_comp_grab_input(1, 1))
                    {
                       e_comp_x->lock_grab_break_wnd = windows[i];
                       free(windows);
                       goto works;
                    }
                  ecore_x_window_show(windows[i]);
               }
          }
        free(windows);
     }
works:
   e_comp_override_add();

   return EINA_TRUE;
fail:
   /* everything failed - can't lock */
   e_util_dialog_show(_("Lock Failed"),
                      _("Locking the desktop failed because some application<ps/>"
                        "has grabbed either the keyboard or the mouse or both<ps/>"
                        "and their grab is unable to be broken."));
   return EINA_FALSE;
}

static Eina_Bool
_e_comp_x_setup(Ecore_X_Window root, int w, int h)
{
   Ecore_X_Window_Attributes att;
   Ecore_X_Atom a;
   Ecore_X_Window blackwin = 0;
   Eina_Bool res;
   unsigned int i;

   res = ecore_x_screen_is_composited(0);
   if (res)
     {
        ERR(_("Another compositor is already running on your display server."));
        return EINA_FALSE;
     }
   if (!ecore_x_window_manage(root)) return EINA_FALSE;

   a = ecore_x_atom_get("E_COMP_BLACK_WIN");
   if (ecore_x_window_prop_window_get(root, a, &blackwin, 1) == 1)
     {
        ecore_x_window_ignore_set(blackwin, 1);
     }

   e_comp_x = e_comp->x_comp_data = E_NEW(E_Comp_X_Data, 1);
   ecore_x_e_window_profile_supported_set(root, EINA_TRUE);
   e_comp->cm_selection = ecore_x_window_input_new(root, 0, 0, 1, 1);
   if (!e_comp->cm_selection) return EINA_FALSE;
   ecore_x_icccm_name_class_set(e_comp->cm_selection, "comp", "cm_selection");
   e_comp_ignore_win_add(E_PIXMAP_TYPE_X, e_comp->cm_selection);
   e_hints_init(root, e_comp->cm_selection);
   ecore_x_window_background_color_set(root, 0, 0, 0);
   ecore_x_screen_is_composited_set(0, e_comp->cm_selection);
   ecore_x_selection_owner_set(e_comp->cm_selection, ecore_x_atom_get("WM_S0"), ecore_x_current_time_get());

   e_comp->win = ecore_x_composite_render_window_enable(root);
   if (!e_comp->win)
     {
        ERR(_("Your display server does not support the compositor overlay window.\n"
              "This is needed for Enlightenment to function."));
        return EINA_FALSE;
     }
   e_comp->root = root;

   memset((&att), 0, sizeof(Ecore_X_Window_Attributes));
   ecore_x_window_attributes_get(e_comp->win, &att);

   if ((att.depth != 24) && (att.depth != 32))
     {
        /*
                  e_util_dialog_internal
                  (_("Compositor Error"), _("Your screen is not in 24/32bit display mode.<ps/>"
                  "This is required to be your default depth<ps/>"
                  "setting for the compositor to work properly."));
                  ecore_x_composite_render_window_disable(e_comp->win);
                  free(e_comp);
                  return NULL;
         */
     }
   e_alert_composite_win(root, e_comp->win);

   if (!e_comp->ee)
     {
        if (!e_comp_x_randr_canvas_new(e_comp->win, w, h))
          ecore_job_add(_e_comp_x_add_fail_job, NULL);
     }

   ecore_x_composite_redirect_subwindows(root, ECORE_X_COMPOSITE_UPDATE_MANUAL);

   ecore_evas_data_set(e_comp->ee, "comp", e_comp);
   e_comp->bindings_grab_cb = _e_comp_x_bindings_grab_cb;
   e_comp->bindings_ungrab_cb = _e_comp_x_bindings_ungrab_cb;

   if (e_comp->comp_type == E_PIXMAP_TYPE_NONE)
     {
        if (!e_comp_canvas_init(w, h)) return EINA_FALSE;
        ecore_x_window_key_grab(root, "F", ECORE_EVENT_MODIFIER_SHIFT |
                        ECORE_EVENT_MODIFIER_CTRL |
                        ECORE_EVENT_MODIFIER_ALT, 0);
     }

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_grabinput_focus(e_comp->ee_win, E_FOCUS_METHOD_PASSIVE);
   else
     e_grabinput_focus(e_comp->root, E_FOCUS_METHOD_PASSIVE);

   /* init layers */
   for (i = e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); i <= e_comp_canvas_layer_map(E_LAYER_CLIENT_PRIO); i++)
     {
        char buf[64];
        E_Client *ec;

        e_comp->layers[i].win = ecore_x_window_input_new(root, 0, 0, 1, 1);
        ecore_x_window_show(e_comp->layers[i].win);
        snprintf(buf, sizeof(buf), "%d", e_comp_canvas_layer_map_to(i));
        ecore_x_icccm_name_class_set(e_comp->layers[i].win, buf, "e_layer_win");

        if (i >= e_comp_canvas_layer_map(E_LAYER_CLIENT_ABOVE))
          ecore_x_window_raise(e_comp->layers[i].win);
        ec = _e_comp_x_client_new(e_comp->layers[i].win, 0);
        if (!ec) continue;
        evas_object_name_set(ec->frame, "layer_obj");
        ec->lock_client_stacking = 1;
        ec->internal = 1;
        ec->visible = 1;
        evas_object_del(e_comp->layers[i].obj);
        e_comp->layers[i].obj = ec->frame;
        evas_object_layer_set(ec->frame, e_comp_canvas_layer_map_to(i));
        evas_object_pass_events_set(ec->frame, 1);
        evas_object_show(ec->frame);
     }
   for (i = e_comp_canvas_layer_map(E_LAYER_CLIENT_NORMAL); i >= e_comp_canvas_layer_map(E_LAYER_CLIENT_DESKTOP); i--)
     ecore_x_window_lower(e_comp->layers[i].win);

   ecore_evas_lower(e_comp->ee);
   if (e_comp->comp_type == E_PIXMAP_TYPE_NONE)
     {
        e_comp->pointer = e_pointer_window_new(e_comp->root, 0);
        e_comp->pointer->color = ecore_x_cursor_color_supported_get();
        e_pointer_type_push(e_comp->pointer, e_comp->pointer, "default");
        ecore_x_icccm_state_set(ecore_evas_window_get(e_comp->ee), ECORE_X_WINDOW_STATE_HINT_NORMAL);
        e_startup();
     }
   else
     e_pointer_window_add(e_comp->pointer, e_comp->root);
   _e_comp_x_manage_windows();

   if (blackwin)
     {
        ecore_x_window_free(blackwin);
        ecore_x_window_prop_property_del(root, a);
        ecore_x_window_ignore_set(blackwin, 0);
     }

   return !!e_comp->canvas->resize_object;
}

static Eina_Bool
_e_comp_x_screens_setup(void)
{
   Ecore_X_Window root;
   int rw, rh;
   Eina_Bool ret;

   if (e_comp->comp_type == E_PIXMAP_TYPE_NONE)
     {
        e_comp_x_randr_screen_iface_set();
        if (!e_randr2_init()) return 0;
     }
   root = ecore_x_window_root_first_get();
   if (!root)
     {
        e_error_message_show("X reports there are no root windows!\n");
        return 0;
     }
   ecore_x_window_size_get(root, &rw, &rh);
   if (e_comp->comp_type == E_PIXMAP_TYPE_NONE)
     e_randr2_screens_setup(rw, rh);
   ret = _e_comp_x_setup(root, rw, rh);
   if (ret) return EINA_TRUE;
   e_randr2_shutdown();
   e_xinerama_screens_set(NULL);
   e_comp->screen = NULL;
   return EINA_FALSE;
}

E_API Eina_Bool
e_comp_x_init(void)
{
   Eina_List *h = NULL;
   if (!ecore_x_init(NULL))
     {
        fprintf(stderr, _("Enlightenment cannot initialize X Connection...\n"));
        return EINA_FALSE;
     }

   // delay the x vsync animator hook by 0.5 frames
   ecore_x_vsync_animator_tick_delay_set(0.5);

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     ecore_x_io_error_handler_set(_e_main_cb_x_fatal, NULL);

   if (!ecore_x_composite_query())
     {
        _e_comp_x_print_win(0);
        ecore_x_shutdown();
        e_error_message_show
          (_("Your display server does not support XComposite, "
             "or Ecore-X was built without XComposite support. "
             "Note that for composite support you will also need "
             "XRender and XFixes support in X11 and Ecore."));
        return EINA_FALSE;
     }
   if (!ecore_x_damage_query())
     {
        e_error_message_show
          (_("Your display server does not support XDamage "
             "or Ecore was built without XDamage support."));
        return EINA_FALSE;
     }
   if (!e_atoms_init()) return 0;

   clients_win_hash = eina_hash_int32_new(NULL);
   damages_hash = eina_hash_int32_new(NULL);
   alarm_hash = eina_hash_int32_new(NULL);
   dead_wins = eina_hash_int32_new(NULL);
   pending_configures = eina_hash_int32_new(NULL);
   frame_extents = eina_hash_string_superfast_new(free);

   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_DESK_SET, _e_comp_x_hook_client_desk_set, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_RESIZE_BEGIN, _e_comp_x_hook_client_resize_begin, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_RESIZE_END, _e_comp_x_hook_client_resize_end, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN, _e_comp_x_hook_client_move_begin, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_MOVE_END, _e_comp_x_hook_client_move_end, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_DEL, _e_comp_x_hook_client_del, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_NEW_CLIENT, _e_comp_x_hook_client_new, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_EVAL_FETCH, _e_comp_x_hook_client_fetch, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_EVAL_PRE_FRAME_ASSIGN, _e_comp_x_hook_client_pre_frame_assign, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_UNREDIRECT, _e_comp_x_hook_client_unredirect, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_REDIRECT, _e_comp_x_hook_client_redirect, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_EVAL_POST_NEW_CLIENT, _e_comp_x_hook_client_post_new_client, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_FOCUS_SET, _e_comp_x_hook_client_focus_set, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_FOCUS_UNSET, _e_comp_x_hook_client_focus_unset, NULL));
   h = eina_list_append(h, e_client_hook_add(E_CLIENT_HOOK_EVAL_END, _e_comp_x_hook_client_eval_end, NULL));

   if (!_e_comp_x_screens_setup())
     {
        e_atoms_shutdown();
        E_FREE_LIST(h, e_client_hook_del);
        E_FREE_FUNC(clients_win_hash, eina_hash_free);
        E_FREE_FUNC(damages_hash, eina_hash_free);
        E_FREE_FUNC(alarm_hash, eina_hash_free);
        E_FREE_FUNC(frame_extents, eina_hash_free);
        return 0;
     }

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_COMP_OBJECT_ADD, _e_comp_x_object_add, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_DESTROY, _e_comp_x_destroy, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_SHOW, _e_comp_x_show, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_SHOW_REQUEST, _e_comp_x_show_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_RESIZE_REQUEST, _e_comp_x_resize_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_HIDE, _e_comp_x_hide, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_REPARENT, _e_comp_x_reparent, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_CONFIGURE, _e_comp_x_configure, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_CONFIGURE_REQUEST, _e_comp_x_configure_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_STATE_REQUEST, _e_comp_x_state_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_STACK, _e_comp_x_stack, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_STACK_REQUEST, _e_comp_x_stack_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_CLIENT_MESSAGE, _e_comp_x_message, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_PROPERTY, _e_comp_x_property, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_SHAPE, _e_comp_x_shape, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_DAMAGE_NOTIFY, _e_comp_x_damage, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_DAMAGE, _e_comp_x_damage_win, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_MAPPING_CHANGE, _e_comp_x_mapping_change, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_FOCUS_IN, _e_comp_x_focus_in, NULL);
   if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
     E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_FOCUS_OUT, _e_comp_x_focus_out, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_WINDOW_MOVE_RESIZE_REQUEST,
                         _e_comp_x_move_resize_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_DESKTOP_CHANGE,
                         _e_comp_x_desktop_change, NULL);
   if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
     E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_SYNC_ALARM,
                           _e_comp_x_sync_alarm, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_BUTTON_DOWN,
                         _e_comp_x_mouse_down, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_BUTTON_UP,
                         _e_comp_x_mouse_up, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_WHEEL,
                         _e_comp_x_mouse_wheel, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_MOVE,
                         _e_comp_x_mouse_move, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_MOUSE_IN,
                         _e_comp_x_mouse_in, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_MOUSE_OUT,
                         _e_comp_x_mouse_out, NULL);

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_ZONE_SET,
                         _e_comp_x_client_zone_set, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CLIENT_PROPERTY,
                         _e_comp_x_client_property, NULL);

   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_FRAME_EXTENTS_REQUEST,
                         _e_comp_x_cb_frame_extents_request, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_PING,
                         _e_comp_x_cb_ping, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_RANDR_OUTPUT_PROPERTY_NOTIFY, _e_comp_x_backlight_notify_cb, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_DEVICES_CHANGE, _e_comp_x_devices_change_cb, NULL);
   if (ecore_x_randr_version_get() >= RANDR_VERSION_1_3)
     backlight_atom = ecore_x_atom_get("Backlight");

   if (!backlight_atom)
     backlight_atom = ecore_x_atom_get("BACKLIGHT");

   ecore_x_passive_grab_replay_func_set(_e_comp_x_grab_replay, NULL);

   if (!e_xsettings_init())
     e_error_message_show(_("Enlightenment cannot initialize the XSettings system.\n"));
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_RANDR_CHANGE, _e_comp_x_randr_change, NULL);

   E_LIST_HANDLER_APPEND(handlers, E_EVENT_POWERSAVE_UPDATE, _e_comp_x_powersave, NULL);

   ecore_x_sync();
   _x_idle_flush = ecore_idle_enterer_add(_e_comp_x_flusher, NULL);

   if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
     {
        ecore_x_screensaver_event_listen_set(1);
        E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_ON, _e_comp_x_screensaver_on, NULL);
        E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_OFF, _e_comp_x_screensaver_off, NULL);
        E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_SCREENSAVER_NOTIFY, _e_comp_x_screensaver_notify_cb, NULL);
        ecore_x_screensaver_custom_blanking_enable();

        e_screensaver_attrs_set(ecore_x_screensaver_timeout_get(),
                                ecore_x_screensaver_blank_get(),
                                ecore_x_screensaver_expose_get());
        e_desklock_show_hook_add(_e_comp_x_desklock_show);
        e_desklock_hide_hook_add(_e_comp_x_desklock_hide);
        int timeout = e_screensaver_timeout_get(EINA_TRUE);
        ecore_x_screensaver_set(timeout + 10,
                                0,
                                !e_config->screensaver_blanking,
                                !e_config->screensaver_expose);
        ecore_x_screensaver_set(timeout,
                                0,
                                e_config->screensaver_blanking,
                                e_config->screensaver_expose);
        e_comp_x_devices_config_apply(EINA_FALSE);
     }
   else
     e_dnd_init();
   eina_list_free(h);
   return EINA_TRUE;
}

E_API void
e_comp_x_shutdown(void)
{
   _e_comp_x_del(e_comp);
   E_FREE_LIST(handlers, ecore_event_handler_del);
   E_FREE_FUNC(clients_win_hash, eina_hash_free);
   E_FREE_FUNC(damages_hash, eina_hash_free);
   E_FREE_FUNC(alarm_hash, eina_hash_free);
   E_FREE_FUNC(pending_configures, eina_hash_free);
   E_FREE_FUNC(frame_extents, eina_hash_free);
   E_FREE_FUNC(mouse_in_fix_check_timer, ecore_timer_del);
   e_xsettings_shutdown();
   if (x_fatal) return;
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     ecore_x_screensaver_custom_blanking_disable();
   e_atoms_shutdown();
   /* ecore_x_ungrab(); */
   ecore_x_focus_reset();
   ecore_x_events_allow_all();
   ecore_x_shutdown();
}

EINTERN void
e_comp_x_nocomp_end(void)
{
   e_comp->nocomp = 0;
   ecore_x_window_show(e_comp->win);
   ecore_x_composite_redirect_subwindows(e_comp->root, ECORE_X_COMPOSITE_UPDATE_MANUAL);
   _e_comp_x_focus_check();
}

#ifdef HAVE_WAYLAND
EINTERN void
e_comp_x_xwayland_client_setup(E_Client *ec, E_Client *wc)
{
   Ecore_X_Window win, pwin;
   E_Comp_X_Client_Data *cd;
   Eina_Bool placed;

   win = e_client_util_win_get(ec);
   pwin = e_client_util_pwin_get(ec);
   cd = ec->comp_data;
   e_comp_wl_client_xwayland_setup(wc, cd, e_pixmap_ref(ec->pixmap));
   eina_hash_del(damages_hash, &cd->damage, ec);
   ecore_x_damage_free(cd->damage);
   E_FREE_FUNC(cd->first_draw_delay, ecore_timer_del);
   cd->damage = 0;
   ec->comp_data = NULL;
   cd->evas_init = 0;
   _e_comp_x_client_evas_init(wc);
   if (!e_comp_object_frame_exists(ec->frame))
     {
        int l, r, t, b;

        e_comp_object_frame_geometry_get(ec->frame, &l, &r, &t, &b);
        e_comp_object_frame_geometry_set(wc->frame, l, r, t, b);
     }
   wc->borderless = ec->borderless;
   wc->border.changed = 1;
   EC_CHANGED(wc);
   wc->depth = ec->depth;
   wc->override = ec->override;
   placed = ec->placed;
   wc->input_only = ec->input_only;
   wc->border_size = ec->border_size;
   wc->no_shape_cut = 0;
   memcpy(&wc->icccm, &ec->icccm, sizeof(ec->icccm));
   memcpy(&wc->netwm, &ec->netwm, sizeof(ec->netwm));
   memcpy(&wc->e, &ec->e, sizeof(ec->e));
   wc->new_client = !wc->override;
   if (wc->new_client)
     e_comp->new_clients++;

   eina_hash_set(clients_win_hash, &win, wc);
   if (pwin)
     eina_hash_set(clients_win_hash, &pwin, wc);
   wc->visible = 1;
   if (wc->ignored)
     e_client_unignore(wc);
   if (ec->override)
     e_client_focus_stack_set(eina_list_remove(e_client_focus_stack_get(), wc));
   evas_object_name_set(wc->frame, evas_object_name_get(ec->frame));
   wc->x = ec->x, wc->y = ec->y;
   wc->client.x = ec->client.x, wc->client.y = ec->client.y;
   wc->w = ec->w, wc->h = ec->h;
   wc->client.w = ec->client.w, wc->client.h = ec->client.h;
   wc->layer = ec->layer;
   wc->netwm.sync.request = 1;
   evas_object_layer_set(wc->frame, evas_object_layer_get(ec->frame));
   evas_object_geometry_set(wc->frame, ec->x, ec->y, ec->w, ec->h);
   evas_object_show(wc->frame);
   e_pixmap_alias(wc->pixmap, E_PIXMAP_TYPE_X, e_pixmap_window_get(ec->pixmap));
   e_object_del(E_OBJECT(ec));
   e_hints_window_visible_set(wc);
   _e_comp_x_client_stack(wc);
   wc->placed = placed;
   if (!placed) wc->changes.pos = 0;
}
#endif

E_API inline E_Pixmap *
e_comp_x_client_pixmap_get(const E_Client *ec)
{
#ifdef HAVE_WAYLAND
   if ((!e_pixmap_is_x(ec->pixmap)) && e_client_has_xwindow(ec))
     return e_comp_wl_client_xwayland_pixmap(ec);
#endif
   return ec->pixmap;
}
#define EXECUTIVE_MODE_ENABLED
#define E_COMP_X
#include "e.h"

typedef enum
{
   DEVICE_FLAG_NONE,
   DEVICE_FLAG_TOUCHPAD
} Device_Flags;

static char *devstring = NULL;

static void
_handle_dev_prop(int dev_slot, const char *dev, const char *prop, Device_Flags dev_flags)
{
   int num, size;
   Ecore_X_Atom fmt;

   ///////////////////////////////////////////////////////////////////////////
   // libinput devices
   if (!strcmp(prop, "libinput Middle Emulation Enabled"))
     {
        unsigned char cfval = 0;
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          cfval = e_config->touch_emulate_middle_button;
        else
          cfval = e_config->mouse_emulate_middle_button;
        if ((val) && (size == 8) && (num == 1) && (cfval != val[0]))
          {
             val[0] = cfval;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "libinput Accel Speed"))
     {
        float cfval = 0.0;
        float *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          cfval = e_config->touch_accel;
        else
          cfval = e_config->mouse_accel;
        if ((val) && (size == 32) && (num == 1) && (fabs(cfval - val[0]) >= 0.01))
          {
             val[0] = cfval;
             printf("DEV: change [%s] [%s] -> %1.3f\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "libinput Tapping Enabled"))
     {
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          {
             unsigned char *val = ecore_x_input_device_property_get
               (dev_slot, prop, &num, &fmt, &size);
             if ((val) && (size == 8) && (num == 1) &&
                 (e_config->touch_tap_to_click != val[0]))
               {
                  val[0] = e_config->touch_tap_to_click;
                  printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
                  ecore_x_input_device_property_set
                    (dev_slot, prop, val, num, fmt, size);
               }
             free(val);
          }
     }
//   else if (!strcmp(prop, "libinput Tapping Button Mapping Enabled"))
//     {
//        // 1 bool, 0 = LRM, 1 = LMR
//     }
   else if ((!strcmp(prop, "libinput Horizontal Scrolling Enabled")) ||
            (!strcmp(prop, "libinput Horizontal Scroll Enabled")))
     {
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          {
             unsigned char *val = ecore_x_input_device_property_get
               (dev_slot, prop, &num, &fmt, &size);
             if ((val) && (size == 8) && (num == 1) &&
                 (e_config->touch_scrolling_horiz != val[0]))
               {
                  val[0] = e_config->touch_scrolling_horiz;
                  printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
                  ecore_x_input_device_property_set
                    (dev_slot, prop, val, num, fmt, size);
               }
             free(val);
          }
     }
   else if (!strcmp(prop, "libinput Scroll Method Enabled"))
     {
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          {
             // 3 bool, 2 finger, edge, button
             unsigned char *val0 = ecore_x_input_device_property_get
               (dev_slot, "libinput Scroll Methods Available", &num, &fmt, &size);
             if ((val0) && (size == 8) && (num >= 3))
               {
                  unsigned char *val = ecore_x_input_device_property_get
                    (dev_slot, prop, &num, &fmt, &size);
                  if ((val) && (size == 8) && (num >= 3))
                    {
                       unsigned char cf_2finger = 0, cf_edge = 0;

                       if (val0[0]) cf_2finger = e_config->touch_scrolling_2finger;
                       else cf_2finger = val[0];
                       if (val0[1]) cf_edge = e_config->touch_scrolling_edge;
                       else cf_edge = val[1];
                       if ((cf_2finger != val[0]) || (cf_edge != val[1]))
                         {
                            val[0] = cf_2finger;
                            val[1] = cf_edge;
                            printf("DEV: change [%s] [%s] -> %i %i %i\n", dev, prop, val[0], val[1], val[2]);
                            ecore_x_input_device_property_set
                              (dev_slot, prop, val, num, fmt, size);
                         }
                    }
                  free(val);
               }
             free(val0);
          }
     }
   else if (!strcmp(prop, "libinput Natural Scrolling Enabled"))
     {
        unsigned char cfval = 0;
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if (dev_flags == DEVICE_FLAG_TOUCHPAD)
          cfval = e_config->touch_natural_scroll;
        else
          cfval = e_config->mouse_natural_scroll;
        if ((val) && (size == 8) && (num == 1) && (cfval != val[0]))
          {
             val[0] = cfval;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
// not for now - default
//   else if (!strcmp(prop, "libinput Accel Profile Enabled"))
//     {
//        // 1 bool, 0 = adaptive, 1 = flat
//     }
// do via button mapping for now - not sure about this evdev can't do this
//   else if (!strcmp(prop, "libinput Left Handed Enabled"))
//     {
//        // 1 bool
//     }

   ///////////////////////////////////////////////////////////////////////////
   // synaptics devices
   else if (!strcmp(prop, "Synaptics Middle Button Timeout"))
     {
        // 1 int - in ms
        unsigned int *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 32) && (num == 1) &&
            (e_config->touch_emulate_middle_button && (val[0] != 50)))
          {
             val[0] = 50;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Move Speed"))
     {
        float chval[4] = { 0.0 };

        // 4 val float min, max, accel, unused
        chval[0] = 1.0;
        chval[1] = 1.75;
        chval[2] = 0.15 + (e_config->touch_accel * 0.15);
        float *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 32) && (num == 4) &&
            ((fabs(chval[0] - val[0]) >= 0.01) ||
             (fabs(chval[1] - val[1]) >= 0.01) ||
             (fabs(chval[2] - val[2]) >= 0.01)))
          {
             val[0] = chval[0];
             val[1] = chval[1];
             val[2] = chval[2];
             printf("DEV: change [%s] [%s] -> %1.3f %1.3f %1.3f %1.3f\n", dev, prop, val[1], val[1], val[2], val[3]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Tap Action"))
     {
        // 7 val, 8 bit bit 0 = off, >0 mouse button reported
        // TR, BR, TL, BL, F1, F2, F3
        // 1, 1, 1, 0, 1, 3, 2 <- tap to click
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        const char tapval[7]   =  { 1, 1, 1, 0, 1, 3, 2 };
        const char notapval[7] =  { 0, 0, 0, 0, 0, 0, 0 };
        Eina_Bool have_tapval = EINA_FALSE;
        Eina_Bool have_notapval = EINA_FALSE;
        int i;

        if (num >= 7)
          {
             have_tapval = EINA_TRUE;
             for (i = 0; i < 7; i++) if (val[i] != tapval[i]) have_tapval = EINA_FALSE;
             have_notapval = EINA_TRUE;
             for (i = 0; i < 7; i++) if (val[i] != notapval[i]) have_notapval = EINA_FALSE;
          }
        if ((val) && (size == 8) && (num >= 7) &&
            (((e_config->touch_tap_to_click) && (!have_tapval)) ||
             ((!e_config->touch_tap_to_click) && (!have_notapval))))
          {
             if (e_config->touch_tap_to_click)
               {
                  for (i = 0; i < 7; i++) val[i] = tapval[i];
               }
             else
               {
                  for (i = 0; i < 7; i++) val[i] = notapval[i];
               }
             printf("DEV: change [%s] [%s] -> %i %i %i %i %i %i %i\n", dev, prop, val[0], val[1], val[2], val[3], val[4], val[5], val[6]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics ClickPad"))
     {
        // 1 bool
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num == 1) &&
            (e_config->touch_clickpad) != (val[0]))
          {
             val[0] = e_config->touch_clickpad;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Edge Scrolling"))
     {
        // 3 bool - v, h, corner
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num >= 2) &&
            (((e_config->touch_scrolling_edge) &&
              ((!val[0]) || (val[1] != e_config->touch_scrolling_horiz))) ||
             ((!e_config->touch_scrolling_edge) &&
              ((val[0]) || (val[1])))))
          {
             if (e_config->touch_scrolling_edge)
               {
                  val[0] = 1;
                  val[1] = e_config->touch_scrolling_horiz;
               }
             else
               {
                  val[0] = 0;
                  val[1] = 0;
               }
             printf("DEV: change [%s] [%s] -> %i %i\n", dev, prop, val[0], val[1]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Two-Finger Scrolling"))
     {
        // 2 bool - h, v
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num >= 2) &&
            (((e_config->touch_scrolling_2finger) &&
              ((!val[0]) || (val[1] != e_config->touch_scrolling_horiz))) ||
             ((!e_config->touch_scrolling_2finger) &&
              ((val[0]) || (val[1])))))
          {
             if (e_config->touch_scrolling_2finger)
               {
                  val[0] = 1;
                  val[1] = e_config->touch_scrolling_horiz;
               }
             else
               {
                  val[0] = 0;
                  val[1] = 0;
               }
             printf("DEV: change [%s] [%s] -> %i %i\n", dev, prop, val[0], val[1]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Circular Scrolling"))
     {
        // 1 bool
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num == 1) &&
            (e_config->touch_scrolling_circular != val[0]))
          {
             val[0] = e_config->touch_scrolling_circular;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Palm Detection"))
     {
        // 1 bool - always turn on
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num == 1) &&
            (e_config->touch_palm_detect) != (val[0]))
          {
             val[0] = e_config->touch_palm_detect;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Synaptics Scrolling Distance"))
     {
        // 2 ints v, h - invert them for natural (negative)
        int *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 32) && (num == 2) &&
            (((e_config->touch_natural_scroll && ((val[0] > 0) || (val[1] > 0)))) ||
             ((!e_config->touch_natural_scroll && ((val[0] < 0) || (val[1] < 0))))))
          {
             if (e_config->mouse_natural_scroll)
               {
                  if (val[0] > 0) val[0] = -val[0];
                  if (val[1] > 0) val[1] = -val[1];
               }
             else
               {
                  if (val[0] < 0) val[0] = -val[0];
                  if (val[1] < 0) val[1] = -val[1];
               }
             printf("DEV: change [%s] [%s] -> %i %i\n", dev, prop, val[0], val[1]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
//   else if (!strcmp(prop, "Synaptics Off"))
//     {
//        // 8 bit 0 = on, 1 = off (except physical clicks), 2 = 
//     }

   ///////////////////////////////////////////////////////////////////////////
   // evdev devices
   else if (!strcmp(prop, "Evdev Middle Button Emulation"))
     {
        unsigned char *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 8) && (num == 1) &&
            (e_config->mouse_emulate_middle_button) != (val[0]))
          {
             val[0] = e_config->mouse_emulate_middle_button;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
   else if (!strcmp(prop, "Evdev Middle Button Timeout"))
     {
        unsigned short *val = ecore_x_input_device_property_get
          (dev_slot, prop, &num, &fmt, &size);
        if ((val) && (size == 16) && (num == 1) &&
            (e_config->mouse_emulate_middle_button && (val[0] != 50)))
          {
             val[0] = 50;
             printf("DEV: change [%s] [%s] -> %i\n", dev, prop, val[0]);
             ecore_x_input_device_property_set
               (dev_slot, prop, val, num, fmt, size);
          }
        free(val);
     }
}

E_API void
e_comp_x_devices_config_apply(Eina_Bool force)
{
   int num_devs, i;
   Eina_Bool driver_evdev = EINA_FALSE;
   Eina_Bool driver_libinput = EINA_FALSE;
   Eina_Bool driver_synaptics = EINA_FALSE;
   Eina_Strbuf *sbuf;
   Eina_Bool changed = EINA_TRUE;

   num_devs = ecore_x_input_device_num_get();
   sbuf = eina_strbuf_new();
   if (!sbuf) return;
   for (i = 0; i < num_devs; i++)
     {
        eina_strbuf_append(sbuf, ecore_x_input_device_name_get(i));
     }
   if ((devstring) && (!strcmp(devstring, eina_strbuf_string_get(sbuf))))
     changed = EINA_FALSE;
   else
     {
        free(devstring);
        devstring = eina_strbuf_string_steal(sbuf);
     }
   eina_strbuf_free(sbuf);
   changed |= force;
   printf("DEV: CHANGES ... have %i devices, changed=%i\n", num_devs, changed);
   if (!changed) return;
   for (i = 0; i < num_devs; i++)
     {
        const char *name;
        char **props;
        int num_props, j;
        Device_Flags dev_flags = 0;

        name = ecore_x_input_device_name_get(i);
//        printf("DEV: DEV=%i: [%s]\n", i, name);
        props = ecore_x_input_device_properties_list(i, &num_props);
        if (props)
          {
             int num, size;
             Ecore_X_Atom fmt;
             unsigned char *val;

             // figure out device flags - for now is it a mouse or touchpad
             val = ecore_x_input_device_property_get
               (i, "libinput Scroll Methods Available", &num, &fmt, &size);
             if ((val) && (size == 8) && (num >= 3))
               {
                  if ((!val[2]) && (val[0] || val[1]))
                    {
                       dev_flags = DEVICE_FLAG_TOUCHPAD;
                    }
               }
             if (!val)
               {
                  val = ecore_x_input_device_property_get
                    (i, "Synaptics Move Speed", &num, &fmt, &size);
                  if ((val) && (size == 32) && (num == 4))
                    {
                       dev_flags = DEVICE_FLAG_TOUCHPAD;
                    }
               }
             free(val);

             for (j = 0; j < num_props; j++)
               {
//                  printf("DEV:   PROP=%i: [%s]\n", j, props[j]);
                  if ((!driver_evdev) && (!strncmp(props[j], "Evdev ", 6)))
                    driver_evdev = EINA_TRUE;
                  else if ((!driver_libinput) && (!strncmp(props[j], "libinput ", 9)))
                    driver_libinput = EINA_TRUE;
                  else if ((!driver_synaptics) && (!strncmp(props[j], "Synaptics ", 10)))
                    driver_synaptics = EINA_TRUE;
                  _handle_dev_prop(i, name, props[j], dev_flags);
               }
             ecore_x_input_device_properties_free(props, num_props);
          }
     }

   // generic accel - if synatics or evdev props found?
     {
        int accel_numerator, accel_denominator;

        accel_numerator = 20 + (e_config->mouse_accel * 20.0);
        accel_denominator = 10;
        ecore_x_pointer_control_set(accel_numerator, accel_denominator,
                                    e_config->mouse_accel_threshold);
     }
   // button mapping - for mouse hand and natural scroll
     {
        unsigned char map[256] = { 0 };
        int n;

        if (ecore_x_pointer_mapping_get(map, 256))
          {
             for (n = 0; n < 256; n++)
               {
                  if (!map[n]) break;
               }
             if (n < 3)
               {
                  map[0]  = 1;
                  map[1]  = 2;
                  map[2]  = 3;
                  n = 3;
               }
             if (e_config->mouse_hand == E_MOUSE_HAND_RIGHT)
               {
                  map[0] = 1;
                  map[2] = 3;
               }
             else if (e_config->mouse_hand == E_MOUSE_HAND_LEFT)
               {
                  map[0] = 3;
                  map[2] = 1;
               }
             // if we are not on libinput or synaptics drivers anywehre then
             // swap buttons the old fashioned way
             if ((n >= 5) && (!driver_libinput) && (!driver_synaptics))
               {
                  if (e_config->mouse_natural_scroll)
                    {
                       map[3] = 5;
                       map[4] = 4;
                    }
                  else
                    {
                       map[3] = 4;
                       map[4] = 5;
                    }
               }
             ecore_x_pointer_mapping_set(map, n);
          }
     }
}
#include "e.h"
#include <Ecore_X.h>


#define RANDR_VERSION_1_1 ((1 << 16) | 1)
#define RANDR_VERSION_1_2 ((1 << 16) | 2)
#define RANDR_VERSION_1_3 ((1 << 16) | 3)
#define RANDR_VERSION_1_4 ((1 << 16) | 4)

static char *_output_screen_get(Ecore_X_Window root, Ecore_X_Randr_Output o);
static Ecore_X_Randr_Edid_Display_Interface_Type _output_conn_type_get(Ecore_X_Window root, Ecore_X_Randr_Output o);
static char *_output_name_get(Ecore_X_Window root, Ecore_X_Randr_Output o);
static Eina_Bool _is_lid_name(const char *name);
static Eina_Bool _cb_screen_change(void *data, int type, void *event);
static Eina_Bool _cb_crtc_change(void *data, int type, void *event);
static Eina_Bool _cb_output_change(void *data, int type, void *event);
static Eina_Bool _output_name_find(Ecore_X_Window root, const char *name, Ecore_X_Randr_Output *outputs, int outputs_num, Ecore_X_Randr_Output *out_ret);
static Eina_Bool _output_exists(Ecore_X_Randr_Output out, Ecore_X_Randr_Crtc_Info *info);
static Eina_Bool _rotation_exists(int rot, Ecore_X_Randr_Crtc_Info *info);
static Ecore_X_Randr_Mode _mode_screen_find(Ecore_X_Window root, E_Randr2_Screen *s, Ecore_X_Randr_Output out);

static Eina_List *handlers;

E_Comp_Screen_Iface xiface =
{
   .available = e_comp_x_randr_available,
   .init = e_comp_x_randr_init,
   .shutdown = e_comp_x_randr_shutdown,
   .create = e_comp_x_randr_create,
   .apply = e_comp_x_randr_config_apply,
   .relative_motion = EINA_FALSE,
   .backlight_enabled = EINA_TRUE,
};

static void
_e_comp_x_randr_pre_swap(void *data EINA_UNUSED, Evas *e EINA_UNUSED)
{
   if (!e_comp_config_get()->grab) return;
   if (!e_comp->grabbed) return;
   ecore_x_ungrab();
   e_comp->grabbed = 0;
}

static void
_e_comp_x_randr_grab_cb(void)
{
   if (!e_comp->grabbed)
     {
        ecore_x_grab();
        ecore_x_sync();
     }
   else
     ecore_x_ungrab();
}

static char *
_output_screen_get(Ecore_X_Window root, Ecore_X_Randr_Output o)
{
   // get the name of the screen - likely a model name or manufacturer name
   char *name;
   unsigned long len = 0;
   unsigned char *edid = ecore_x_randr_output_edid_get(root, o, &len);
   if (!edid) return NULL;
   name = ecore_x_randr_edid_display_name_get(edid, len);
   free(edid);
   if (name)
     {
        char *name2 = strdup(name);
        free(name);
        return name2;
     }
   return NULL;
}

static Ecore_X_Randr_Edid_Display_Interface_Type
_output_conn_type_get(Ecore_X_Window root, Ecore_X_Randr_Output o)
{
   // get what kind of connector (hdmi, dvi, displayport etc.) - vga is
   Ecore_X_Randr_Edid_Display_Interface_Type type;
   unsigned long len = 0;
   unsigned char *edid = ecore_x_randr_output_edid_get(root, o, &len);
   if (!edid) return ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_UNDEFINED;
   type = ecore_x_randr_edid_display_interface_type_get(edid, len);
   free(edid);
   return type;
}

static char *
_output_name_get(Ecore_X_Window root, Ecore_X_Randr_Output o)
{
   // get the output name - like connector (hdmi-0, dp1, dvi-0-1 etc.)
   char *name = ecore_x_randr_output_name_get(root, o, NULL);
   if (name)
     {
        char *name2 = strdup(name);
        free(name);
        return name2;
     }
   return _output_screen_get(root, o);
}

static Eina_Bool
_is_lid_name(const char *name)
{
   // a fixed list of possible built in connector names - likely a laptop
   // or device internal display
   if (!name) return EINA_FALSE;
   if      (strstr(name, "LVDS")) return EINA_TRUE;
   else if (strstr(name, "lvds")) return EINA_TRUE;
   else if (strstr(name, "Lvds")) return EINA_TRUE;
   else if (strstr(name, "LCD"))  return EINA_TRUE;
   else if (strstr(name, "eDP"))  return EINA_TRUE;
   else if (strstr(name, "edp"))  return EINA_TRUE;
   else if (strstr(name, "EDP"))  return EINA_TRUE;
   else if (strstr(name, "DSI"))  return EINA_TRUE;
   else if (strstr(name, "dsi"))  return EINA_TRUE;
   return EINA_FALSE;
}

static char *
_edid_string_get(Ecore_X_Window root, Ecore_X_Randr_Output o)
{
   // convert the edid binary data into a hex string so we can use it as
   // part of a name
   unsigned char *edid = NULL;
   unsigned long len = 0;
   char *edid_str = NULL;

   edid = ecore_x_randr_output_edid_get(root, o, &len);
   if (edid)
     {
        unsigned int k, kk;

        edid_str = malloc((len * 2) + 1);
        if (edid_str)
          {
             const char *hexch = "0123456789abcdef";

             for (kk = 0, k = 0; k < len; k++)
               {
                  edid_str[kk    ] = hexch[(edid[k] >> 4) & 0xf];
                  edid_str[kk + 1] = hexch[ edid[k]       & 0xf];
                  kk += 2;
               }
             edid_str[kk] = 0;
          }
        free(edid);
     }
   return edid_str;
}

static E_Randr2_Screen *
_info_unconf_primary_find(E_Randr2 *r)
{
   Eina_List *l;
   E_Randr2_Screen *s, *s_primary = NULL;
   int priority = 0;

   EINA_LIST_FOREACH(r->screens, l, s)
     {
        if (!((s->config.enabled) &&
              (s->config.mode.w > 0) && (s->config.mode.h > 0) &&
              (s->config.geom.w > 0) && (s->config.geom.h > 0)))
          continue;
        if (s->config.priority > priority)
          {
             s_primary = s;
             priority = s->config.priority;
          }
     }
   return s_primary;
}

static E_Randr2_Screen *
_info_unconf_left_find(E_Randr2 *r)
{
   Eina_List *l;
   E_Randr2_Screen *s, *s_left = NULL;
   int left_x = 0x7fffffff;
   int left_size = 0;

   EINA_LIST_FOREACH(r->screens, l, s)
     {
        if (!((s->config.enabled) &&
              (s->config.mode.w > 0) && (s->config.mode.h > 0) &&
              (s->config.geom.w > 0) && (s->config.geom.h > 0)))
          continue;
        if ((s->config.geom.x <= left_x) &&
            ((s->config.geom.w * s->config.geom.h) > left_size))
          {
             left_size = s->config.geom.w * s->config.geom.h;
             left_x = s->config.geom.x;
             s_left = s;
          }
     }
   return s_left;
}

static E_Randr2_Screen *
_info_unconf_closest_find(E_Randr2 *r, E_Randr2_Screen *s2, Eina_Bool configured)
{
   Eina_List *l;
   E_Randr2_Screen *s, *s_sel = NULL;
   int dist = 0x7fffffff;
   int dx, dy;

   EINA_LIST_FOREACH(r->screens, l, s)
     {
        if (s == s2) continue;
        if (!((s->config.enabled) &&
              (s->config.mode.w > 0) && (s->config.mode.h > 0) &&
              (s->config.geom.w > 0) && (s->config.geom.h > 0)))
          continue;
        if ((!configured) &&
            (s->config.relative.mode != E_RANDR2_RELATIVE_UNKNOWN))
          continue;
        else if ((configured) &&
                 (s->config.relative.mode == E_RANDR2_RELATIVE_UNKNOWN))
          continue;
        dx = (s->config.geom.x + (s->config.geom.w / 2)) -
          (s2->config.geom.x + (s2->config.geom.w / 2));
        dy = (s->config.geom.y + (s->config.geom.h / 2)) -
          (s2->config.geom.y + (s2->config.geom.h / 2));
        dx = sqrt((dx * dx) + (dy * dy));
        if (dx < dist)
          {
             s_sel = s;
             dist = dx;
          }
     }
   return s_sel;
}

static void
_info_relative_fixup(E_Randr2 *r)
{
   E_Randr2_Screen *s, *s2;
   int d, dx, dy;

   s = _info_unconf_primary_find(r);
   if (s) s->config.relative.mode = E_RANDR2_RELATIVE_NONE;
   else
     {
        s = _info_unconf_left_find(r);
        if (!s) return;
        s->config.relative.mode = E_RANDR2_RELATIVE_NONE;
     }
   for (;;)
     {
        // find the next screen that is closest to the last one we configured
        /// that is still not configured yet
        s = _info_unconf_closest_find(r, s, EINA_FALSE);
        if (!s) break;
        s2 = _info_unconf_closest_find(r, s, EINA_TRUE);
        // fix up s->config.relative.mode, s->config.relative.to and
        // s->config.relative.align to match (as closely as possible)
        // the geometry given - config s relative to s2
        if (!s2) s->config.relative.mode = E_RANDR2_RELATIVE_NONE;
        else
          {
             s->config.relative.to = strdup(s2->id);
             s->config.relative.align = 0.0;
             s->config.relative.mode = E_RANDR2_RELATIVE_NONE;
             if ((s->config.geom.x + s->config.geom.w) <=
                 s2->config.geom.x)
               {
                  s->config.relative.mode = E_RANDR2_RELATIVE_TO_LEFT;
                  d = s->config.geom.h - s2->config.geom.h;
                  dy = s2->config.geom.y - s->config.geom.y;
                  if (d != 0)
                    s->config.relative.align = ((double)dy) / ((double)d);
               }
             else if (s->config.geom.x >=
                      (s2->config.geom.x + s2->config.geom.w))
               {
                  s->config.relative.mode = E_RANDR2_RELATIVE_TO_RIGHT;
                  d = s->config.geom.h - s2->config.geom.h;
                  dy = s2->config.geom.y - s->config.geom.y;
                  if (d != 0)
                    s->config.relative.align = ((double)dy) / ((double)d);
               }
             else if ((s->config.geom.y + s->config.geom.h) <=
                      s2->config.geom.y)
               {
                  s->config.relative.mode = E_RANDR2_RELATIVE_TO_ABOVE;
                  d = s->config.geom.w - s2->config.geom.w;
                  dx = s2->config.geom.x - s->config.geom.x;
                  if (d != 0)
                    s->config.relative.align = ((double)dx) / ((double)d);
               }
             else if (s->config.geom.y >=
                      (s2->config.geom.y + s2->config.geom.h))
               {
                  s->config.relative.mode = E_RANDR2_RELATIVE_TO_BELOW;
                  d = s->config.geom.w - s2->config.geom.w;
                  dx = s2->config.geom.x - s->config.geom.x;
                  if (d != 0)
                    s->config.relative.align = ((double)dx) / ((double)d);
               }
             else if ((s->config.geom.x == s2->config.geom.x) &&
                      (s->config.geom.y == s2->config.geom.y) &&
                      (s->config.geom.w == s2->config.geom.w) &&
                      (s->config.geom.h == s2->config.geom.h))
               {
                  s->config.relative.mode = E_RANDR2_RELATIVE_CLONE;
               }
             if (s->config.relative.align < 0.0)
               s->config.relative.align = 0.0;
             else if (s->config.relative.align > 1.0)
               s->config.relative.align = 1.0;
          }
     }
}

static Eina_Bool
_cb_screen_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Screen_Change *ev = event;
   printf("RRR: CB screen change...\n");
   ecore_x_randr_config_timestamp_get(ev->root);
   ecore_x_randr_screen_current_size_get(ev->root, NULL, NULL, NULL, NULL);
   ecore_x_sync();
   if (!e_randr2_cfg->ignore_hotplug_events)
     e_randr2_screen_refresh_queue(EINA_TRUE);
   return EINA_TRUE;
}

static Eina_Bool
_cb_crtc_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Randr_Crtc_Change *ev = event;
   printf("RRR: CB crtc change...\n");
   ecore_x_randr_config_timestamp_get(ev->win);
   ecore_x_randr_screen_current_size_get(ev->win, NULL, NULL, NULL, NULL);
   ecore_x_sync();
   if (!e_randr2_cfg->ignore_hotplug_events)
     e_randr2_screen_refresh_queue(EINA_FALSE);
   return EINA_TRUE;
}

static Eina_Bool
_cb_output_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Randr_Output_Change *ev = event;
   printf("RRR: CB output change...\n");
   ecore_x_randr_config_timestamp_get(ev->win);
   ecore_x_randr_screen_current_size_get(ev->win, NULL, NULL, NULL, NULL);
   ecore_x_sync();
   if (!e_randr2_cfg->ignore_hotplug_events)
     e_randr2_screen_refresh_queue(EINA_TRUE);
   return EINA_TRUE;
}

static Eina_Bool
_output_name_find(Ecore_X_Window root, const char *name, Ecore_X_Randr_Output *outputs, int outputs_num, Ecore_X_Randr_Output *out_ret)
{
   int i;
   char *n;

   for (i = 0; i < outputs_num; i++)
     {
        n = _output_name_get(root, outputs[i]);
        if ((n) && (!strcmp(n, name)))
          {
             free(n);
             *out_ret = outputs[i];
             return EINA_TRUE;
          }
        free(n);
     }
   return EINA_FALSE;
}

static Eina_Bool
_output_exists(Ecore_X_Randr_Output out, Ecore_X_Randr_Crtc_Info *info)
{
   int i;

   for (i = 0; i < info->npossible; i++)
     {
        if (out == info->possible[i]) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static Eina_Bool
_rotation_exists(int rot, Ecore_X_Randr_Crtc_Info *info)
{
   if ((rot == 0) && (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_0))
     return EINA_TRUE;
   if ((rot == 90) && (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_90))
     return EINA_TRUE;
   if ((rot == 180) && (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_180))
     return EINA_TRUE;
   if ((rot == 270) && (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_270))
     return EINA_TRUE;
   return EINA_FALSE;
}

static Ecore_X_Randr_Mode
_mode_screen_find(Ecore_X_Window root, E_Randr2_Screen *s, Ecore_X_Randr_Output out)
{
   Ecore_X_Randr_Mode_Info *minfo;
   Ecore_X_Randr_Mode mode = 0, *modes;
   int modes_num = 0, modes_pref = 0, distance = 0x7fffffff;
   int diff, i;
   double refresh;

   modes = ecore_x_randr_output_modes_get(root, out, &modes_num, &modes_pref);
   if (!modes) printf("RRR: modes for '%s' FETCH FAILED!!!\n", s->info.name);
   printf("RRR: modes for '%s' are %p [%i]\n", s->info.name, modes, modes_num);
   if (modes)
     {
        for (i = 0; i < modes_num; i++)
          {
             minfo = ecore_x_randr_mode_info_get(root, modes[i]);
             if (!minfo) continue;
             refresh = (double)minfo->dotClock /
               (double)(minfo->hTotal * minfo->vTotal);
             diff =
               (100 * abs(s->config.mode.w - (int)minfo->width)) +
               (100 * abs(s->config.mode.h - (int)minfo->height)) +
               fabs((100 * s->config.mode.refresh) - (100 * refresh));
             if (diff < distance)
               {
                  mode = modes[i];
                  distance = diff;
               }
             ecore_x_randr_mode_info_free(minfo);
          }
        free(modes);
     }
   return mode;
}

static E_Dialog *screen_error_dialog = NULL;

static void
_screen_error_remove_cb(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   screen_error_dialog = NULL;
   e_object_del(E_OBJECT(dia));
}

static Eina_Bool
_cb_no_outputs_timer(void *data EINA_UNUSED)
{
   if (!screen_error_dialog)
     {
        screen_error_dialog = e_dialog_new(NULL, "E", "_error_dialog");

        e_dialog_title_set(screen_error_dialog, _("Screen setup Error"));
        e_dialog_text_set(screen_error_dialog, _("You seem to have no screens configured to<br>"
                                 "be on given the outputs you have. This should<br>"
                                 "be fixed by going to:<br>"
                                 "<br>"
                                 "<b>Settings -> Screen -> Screen Setup</><br>"
                                 "<br>"
                                 "And configure at least one screen to be on."));
       e_dialog_icon_set(screen_error_dialog, "dialog-error", 64);
       e_dialog_button_add(screen_error_dialog, _("OK"), NULL, _screen_error_remove_cb, NULL);
       e_dialog_button_focus_num(screen_error_dialog, 0);
       elm_win_center(screen_error_dialog->win, 1, 1);
       e_dialog_show(screen_error_dialog);
     }
   else
     {
       elm_win_raise(screen_error_dialog->win);
     }

   return EINA_FALSE;
}

E_API void
e_comp_x_randr_init(void)
{
   // add handler for randr screen change events
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_SCREEN_CHANGE,
                         _cb_screen_change, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_RANDR_CRTC_CHANGE,
                         _cb_crtc_change, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_X_EVENT_RANDR_OUTPUT_CHANGE,
                         _cb_output_change, NULL);
   // if it's 1.2 or better then we can select for these events
   if (ecore_x_randr_version_get() >= RANDR_VERSION_1_2)
     {
        Ecore_X_Window root = ecore_x_window_root_first_get();
        ecore_x_randr_events_select(root, EINA_TRUE);
     }
}

E_API void
e_comp_x_randr_shutdown(void)
{
   // clear up event listening
   if (ecore_x_randr_version_get() >= RANDR_VERSION_1_2)
     {
        Ecore_X_Window root = ecore_x_window_root_first_get();
        ecore_x_randr_events_select(root, EINA_FALSE);
     }
   E_FREE_LIST(handlers, ecore_event_handler_del);
}

E_API void
e_comp_x_randr_config_apply(void)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   Ecore_X_Window root = ecore_x_window_root_first_get();
   int minw, minh, maxw, maxh, nw, nh, pw, ph, ww, hh;
   Ecore_X_Randr_Crtc *crtcs = NULL;
   Ecore_X_Randr_Output *outputs = NULL, out, *outconf;
   E_Randr2_Screen **screenconf;
   int crtcs_num = 0, outputs_num = 0, i, numout;
   Ecore_X_Randr_Crtc_Info *info;
   int top_priority = 0;
   int px, py;
   Eina_Bool fix_pointer = EINA_TRUE;

   ecore_x_grab();
   // set virtual resolution
   nw = e_randr2->w;
   nh = e_randr2->h;
   ecore_x_randr_screen_size_range_get(root, &minw, &minh, &maxw, &maxh);
   ecore_x_randr_screen_current_size_get(root, &pw, &ph, NULL, NULL);
     {
        int dww = 0, dhh = 0, dww2 = 0, dhh2 = 0;
        ecore_x_randr_screen_current_size_get(root, &dww, &dhh, &dww2, &dhh2);
        printf("RRR: cur size: %ix%i\n", dww, dhh);
     }
   printf("RRR: size range: %ix%i -> %ix%i\n", minw, minh, maxw, maxh);
   if (nw > maxw) nw = maxw;
   if (nh > maxh) nh = maxh;
   if (nw < minw) nw = minw;
   if (nh < minh) nh = minh;
   ww = nw; if (nw < pw) ww = pw;
   hh = nh; if (nh < ph) hh = ph;

   ecore_x_pointer_xy_get(root, &px, &py);
   ecore_x_randr_screen_current_size_set(root, ww, hh, -1, -1);
     {
        int dww = 0, dhh = 0, dww2 = 0, dhh2 = 0;
        ecore_x_randr_screen_current_size_get(root, &dww, &dhh, &dww2, &dhh2);
        printf("RRR: cur size: %ix%i\n", dww, dhh);
     }
   printf("RRR: set vsize: %ix%i\n", nw, nh);

   crtcs = ecore_x_randr_crtcs_get(root, &crtcs_num);
   outputs = ecore_x_randr_outputs_get(root, &outputs_num);

   if ((crtcs) && (outputs))
     {
        outconf = alloca(crtcs_num * sizeof(Ecore_X_Randr_Output));
        screenconf = alloca(crtcs_num * sizeof(E_Randr2_Screen *));
        memset(outconf, 0, crtcs_num * sizeof(Ecore_X_Randr_Output));
        memset(screenconf, 0, crtcs_num * sizeof(E_Randr2_Screen *));

        // decide which outputs get which crtcs
        EINA_LIST_FOREACH(e_randr2->screens, l, s)
          {
             printf("RRR: find output for '%s'\n", s->info.name);
             // XXX: find clones and set them as outputs in an array
             if ((s->config.configured) &&
                 (_output_name_find(root, s->info.name, outputs,
                                    outputs_num, &out)))
               {
                  printf("RRR:   enabled: %i\n", s->config.enabled);
                  if (s->config.enabled)
                    {
                       if (s->config.priority > top_priority)
                         top_priority = s->config.priority;
                       for (i = 0; i < crtcs_num; i++)
                         {
                            if (!outconf[i])
                              {
                                 printf("RRR:     crtc slot empty: %i\n", i);
                                 info = ecore_x_randr_crtc_info_get(root,
                                                                    crtcs[i]);
                                 if (info)
                                   {
                                      if (_output_exists(out, info) &&
                                          _rotation_exists(s->config.rotation,
                                                           info))
                                        {
                                           printf("RRR:       assign slot out: %x\n", out);
                                           outconf[i] = out;
                                           screenconf[i] = s;
                                           ecore_x_randr_crtc_info_free(info);
                                           break;
                                        }
                                      ecore_x_randr_crtc_info_free(info);
                                   }
                              }
                         }
                    }
               }
          }
        numout = 0;
        for (i = 0; i < crtcs_num; i++)
          {
             if (outconf[i]) numout++;
          }
        if (numout)
          {
             Ecore_X_Rectangle *scrs = alloca(crtcs_num * sizeof(Ecore_X_Rectangle));
             int scrs_num;

             scrs_num = 0;
             // set up a crtc to drive each output (or not)
             for (i = 0; i < crtcs_num; i++)
               {
                  // XXX: find clones and set them as outputs in an array
                  if (outconf[i])
                    {
                       Ecore_X_Randr_Orientation orient =
                         ECORE_X_RANDR_ORIENTATION_ROT_0;
                       Ecore_X_Randr_Mode mode;

                       mode = _mode_screen_find(root, screenconf[i], outconf[i]);
                       if (screenconf[i]->config.rotation == 0)
                         orient = ECORE_X_RANDR_ORIENTATION_ROT_0;
                       else if (screenconf[i]->config.rotation == 90)
                         orient = ECORE_X_RANDR_ORIENTATION_ROT_90;
                       else if (screenconf[i]->config.rotation == 180)
                         orient = ECORE_X_RANDR_ORIENTATION_ROT_180;
                       else if (screenconf[i]->config.rotation == 270)
                         orient = ECORE_X_RANDR_ORIENTATION_ROT_270;
                       printf("RRR: crtc on: %i = '%s'     @ %i %i    - %ix%i orient %i mode %x out %x\n",
                              i, screenconf[i]->info.name,
                              screenconf[i]->config.geom.x,
                              screenconf[i]->config.geom.y,
                              screenconf[i]->config.geom.w,
                              screenconf[i]->config.geom.h,
                              orient, mode, outconf[i]);
                       if (!ecore_x_randr_crtc_settings_set
                             (root, crtcs[i], &(outconf[i]), 1,
                              screenconf[i]->config.geom.x,
                              screenconf[i]->config.geom.y,
                              mode, orient))
                       printf("RRR:   failed to set crtc!!!!!!\n");
                       if (E_INSIDE(px, py,
                                    screenconf[i]->config.geom.x,
                                    screenconf[i]->config.geom.y,
                                    screenconf[i]->config.geom.w,
                                    screenconf[i]->config.geom.h))
                         fix_pointer = EINA_FALSE;
                       px = screenconf[i]->config.geom.x +
                         (screenconf[i]->config.geom.w / 2);
                       py = screenconf[i]->config.geom.y +
                         (screenconf[i]->config.geom.h / 2);
                       ecore_x_randr_crtc_panning_area_set
                         (root, crtcs[i],
                          screenconf[i]->config.geom.x,
                          screenconf[i]->config.geom.y,
                          screenconf[i]->config.geom.w,
                          screenconf[i]->config.geom.h);
                       if (screenconf[i]->config.priority == top_priority)
                         {
                            ecore_x_randr_primary_output_set(root, outconf[i]);
                            top_priority = -1;
                         }
                       scrs[scrs_num].x      = screenconf[i]->config.geom.x;
                       scrs[scrs_num].y      = screenconf[i]->config.geom.y;
                       scrs[scrs_num].width  = screenconf[i]->config.geom.w;
                       scrs[scrs_num].height = screenconf[i]->config.geom.h;
                       scrs_num++;
                    }
                  else
                    {
                       printf("RRR: crtc off: %i\n", i);
                       ecore_x_randr_crtc_settings_set
                         (root, crtcs[i], NULL, 0, 0, 0, 0,
                          ECORE_X_RANDR_ORIENTATION_ROT_0);
                    }
               }
             ecore_x_root_screen_barriers_set(scrs, scrs_num);
          }
        else
          {
             printf("RRR: EERRRRRROOOORRRRRRR no outputs to configure!\n");
             ecore_timer_add(5.0, _cb_no_outputs_timer, NULL);
             ecore_x_root_screen_barriers_set(NULL, 0);
          }
     }
   if (fix_pointer)
     {
        ecore_x_pointer_warp(root, px, py);
     }
   free(outputs);
   free(crtcs);

   printf("RRR: set vsize: %ix%i\n", nw, nh);
   ecore_x_randr_screen_current_size_set(root, nw, nh, -1, -1);
     {
        int dww = 0, dhh = 0, dww2 = 0, dhh2 = 0;
        ecore_x_randr_screen_current_size_get(root, &dww, &dhh, &dww2, &dhh2);
        printf("RRR: cur size: %ix%i\n", dww, dhh);
//        ecore_x_randr_screen_reset(root);
//        ecore_x_randr_screen_current_size_set(root, nw, nh, -1, -1);
//        ecore_x_sync();
//        ecore_x_randr_screen_current_size_get(root, &dww, &dhh, &dww2, &dhh2);
//        printf("RRR: cur size: %ix%i\n", dww,d hh);
     }
   ecore_x_randr_screen_size_range_get(root, NULL, NULL, NULL, NULL);
   ecore_x_ungrab();
   ecore_x_sync();

   // ignore the next batch of randr events - we caused them ourselves
   // XXX: a problem. thew first time we configure the screen we may not
   // get any events back to clear the ignore flag below, so only apply
   // here if the randr config now doesn't match what we want to set up.
//   event_ignore = EINA_TRUE;
}

E_API Eina_Bool
e_comp_x_randr_available(void)
{
   // is randr extn there?
   return ecore_x_randr_query();
}

E_API E_Randr2 *
e_comp_x_randr_create(void)
{
   Ecore_X_Randr_Crtc *crtcs = NULL;
   Ecore_X_Randr_Output *outputs = NULL;
   E_Zone *zone;
   int crtcs_num = 0, outputs_num = 0, i, j, k;
   Ecore_X_Window root = ecore_x_window_root_first_get();
   E_Randr2 *r = calloc(1, sizeof(E_Randr2));
   if (!r) return NULL;

   printf("RRR: ................. info get!\n");
   // do this to force xrandr to update its content
   ecore_x_randr_screen_refresh(root);
   ecore_x_randr_config_timestamp_get(root);
   ecore_x_randr_screen_size_range_get(root, NULL, NULL, NULL, NULL);
   ecore_x_sync();

   crtcs = ecore_x_randr_crtcs_get(root, &crtcs_num);
   outputs = ecore_x_randr_outputs_get(root, &outputs_num);

   for (i = 0; i < outputs_num; i++)
     {
        Ecore_X_Randr_Mode *modes;
        Ecore_X_Randr_Edid_Display_Interface_Type conn;
        int modes_num = 0, modes_pref = 0, priority;
        E_Config_Randr2_Screen *cs;
        E_Randr2_Screen *s = calloc(1, sizeof(E_Randr2_Screen));
        if (!s) continue;

        printf("RRR: NEW SCREEN ...\n");
        s->info.name = _output_name_get(root, outputs[i]);
        printf("RRR: .... out %s\n", s->info.name);
        if (!s->info.name)
          {
             free(s);
             continue;
          }
        s->info.screen = _output_screen_get(root, outputs[i]);
        s->info.edid = _edid_string_get(root, outputs[i]);
        if (s->info.edid)
        s->id = malloc(strlen(s->info.name) + 1 + strlen(s->info.edid) + 1);
        else
        s->id = malloc(strlen(s->info.name) + 1 + 1);
        if (!s->id)
          {
             free(s->info.screen);
             free(s->info.edid);
             free(s);
             continue;
          }
        strcpy(s->id, s->info.name);
        strcat(s->id, "/");
        if (s->info.edid) strcat(s->id, s->info.edid);
        conn = _output_conn_type_get(root, outputs[i]);
        if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_UNDEFINED)
          s->info.connector = E_RANDR2_CONNECTOR_UNDEFINED;
        else if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_DVI)
          s->info.connector = E_RANDR2_CONNECTOR_DVI;
        else if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_HDMI_A)
          s->info.connector = E_RANDR2_CONNECTOR_HDMI_A;
        else if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_HDMI_B)
          s->info.connector = E_RANDR2_CONNECTOR_HDMI_B;
        else if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_MDDI)
          s->info.connector = E_RANDR2_CONNECTOR_MDDI;
        else if (conn == ECORE_X_RANDR_EDID_DISPLAY_INTERFACE_DISPLAY_PORT)
          s->info.connector = E_RANDR2_CONNECTOR_DISPLAY_PORT;
        s->info.is_lid = _is_lid_name(s->info.name);
        s->info.lid_closed = s->info.is_lid && e_acpi_lid_is_closed();
        printf("RRR: ...... lid_closed = %i (%i && %i)\n", s->info.lid_closed, s->info.is_lid, e_acpi_lid_is_closed());
        if (ecore_x_randr_output_connection_status_get(root, outputs[i]) ==
            ECORE_X_RANDR_CONNECTION_STATUS_CONNECTED)
          s->info.connected = EINA_TRUE;
        printf("RRR: ...... connected %i\n", s->info.connected);
        if (ecore_x_randr_output_backlight_level_get(root, outputs[i]) >= 0.0)
          s->info.backlight = EINA_TRUE;
        ecore_x_randr_output_size_mm_get(root, outputs[i],
                                         &(s->info.size.w), &(s->info.size.h));
        modes = ecore_x_randr_output_modes_get(root, outputs[i],
                                               &modes_num, &modes_pref);
        printf("RRR: ...... modes %p\n", modes);
        if (modes)
          {
             for (j = 0; j < modes_num; j++)
               {
                  Ecore_X_Randr_Mode_Info *minfo =
                    ecore_x_randr_mode_info_get(root, modes[j]);
                  if (minfo)
                    {
                       E_Randr2_Mode *m = calloc(1, sizeof(E_Randr2_Mode));
                       if (m)
                         {
                            m->w = minfo->width;
                            m->h = minfo->height;
                            m->refresh =
                            (double)minfo->dotClock /
                            (double)(minfo->hTotal * minfo->vTotal);
                            m->flags = minfo->modeFlags;
                            if (j == (modes_pref - 1))
                              m->preferred = EINA_TRUE;
                            s->info.modes = eina_list_append(s->info.modes, m);
                         }
                       ecore_x_randr_mode_info_free(minfo);
                    }
               }
             free(modes);
             e_randr2_screen_modes_sort(s);
          }
        cs = NULL;
        priority = 0;
        if (e_randr2_cfg) cs = e_randr2_config_screen_find(s, e_randr2_cfg);
        if (cs)
          priority = cs->priority;
        else if (ecore_x_randr_primary_output_get(root) == outputs[i])
          priority = 100;
        s->config.priority = priority;
        for (j = 0; j < crtcs_num; j++)
          {
             Eina_Bool ok, possible;
             Ecore_X_Randr_Crtc_Info *info =
               ecore_x_randr_crtc_info_get(root, crtcs[j]);
             if (info)
               {
                  ok = EINA_FALSE;
                  possible = EINA_FALSE;
                  for (k = 0; k < info->noutput; k++)
                    {
                       if (info->outputs[k] == outputs[i])
                         {
                            ok = EINA_TRUE;
                            break;
                         }
                    }
                  if (!ok)
                    {
                       for (k = 0; k < info->npossible; k++)
                         {
                            if (info->possible[k] == outputs[i])
                              {
                                 ok = EINA_TRUE;
                                 possible = EINA_TRUE;
                                 break;
                              }
                         }
                    }
                  if (ok)
                    {
                       if (!possible)
                         {
                            Ecore_X_Randr_Mode_Info *minfo;

                            s->config.geom.x = info->x;
                            s->config.geom.y = info->y;
                            s->config.geom.w = info->width;
                            s->config.geom.h = info->height;
                            if (info->rotation & ECORE_X_RANDR_ORIENTATION_ROT_0)
                              s->config.rotation = 0;
                            else if (info->rotation & ECORE_X_RANDR_ORIENTATION_ROT_90)
                              s->config.rotation = 90;
                            else if (info->rotation & ECORE_X_RANDR_ORIENTATION_ROT_180)
                              s->config.rotation = 180;
                            else if (info->rotation & ECORE_X_RANDR_ORIENTATION_ROT_270)
                              s->config.rotation = 270;
                            minfo = ecore_x_randr_mode_info_get(root,
                                                                info->mode);
                            if (minfo)
                              {
                                 s->config.enabled = EINA_TRUE;
                                 s->config.mode.w = minfo->width;
                                 s->config.mode.h = minfo->height;
                                 s->config.mode.refresh =
                                   (double)minfo->dotClock /
                                   (double)(minfo->hTotal * minfo->vTotal);
                                 ecore_x_randr_mode_info_free(minfo);
                              }
                            printf("RRR: '%s' %i %i %ix%i\n",
                                   s->info.name,
                                   s->config.geom.x, s->config.geom.y,
                                   s->config.geom.w, s->config.geom.h);
                         }
                       if (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_0)
                         s->info.can_rot_0 = EINA_TRUE;
                       if (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_90)
                         s->info.can_rot_90 = EINA_TRUE;
                       if (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_180)
                         s->info.can_rot_180 = EINA_TRUE;
                       if (info->rotations & ECORE_X_RANDR_ORIENTATION_ROT_270)
                         s->info.can_rot_270 = EINA_TRUE;
                       if (cs)
                         {
                            if (cs->profile)
                              s->config.profile = strdup(cs->profile);
                            else
                              s->config.profile = NULL;
                            s->config.scale_multiplier = cs->scale_multiplier;
                         }
                    }
                  ecore_x_randr_crtc_info_free(info);
               }
          }
        zone = e_zone_for_id_get(s->id);
        if (zone && !zone->output) zone->output = s;
        r->screens = eina_list_append(r->screens, s);
     }

   free(outputs);
   free(crtcs);

   _info_relative_fixup(r);
   return r;
}

E_API void
e_comp_x_randr_screen_iface_set(void)
{
   if (e_comp->screen)
     CRI("CANNOT SET XIFACE; IFACE ALREADY EXISTS!");
   e_comp->screen = &xiface;
}

E_API Eina_Bool
e_comp_x_randr_canvas_new(Ecore_Window parent, int w, int h)
{
   Eina_Bool ret = EINA_TRUE;

   if (!getenv("ECORE_X_NO_XLIB"))
     {
        if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_OPENGL_X11))
          e_comp_gl_set(EINA_TRUE);
     }
   if (e_comp_gl_get() && (e_comp_config_get()->engine == E_COMP_ENGINE_GL))
     {
        int opt[20];
        int opt_i = 0;

        if (e_comp_config_get()->indirect)
          {
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_INDIRECT;
             opt_i++;
             opt[opt_i] = 1;
             opt_i++;
          }
        if (e_comp_config_get()->vsync)
          {
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_VSYNC;
             opt_i++;
             opt[opt_i] = 1;
             opt_i++;
          }
        if (e_comp_config_get()->swap_mode)
          {
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_SWAP_MODE;
             opt_i++;
             opt[opt_i] = e_comp_config_get()->swap_mode;
             opt_i++;
          }
        if (opt_i > 0)
          {
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_GL_DEPTH;
             opt_i++;
             opt[opt_i] = 24;
             opt_i++;
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_GL_STENCIL;
             opt_i++;
             opt[opt_i] = 8;
             opt_i++;
             opt[opt_i] = ECORE_EVAS_GL_X11_OPT_NONE;
             e_comp->ee = ecore_evas_gl_x11_options_new(NULL, parent, 0, 0, w, h, opt);
          }
        if (!e_comp->ee)
          e_comp->ee = ecore_evas_gl_x11_new(NULL, parent, 0, 0, w, h);
        if (e_comp->ee)
          {
             e_comp->gl = 1;
             ecore_evas_gl_x11_pre_post_swap_callback_set(e_comp->ee, e_comp, _e_comp_x_randr_pre_swap, NULL);
          }
     }
   if (!e_comp->ee)
     {
        e_comp->ee = ecore_evas_software_x11_new(NULL, parent, 0, 0, w, h);
        if (e_comp_config_get()->engine == E_COMP_ENGINE_GL)
          ret = EINA_FALSE;
        e_comp_gl_set(0);
        // tell elm and all elm apps to not allow acceleration since comp
        // can't do it (or doesn't want to), so this may cause issues in
        // gl drivers etc. - this addresses a vbox crash bug with vm
        // opengl acceleration
        elm_config_accel_preference_set("none");
        elm_config_accel_preference_override_set(EINA_TRUE);
        elm_config_all_flush();
        elm_config_save();
     }
   if (ret)
     e_comp->grab_cb = _e_comp_x_randr_grab_cb;
   return ret;
}
#include "e.h"

E_API E_Config *e_config = NULL;
E_API E_Config_Bindings *e_bindings = NULL;

static int _e_config_revisions = 9;

/* local subsystem functions */
static void      _e_config_save_cb(void *data);
static void      _e_config_free(E_Config *cfg);
static Eina_Bool _e_config_cb_timer(void *data);

/* local subsystem globals */
static int _e_config_save_block = 0;
static E_Powersave_Deferred_Action *_e_config_save_defer = NULL;
static const char *_e_config_profile = NULL;

static E_Config_DD *_e_config_edd = NULL;
static E_Config_DD *_e_config_binding_edd = NULL;
static E_Config_DD *_e_config_module_edd = NULL;
static E_Config_DD *_e_config_font_fallback_edd = NULL;
static E_Config_DD *_e_config_font_default_edd = NULL;
static E_Config_DD *_e_config_theme_edd = NULL;
static E_Config_DD *_e_config_bindings_mouse_edd = NULL;
static E_Config_DD *_e_config_bindings_key_edd = NULL;
static E_Config_DD *_e_config_bindings_edge_edd = NULL;
static E_Config_DD *_e_config_bindings_signal_edd = NULL;
static E_Config_DD *_e_config_bindings_wheel_edd = NULL;
static E_Config_DD *_e_config_bindings_acpi_edd = NULL;
static E_Config_DD *_e_config_bindings_swipe_edd = NULL;
static E_Config_DD *_e_config_path_append_edd = NULL;
static E_Config_DD *_e_config_desktop_bg_edd = NULL;
static E_Config_DD *_e_config_desklock_bg_edd = NULL;
static E_Config_DD *_e_config_desktop_name_edd = NULL;
static E_Config_DD *_e_config_menu_applications_edd = NULL;
static E_Config_DD *_e_config_color_class_edd = NULL;
static E_Config_DD *_e_config_gadcon_edd = NULL;
static E_Config_DD *_e_config_gadcon_client_edd = NULL;
static E_Config_DD *_e_config_shelf_edd = NULL;
static E_Config_DD *_e_config_shelf_desk_edd = NULL;
static E_Config_DD *_e_config_mime_icon_edd = NULL;
static E_Config_DD *_e_config_syscon_action_edd = NULL;
static E_Config_DD *_e_config_env_var_edd = NULL;
static E_Config_DD *_e_config_xkb_layout_edd = NULL;
static E_Config_DD *_e_config_xkb_option_edd = NULL;

E_API int E_EVENT_CONFIG_ICON_THEME = 0;
E_API int E_EVENT_CONFIG_MODE_CHANGED = 0;
E_API int E_EVENT_CONFIG_LOADED = 0;

static Eina_Lock  _e_config_pending_files_lock;
static Eina_Hash *_e_config_pending_files = NULL;
static Eina_Thread_Queue *_e_config_thread_thq = NULL;

static Eina_List *handlers = NULL;

typedef struct _E_Color_Class
{
   const char	 *name; /* stringshared name */
   int		  r, g, b, a;
   int		  r2, g2, b2, a2;
   int		  r3, g3, b3, a3;
} E_Color_Class;

typedef enum
{
   E_CONFIG_SAVE_THREAD_SAVE,
   E_CONFIG_SAVE_THREAD_QUIT
} E_Config_Save_Thread_Message_Type;

typedef struct _E_Config_Save_Thread_Message
{
   Eina_Thread_Queue_Msg head;
   E_Config_Save_Thread_Message_Type type;
   char *path, *destpath;
} E_Config_Save_Thread_Message;

static Eina_Bool
_e_config_cb_efreet_cache_update(void *data EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   if (e_config)
     {
        if (e_config->icon_theme)
          {
             if (!efreet_icon_theme_find(e_config->icon_theme))
               {
                  eina_stringshare_replace(&e_config->icon_theme, "hicolor");
                  e_config_save_queue();
               }
          }
     }
   return ECORE_CALLBACK_RENEW;
}


static const char *
_e_config_profile_name_get(Eet_File *ef)
{
   /* profile config exists */
   char *data;
   const char *s = NULL;
   int data_len = 0;

   data = eet_read(ef, "config", &data_len);
   if ((data) && (data_len > 0))
     {
        int ok = 1;

        for (s = data; s < (data + data_len); s++)
          {
             // if profile is not all ascii (valid printable ascii - no
             // control codes etc.) or it contains a '/' (invalid as its a
             // directory delimiter) - then it's invalid
             if ((*s < ' ') || (*s > '~') || (*s == '/'))
               {
                  ok = 0;
                  break;
               }
          }
        s = NULL;
        if (ok)
          s = eina_stringshare_add_length(data, data_len);
        free(data);
     }
   if (s)
     {
        char buf[PATH_MAX];
        char buf2[PATH_MAX];

        e_user_dir_snprintf(buf, sizeof(buf), "config/%s", s);
        if (!ecore_file_is_dir(buf))
          {
             snprintf(buf2, sizeof(buf2), "config/%s", s);
             e_prefix_data_concat_static(buf, buf2);
             if (!ecore_file_is_dir(buf))
               {
                  printf("CF: warning - profile [%s] dir does not exist in user or system dirs\n", s);
                  eina_stringshare_del(s);
                  s = NULL;
               }
          }
     }
   return s;
}

static Eet_File *
_e_config_pending_file_find(const char *path)
{
   Eet_File *ef, *ef2;

   eina_lock_take(&_e_config_pending_files_lock);
   ef = eina_hash_find(_e_config_pending_files, path);
   if (!ef)
     {
        eina_lock_release(&_e_config_pending_files_lock);
        // do this unlocked as it could block for a while
        ef = eet_open(path, EET_FILE_MODE_WRITE);
        eina_lock_take(&_e_config_pending_files_lock);
        if (ef)
          {
             // handle race due to the above unlock and lock - should be
             // super rare...
             ef2 = eina_hash_find(_e_config_pending_files, path);
             if (ef2)
               {
                  eina_hash_del(_e_config_pending_files, path, ef2);
                  eet_close(ef2);
               }
             eina_hash_add(_e_config_pending_files, path, ef);
          }
        else
          printf("CF: Error: file find %s - create new fail\n", path);
     }
   eina_lock_release(&_e_config_pending_files_lock);
   return ef;
}

static Eina_Bool
_e_config_pending_file_del(const char *path)
{
   Eet_File *ef;
   Eina_Bool ok = EINA_FALSE;

   eina_lock_take(&_e_config_pending_files_lock);
   ef = eina_hash_find(_e_config_pending_files, path);
   if (ef)
     {
        Eet_Error err;
        const char *erstr = "";

        eina_hash_del(_e_config_pending_files, path, ef);
        eina_lock_release(&_e_config_pending_files_lock);
        err = eet_sync_sync(ef);
        switch (err)
          {
           case EET_ERROR_NONE:
             ok = EINA_TRUE;
             /* all good - no error */
             break;
           case EET_ERROR_BAD_OBJECT:
             erstr = _("The EET file handle is bad.");
             break;
           case EET_ERROR_EMPTY:
             erstr = _("The file data is empty.");
             break;
           case EET_ERROR_NOT_WRITABLE:
             erstr = _("The file is not writable. Perhaps the disk is read-only<ps/>or you lost permissions to your files.");
             break;
           case EET_ERROR_OUT_OF_MEMORY:
             erstr = _("Memory ran out while preparing the write.<ps/>Please free up memory.");
             break;
           case EET_ERROR_WRITE_ERROR:
             erstr = _("This is a generic error.");
             break;
           case EET_ERROR_WRITE_ERROR_FILE_TOO_BIG:
             erstr = _("The settings file is too large.<ps/>It should be very small (a few hundred KB at most).");
             break;
           case EET_ERROR_WRITE_ERROR_IO_ERROR:
             erstr = _("You have I/O errors on the disk.<ps/>Maybe it needs replacing?");
             break;
           case EET_ERROR_WRITE_ERROR_OUT_OF_SPACE:
             erstr = _("You ran out of space while writing the file.");
             break;
           case EET_ERROR_WRITE_ERROR_FILE_CLOSED:
             erstr = _("The file was closed while writing.");
             break;
           case EET_ERROR_MMAP_FAILED:
             erstr = _("Memory-mapping (mmap) of the file failed.");
             break;
           case EET_ERROR_X509_ENCODING_FAILED:
             erstr = _("X509 Encoding failed.");
             break;
           case EET_ERROR_SIGNATURE_FAILED:
             erstr = _("Signature failed.");
             break;
           case EET_ERROR_INVALID_SIGNATURE:
             erstr = _("The signature was invalid.");
             break;
           case EET_ERROR_NOT_SIGNED:
             erstr = _("Not signed.");
             break;
           case EET_ERROR_NOT_IMPLEMENTED:
             erstr = _("Feature not implemented.");
             break;
           case EET_ERROR_PRNG_NOT_SEEDED:
             erstr = _("PRNG was not seeded.");
             break;
           case EET_ERROR_ENCRYPT_FAILED:
             erstr = _("Encryption failed.");
             break;
           case EET_ERROR_DECRYPT_FAILED:
             erstr = _("Decryption failed.");
             break;
           default: /* if we get here eet added errors we don't know */
             erstr = _("The error is unknown to Enlightenment.");
             break;
          }
        eet_close(ef);
        if (!ok) printf("CF: Write Error: %s\n", erstr);
     }
   else
     eina_lock_release(&_e_config_pending_files_lock);
   return ok;
}

static void
_e_config_save_thread_main(void *data EINA_UNUSED, Ecore_Thread *eth)
{
   E_Config_Save_Thread_Message *msg;
   void *ref;
   Eina_Bool run = EINA_TRUE;

   while (run)
     {
        msg = eina_thread_queue_wait(_e_config_thread_thq, &ref);
        switch (msg->type)
          {
           case E_CONFIG_SAVE_THREAD_SAVE:
             if (_e_config_pending_file_del(msg->path))
               {
                  Eina_Bool ret = EINA_TRUE;

                  if (_e_config_revisions > 0)
                    {
                       int i;
                       char bsrc[4096], bdst[4096];

                       for (i = _e_config_revisions; i > 1; i--)
                         {
                            snprintf(bsrc, sizeof(bsrc), "%s.%i", msg->destpath, i - 1);
                            snprintf(bdst, sizeof(bdst), "%s.%i", msg->destpath, i);
                            if ((ecore_file_exists(bsrc)) &&
                                (ecore_file_size(bsrc)))
                              {
                                 ret = ecore_file_mv(bsrc, bdst);
                                 if (!ret)
                                   {
                                      printf("CF: Error: Can't rename %s to %s\n", bsrc, bdst);
                                      break;
                                   }
                              }
                         }
                       if (ret)
                         {
                            snprintf(bdst, sizeof(bdst), "%s.1", msg->destpath);
                            ecore_file_mv(msg->destpath, bdst);
                         }
                    }
                  if (!ecore_file_mv(msg->path, msg->destpath))
                    printf("CF: Error: Can't rename %s to %s\n", msg->path, msg->destpath);
                  // open another tmp file now in a thread ready for writes next
                  // time. This can just dangle - no harm
                  _e_config_pending_file_find(msg->path);
                  ecore_thread_feedback(eth, strdup(msg->destpath));
               }
             free(msg->path);
             free(msg->destpath);
             break;
           case E_CONFIG_SAVE_THREAD_QUIT:
             run = EINA_FALSE;
             break;
           default:
             break;
          }
        eina_thread_queue_wait_done(_e_config_thread_thq, ref);
     }
}

static void
_e_config_save_thread_notify(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED, void *msgdata)
{
   char *path = msgdata;
   free(path);
}

static void
_e_config_save_thread_end(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED)
{
   ecore_main_loop_quit();
}

static void
_e_config_save_thread_cancel(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED)
{
   ecore_main_loop_quit();
}

static void
_e_config_save_thread_send(E_Config_Save_Thread_Message_Type type, const char *path, const char *destpath)
{
   E_Config_Save_Thread_Message *msg;
   void *ref;

   msg = eina_thread_queue_send
     (_e_config_thread_thq, sizeof(E_Config_Save_Thread_Message), &ref);
   msg->type = type;
   if (path) msg->path = strdup(path);
   else msg->path = NULL;
   if (destpath) msg->destpath = strdup(destpath);
   else msg->destpath = NULL;
   eina_thread_queue_send_done(_e_config_thread_thq, ref);
}

static void
_e_config_edd_shutdown(void)
{
   E_CONFIG_DD_FREE(_e_config_edd);
   E_CONFIG_DD_FREE(_e_config_module_edd);
   E_CONFIG_DD_FREE(_e_config_font_default_edd);
   E_CONFIG_DD_FREE(_e_config_font_fallback_edd);
   E_CONFIG_DD_FREE(_e_config_theme_edd);
   E_CONFIG_DD_FREE(_e_config_path_append_edd);
   E_CONFIG_DD_FREE(_e_config_desktop_bg_edd);
   E_CONFIG_DD_FREE(_e_config_desklock_bg_edd);
   E_CONFIG_DD_FREE(_e_config_desktop_name_edd);
   E_CONFIG_DD_FREE(e_remember_edd);
   E_CONFIG_DD_FREE(_e_config_menu_applications_edd);
   E_CONFIG_DD_FREE(_e_config_gadcon_edd);
   E_CONFIG_DD_FREE(_e_config_gadcon_client_edd);
   E_CONFIG_DD_FREE(_e_config_shelf_edd);
   E_CONFIG_DD_FREE(_e_config_shelf_desk_edd);
   E_CONFIG_DD_FREE(_e_config_mime_icon_edd);
   E_CONFIG_DD_FREE(_e_config_syscon_action_edd);
   E_CONFIG_DD_FREE(_e_config_env_var_edd);
   E_CONFIG_DD_FREE(_e_config_xkb_layout_edd);
   E_CONFIG_DD_FREE(_e_config_xkb_option_edd);
}

static void
_e_config_edd_init(Eina_Bool old)
{

   _e_config_gadcon_client_edd = E_CONFIG_DD_NEW("E_Config_Gadcon_Client", E_Config_Gadcon_Client);
#undef T
#undef D
#define T E_Config_Gadcon_Client
#define D _e_config_gadcon_client_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, id, STR);
   E_CONFIG_VAL(D, T, geom.pos, INT);
   E_CONFIG_VAL(D, T, geom.size, INT);
   E_CONFIG_VAL(D, T, geom.res, INT);
   E_CONFIG_VAL(D, T, geom.pos_x, DOUBLE);
   E_CONFIG_VAL(D, T, geom.pos_y, DOUBLE);
   E_CONFIG_VAL(D, T, geom.size_w, DOUBLE);
   E_CONFIG_VAL(D, T, geom.size_h, DOUBLE);
   E_CONFIG_VAL(D, T, state_info.seq, INT);
   E_CONFIG_VAL(D, T, state_info.flags, INT);
   E_CONFIG_VAL(D, T, style, STR);
   E_CONFIG_VAL(D, T, orient, INT);
   E_CONFIG_VAL(D, T, autoscroll, UCHAR);
   E_CONFIG_VAL(D, T, resizable, UCHAR);

   _e_config_gadcon_edd = E_CONFIG_DD_NEW("E_Config_Gadcon", E_Config_Gadcon);
#undef T
#undef D
#define T E_Config_Gadcon
#define D _e_config_gadcon_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, id, INT);
   E_CONFIG_VAL(D, T, zone, UINT);
   E_CONFIG_LIST(D, T, clients, _e_config_gadcon_client_edd);

   _e_config_shelf_desk_edd = E_CONFIG_DD_NEW("E_Config_Shelf_Desk", E_Config_Shelf_Desk);
#undef T
#undef D
#define T E_Config_Shelf_Desk
#define D _e_config_shelf_desk_edd
   E_CONFIG_VAL(D, T, x, INT);
   E_CONFIG_VAL(D, T, y, INT);

   _e_config_shelf_edd = E_CONFIG_DD_NEW("E_Config_Shelf", E_Config_Shelf);
#undef T
#undef D
#define T E_Config_Shelf
#define D _e_config_shelf_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, id, INT);
   E_CONFIG_VAL(D, T, zone, INT);
   E_CONFIG_VAL(D, T, layer, INT);
   E_CONFIG_VAL(D, T, popup, UCHAR);
   E_CONFIG_VAL(D, T, orient, INT);
   E_CONFIG_VAL(D, T, fit_along, UCHAR);
   E_CONFIG_VAL(D, T, fit_size, UCHAR);
   E_CONFIG_VAL(D, T, style, STR);
   E_CONFIG_VAL(D, T, size, INT);
   E_CONFIG_VAL(D, T, overlap, INT);
   E_CONFIG_VAL(D, T, autohide, INT);
   E_CONFIG_VAL(D, T, autohide_show_action, INT);
   E_CONFIG_VAL(D, T, hide_timeout, FLOAT);
   E_CONFIG_VAL(D, T, hide_duration, FLOAT);
   E_CONFIG_VAL(D, T, desk_show_mode, INT);
   E_CONFIG_LIST(D, T, desk_list, _e_config_shelf_desk_edd);

   _e_config_desklock_bg_edd = E_CONFIG_DD_NEW("E_Config_Desklock_Background", E_Config_Desklock_Background);
#undef T
#undef D
#define T E_Config_Desklock_Background
#define D _e_config_desklock_bg_edd
   E_CONFIG_VAL(D, T, file, STR);
   E_CONFIG_VAL(D, T, hide_logo, UCHAR);

   _e_config_desktop_bg_edd = E_CONFIG_DD_NEW("E_Config_Desktop_Background", E_Config_Desktop_Background);
#undef T
#undef D
#define T E_Config_Desktop_Background
#define D _e_config_desktop_bg_edd
   E_CONFIG_VAL(D, T, zone, INT);
   E_CONFIG_VAL(D, T, desk_x, INT);
   E_CONFIG_VAL(D, T, desk_y, INT);
   E_CONFIG_VAL(D, T, file, STR);

   _e_config_desktop_name_edd = E_CONFIG_DD_NEW("E_Config_Desktop_Name", E_Config_Desktop_Name);
#undef T
#undef D
#define T E_Config_Desktop_Name
#define D _e_config_desktop_name_edd
   E_CONFIG_VAL(D, T, zone, INT);
   E_CONFIG_VAL(D, T, desk_x, INT);
   E_CONFIG_VAL(D, T, desk_y, INT);
   E_CONFIG_VAL(D, T, name, STR);

   _e_config_path_append_edd = E_CONFIG_DD_NEW("E_Path_Dir", E_Path_Dir);
#undef T
#undef D
#define T E_Path_Dir
#define D _e_config_path_append_edd
   E_CONFIG_VAL(D, T, dir, STR);

   _e_config_module_edd = E_CONFIG_DD_NEW("E_Config_Module", E_Config_Module);
#undef T
#undef D
#define T E_Config_Module
#define D _e_config_module_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, enabled, UCHAR);

   _e_config_font_default_edd = E_CONFIG_DD_NEW("E_Font_Default",
                                                E_Font_Default);
#undef T
#undef D
#define T E_Font_Default
#define D _e_config_font_default_edd
   E_CONFIG_VAL(D, T, text_class, STR);
   E_CONFIG_VAL(D, T, font, STR);
   E_CONFIG_VAL(D, T, size, INT);

   _e_config_font_fallback_edd = E_CONFIG_DD_NEW("E_Font_Fallback",
                                                 E_Font_Fallback);
#undef T
#undef D
#define T E_Font_Fallback
#define D _e_config_font_fallback_edd
   E_CONFIG_VAL(D, T, name, STR);

   _e_config_menu_applications_edd = E_CONFIG_DD_NEW("E_Int_Menu_Applications",
                                                     E_Int_Menu_Applications);
#undef T
#undef D
#define T E_Int_Menu_Applications
#define D _e_config_menu_applications_edd
   E_CONFIG_VAL(D, T, orig_path, STR);
   E_CONFIG_VAL(D, T, try_exec, STR);
   E_CONFIG_VAL(D, T, exec, STR);
   E_CONFIG_VAL(D, T, load_time, LL);
   E_CONFIG_VAL(D, T, exec_valid, INT);

   e_remember_edd = E_CONFIG_DD_NEW("E_Remember", E_Remember);
#undef T
#undef D
#define T E_Remember
#define D e_remember_edd
   E_CONFIG_VAL(D, T, version, UINT);
   E_CONFIG_VAL(D, T, match, INT);
   E_CONFIG_VAL(D, T, no_reopen, INT);
   E_CONFIG_VAL(D, T, apply_first_only, UCHAR);
   E_CONFIG_VAL(D, T, keep_settings, UCHAR);
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, class, STR);
   E_CONFIG_VAL(D, T, title, STR);
   E_CONFIG_VAL(D, T, role, STR);
   E_CONFIG_VAL(D, T, type, INT);
   E_CONFIG_VAL(D, T, transient, UCHAR);
   E_CONFIG_VAL(D, T, apply, INT);
   E_CONFIG_VAL(D, T, max_score, INT);
   E_CONFIG_VAL(D, T, prop.pos_x, INT);
   E_CONFIG_VAL(D, T, prop.pos_y, INT);
   E_CONFIG_VAL(D, T, prop.res_x, INT);
   E_CONFIG_VAL(D, T, prop.res_y, INT);
   E_CONFIG_VAL(D, T, prop.pos_w, INT);
   E_CONFIG_VAL(D, T, prop.pos_h, INT);
   E_CONFIG_VAL(D, T, prop.frame_w, INT);
   E_CONFIG_VAL(D, T, prop.frame_h, INT);
   E_CONFIG_VAL(D, T, prop.w, INT);
   E_CONFIG_VAL(D, T, prop.h, INT);
   E_CONFIG_VAL(D, T, prop.layer, INT);
   E_CONFIG_VAL(D, T, prop.maximize, UINT);
   E_CONFIG_VAL(D, T, prop.lock_user_location, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_location, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_size, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_size, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_stacking, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_stacking, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_iconify, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_iconify, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_desk, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_desk, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_sticky, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_sticky, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_shade, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_shade, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_maximize, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_maximize, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_user_fullscreen, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_client_fullscreen, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_border, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_close, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_focus_in, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_focus_out, UCHAR);
   E_CONFIG_VAL(D, T, prop.lock_life, UCHAR);
   E_CONFIG_VAL(D, T, prop.border, STR);
   E_CONFIG_VAL(D, T, prop.sticky, UCHAR);
   E_CONFIG_VAL(D, T, prop.shaded, UCHAR);
   E_CONFIG_VAL(D, T, prop.skip_winlist, UCHAR);
   E_CONFIG_VAL(D, T, prop.skip_pager, UCHAR);
   E_CONFIG_VAL(D, T, prop.skip_taskbar, UCHAR);
   E_CONFIG_VAL(D, T, prop.fullscreen, UCHAR);
   E_CONFIG_VAL(D, T, prop.desk_x, INT);
   E_CONFIG_VAL(D, T, prop.desk_y, INT);
   E_CONFIG_VAL(D, T, prop.zone, INT);
   E_CONFIG_VAL(D, T, prop.command, STR);
   E_CONFIG_VAL(D, T, prop.icon_preference, UCHAR);
   E_CONFIG_VAL(D, T, prop.desktop_file, STR);
   E_CONFIG_VAL(D, T, prop.offer_resistance, UCHAR);
   E_CONFIG_VAL(D, T, prop.opacity, UCHAR);
   E_CONFIG_VAL(D, T, prop.volume, INT);
   E_CONFIG_VAL(D, T, prop.volume_min, INT);
   E_CONFIG_VAL(D, T, prop.volume_max, INT);
   E_CONFIG_VAL(D, T, prop.mute, UCHAR);
   E_CONFIG_VAL(D, T, uuid, STR);
   E_CONFIG_VAL(D, T, pid, INT);

   _e_config_color_class_edd = E_CONFIG_DD_NEW("E_Color_Class", E_Color_Class);
#undef T
#undef D
#define T E_Color_Class
#define D _e_config_color_class_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, r, INT);
   E_CONFIG_VAL(D, T, g, INT);
   E_CONFIG_VAL(D, T, b, INT);
   E_CONFIG_VAL(D, T, a, INT);
   E_CONFIG_VAL(D, T, r2, INT);
   E_CONFIG_VAL(D, T, g2, INT);
   E_CONFIG_VAL(D, T, b2, INT);
   E_CONFIG_VAL(D, T, a2, INT);
   E_CONFIG_VAL(D, T, r3, INT);
   E_CONFIG_VAL(D, T, g3, INT);
   E_CONFIG_VAL(D, T, b3, INT);
   E_CONFIG_VAL(D, T, a3, INT);

   _e_config_mime_icon_edd = E_CONFIG_DD_NEW("E_Config_Mime_Icon",
                                             E_Config_Mime_Icon);
#undef T
#undef D
#define T E_Config_Mime_Icon
#define D _e_config_mime_icon_edd
   E_CONFIG_VAL(D, T, mime, STR);
   E_CONFIG_VAL(D, T, icon, STR);

   _e_config_syscon_action_edd = E_CONFIG_DD_NEW("E_Config_Syscon_Action",
                                                 E_Config_Syscon_Action);
#undef T
#undef D
#define T E_Config_Syscon_Action
#define D _e_config_syscon_action_edd
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);
   E_CONFIG_VAL(D, T, button, STR);
   E_CONFIG_VAL(D, T, icon, STR);
   E_CONFIG_VAL(D, T, is_main, INT);

   _e_config_env_var_edd = E_CONFIG_DD_NEW("E_Config_Env_Var",
                                           E_Config_Env_Var);
#undef T
#undef D
#define T E_Config_Env_Var
#define D _e_config_env_var_edd
   E_CONFIG_VAL(D, T, var, STR);
   E_CONFIG_VAL(D, T, val, STR);
   E_CONFIG_VAL(D, T, unset, UCHAR);

   _e_config_xkb_layout_edd = E_CONFIG_DD_NEW("E_Config_XKB_Layout",
                                              E_Config_XKB_Layout);
#undef T
#undef D
#define T E_Config_XKB_Layout
#define D _e_config_xkb_layout_edd
   E_CONFIG_VAL(D, T, name, STR);
   E_CONFIG_VAL(D, T, model, STR);
   E_CONFIG_VAL(D, T, variant, STR);

   _e_config_xkb_option_edd = E_CONFIG_DD_NEW("E_Config_XKB_Option",
                                              E_Config_XKB_Option);
#undef T
#undef D
#define T E_Config_XKB_Option
#define D _e_config_xkb_option_edd
   E_CONFIG_VAL(D, T, name, STR);

   _e_config_edd = E_CONFIG_DD_NEW("E_Config", E_Config);
#undef T
#undef D
#define T E_Config
#define D _e_config_edd
   /**/ /* == already configurable via ipc */
   E_CONFIG_VAL(D, T, config_version, INT); /**/
   E_CONFIG_VAL(D, T, config_type, UINT); /**/
   E_CONFIG_VAL(D, T, desktop_default_background, STR); /**/
   E_CONFIG_LIST(D, T, desktop_backgrounds, _e_config_desktop_bg_edd); /**/
   E_CONFIG_LIST(D, T, desktop_names, _e_config_desktop_name_edd); /**/
   E_CONFIG_VAL(D, T, menus_scroll_speed, DOUBLE); /**/
   E_CONFIG_VAL(D, T, menus_fast_mouse_move_threshhold, DOUBLE); /**/
   E_CONFIG_VAL(D, T, menus_click_drag_timeout, DOUBLE); /**/
   E_CONFIG_VAL(D, T, window_maximize_animate, INT); /**/
   E_CONFIG_VAL(D, T, window_maximize_transition, INT); /**/
   E_CONFIG_VAL(D, T, window_maximize_time, DOUBLE); /**/
   E_CONFIG_VAL(D, T, border_shade_animate, INT); /**/
   E_CONFIG_VAL(D, T, border_shade_transition, INT); /**/
   E_CONFIG_VAL(D, T, border_shade_speed, DOUBLE); /**/
   E_CONFIG_VAL(D, T, priority, INT); /**/
   E_CONFIG_VAL(D, T, zone_desks_x_count, INT); /**/
   E_CONFIG_VAL(D, T, zone_desks_y_count, INT); /**/
   E_CONFIG_VAL(D, T, show_desktop_icons, INT); /**/
   E_CONFIG_VAL(D, T, edge_flip_dragging, INT); /**/
   E_CONFIG_VAL(D, T, language, STR); /**/
   E_CONFIG_VAL(D, T, desklock_language, STR); /**/
   E_CONFIG_LIST(D, T, modules, _e_config_module_edd); /**/
   EET_DATA_DESCRIPTOR_ADD_LIST_STRING(D, T, "bad_modules", bad_modules);
   E_CONFIG_LIST(D, T, font_fallbacks, _e_config_font_fallback_edd); /**/
   E_CONFIG_LIST(D, T, font_defaults, _e_config_font_default_edd); /**/
   E_CONFIG_LIST(D, T, mouse_bindings, _e_config_bindings_mouse_edd); /**/
   E_CONFIG_LIST(D, T, key_bindings, _e_config_bindings_key_edd); /**/
   E_CONFIG_LIST(D, T, edge_bindings, _e_config_bindings_edge_edd); /**/
   E_CONFIG_LIST(D, T, signal_bindings, _e_config_bindings_signal_edd); /**/
   E_CONFIG_LIST(D, T, wheel_bindings, _e_config_bindings_wheel_edd); /**/
   E_CONFIG_LIST(D, T, acpi_bindings, _e_config_bindings_acpi_edd); /**/
   E_CONFIG_LIST(D, T, swipe_bindings, _e_config_bindings_swipe_edd); /**/
   E_CONFIG_LIST(D, T, path_append_data, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_images, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_fonts, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_init, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_icons, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_modules, _e_config_path_append_edd); /**/
   E_CONFIG_LIST(D, T, path_append_backgrounds, _e_config_path_append_edd); /**/
   E_CONFIG_VAL(D, T, window_placement_policy, INT); /**/
   E_CONFIG_VAL(D, T, window_grouping, INT); /**/
   E_CONFIG_VAL(D, T, focus_policy, INT); /**/
   E_CONFIG_VAL(D, T, focus_setting, INT); /**/
   E_CONFIG_VAL(D, T, pass_click_on, INT); /**/
   E_CONFIG_VAL(D, T, window_activehint_policy, INT); /**/
   E_CONFIG_VAL(D, T, always_click_to_raise, INT); /**/
   E_CONFIG_VAL(D, T, always_click_to_focus, INT); /**/
   E_CONFIG_VAL(D, T, use_auto_raise, INT); /**/
   E_CONFIG_VAL(D, T, auto_raise_delay, DOUBLE); /**/
   E_CONFIG_VAL(D, T, use_resist, INT); /**/
   E_CONFIG_VAL(D, T, drag_resist, INT); /**/
   E_CONFIG_VAL(D, T, desk_resist, INT); /**/
   E_CONFIG_VAL(D, T, window_resist, INT); /**/
   E_CONFIG_VAL(D, T, gadget_resist, INT); /**/
   E_CONFIG_VAL(D, T, geometry_auto_resize_limit, INT); /**/
   E_CONFIG_VAL(D, T, geometry_auto_move, INT); /**/
   E_CONFIG_VAL(D, T, winlist_warp_while_selecting, INT); /**/
   E_CONFIG_VAL(D, T, winlist_warp_at_end, INT); /**/
   E_CONFIG_VAL(D, T, winlist_no_warp_on_direction, INT); /**/
   E_CONFIG_VAL(D, T, winlist_warp_speed, DOUBLE); /**/
   E_CONFIG_VAL(D, T, winlist_scroll_animate, INT); /**/
   E_CONFIG_VAL(D, T, winlist_scroll_speed, DOUBLE); /**/
   E_CONFIG_VAL(D, T, winlist_list_show_iconified, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_show_other_desk_iconified, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_show_other_screen_iconified, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_show_other_desk_windows, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_show_other_screen_windows, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_uncover_while_selecting, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_jump_desk_while_selecting, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_focus_while_selecting, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_raise_while_selecting, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_move_after_select, INT); /**/
   E_CONFIG_VAL(D, T, winlist_mode, INT); /**/
   E_CONFIG_VAL(D, T, winlist_list_size, DOUBLE); /**/
   E_CONFIG_VAL(D, T, winlist_large_size, DOUBLE); /**/
   E_CONFIG_VAL(D, T, winlist_list_no_miniatures, UCHAR); /**/
   E_CONFIG_VAL(D, T, maximize_policy, INT); /**/
   E_CONFIG_VAL(D, T, allow_manip, INT); /**/
   E_CONFIG_VAL(D, T, border_fix_on_shelf_toggle, INT); /**/
   E_CONFIG_VAL(D, T, allow_above_fullscreen, INT); /**/
   E_CONFIG_VAL(D, T, kill_if_close_not_possible, INT); /**/
   E_CONFIG_VAL(D, T, kill_process, INT); /**/
   E_CONFIG_VAL(D, T, kill_timer_wait, DOUBLE); /**/
   E_CONFIG_VAL(D, T, ping_clients, INT); /**/
   E_CONFIG_VAL(D, T, transition_start, STR); /**/
   E_CONFIG_VAL(D, T, transition_desk, STR); /**/
   E_CONFIG_VAL(D, T, transition_change, STR); /**/
   E_CONFIG_LIST(D, T, remembers, e_remember_edd);
   E_CONFIG_LIST(D, T, menu_applications, _e_config_menu_applications_edd);
   E_CONFIG_VAL(D, T, remember_internal_windows, INT);
   E_CONFIG_VAL(D, T, remember_internal_fm_windows, UCHAR);
   E_CONFIG_VAL(D, T, remember_internal_fm_windows_globally, UCHAR);
   E_CONFIG_VAL(D, T, move_info_follows, INT); /**/
   E_CONFIG_VAL(D, T, resize_info_follows, INT); /**/
   E_CONFIG_VAL(D, T, move_info_visible, INT); /**/
   E_CONFIG_VAL(D, T, resize_info_visible, INT); /**/
   E_CONFIG_VAL(D, T, focus_last_focused_per_desktop, INT); /**/
   E_CONFIG_VAL(D, T, focus_revert_on_hide_or_close, INT); /**/
   E_CONFIG_VAL(D, T, focus_revert_allow_sticky, INT); /**/
   E_CONFIG_VAL(D, T, pointer_slide, INT); /**/
   E_CONFIG_VAL(D, T, disable_all_pointer_warps, INT); /**/
   E_CONFIG_VAL(D, T, pointer_warp_speed, DOUBLE); /**/
   E_CONFIG_VAL(D, T, use_e_cursor, INT); /**/
   E_CONFIG_VAL(D, T, cursor_size, INT); /**/
   E_CONFIG_VAL(D, T, menu_autoscroll_margin, INT); /**/
   E_CONFIG_VAL(D, T, menu_autoscroll_cursor_margin, INT); /**/
   E_CONFIG_VAL(D, T, transient.move, INT); /* FIXME: implement */
   E_CONFIG_VAL(D, T, transient.resize, INT); /* FIXME: implement */
   E_CONFIG_VAL(D, T, transient.raise, INT); /**/
   E_CONFIG_VAL(D, T, transient.lower, INT); /**/
   E_CONFIG_VAL(D, T, transient.layer, INT); /**/
   E_CONFIG_VAL(D, T, transient.desktop, INT); /**/
   E_CONFIG_VAL(D, T, transient.iconify, INT); /**/
   E_CONFIG_VAL(D, T, menu_eap_name_show, INT); /**/
   E_CONFIG_VAL(D, T, menu_eap_generic_show, INT); /**/
   E_CONFIG_VAL(D, T, menu_eap_comment_show, INT); /**/
   E_CONFIG_VAL(D, T, fullscreen_policy, INT); /**/
   E_CONFIG_VAL(D, T, input_method, STR); /**/
   E_CONFIG_LIST(D, T, path_append_messages, _e_config_path_append_edd); /**/
   E_CONFIG_VAL(D, T, exebuf_term_cmd, STR);
   E_CONFIG_LIST(D, T, color_classes, _e_config_color_class_edd);
   E_CONFIG_VAL(D, T, use_app_icon, INT);
   E_CONFIG_VAL(D, T, cnfmdlg_disabled, INT); /**/
   E_CONFIG_VAL(D, T, cfgdlg_auto_apply, INT); /**/
   E_CONFIG_VAL(D, T, cfgdlg_default_mode, INT); /**/
   E_CONFIG_LIST(D, T, gadcons, _e_config_gadcon_edd);
   E_CONFIG_LIST(D, T, shelves, _e_config_shelf_edd);
   E_CONFIG_VAL(D, T, font_hinting, INT); /**/
   E_CONFIG_VAL(D, T, desklock_passwd, INT);
   E_CONFIG_VAL(D, T, desklock_pin, INT);
   E_CONFIG_LIST(D, T, desklock_backgrounds, _e_config_desklock_bg_edd); /**/
   E_CONFIG_VAL(D, T, desklock_auth_method, INT);
   E_CONFIG_VAL(D, T, desklock_login_box_zone, INT);
   E_CONFIG_VAL(D, T, desklock_start_locked, INT);
   E_CONFIG_VAL(D, T, desklock_on_suspend, INT);
   E_CONFIG_VAL(D, T, desklock_autolock_screensaver, INT);
   E_CONFIG_VAL(D, T, desklock_post_screensaver_time, DOUBLE);
   E_CONFIG_VAL(D, T, desklock_ask_presentation, UCHAR);
   E_CONFIG_VAL(D, T, desklock_ask_presentation_timeout, DOUBLE);

   E_CONFIG_VAL(D, T, screensaver_enable, INT);
   E_CONFIG_VAL(D, T, screensaver_timeout, INT);
   E_CONFIG_VAL(D, T, screensaver_interval, INT);
   E_CONFIG_VAL(D, T, screensaver_blanking, INT);
   E_CONFIG_VAL(D, T, screensaver_expose, INT);
   E_CONFIG_VAL(D, T, screensaver_dpms_off, UCHAR);
   E_CONFIG_VAL(D, T, screensaver_ask_presentation, UCHAR);
   E_CONFIG_VAL(D, T, screensaver_ask_presentation_timeout, DOUBLE);
   E_CONFIG_VAL(D, T, screensaver_desklock_timeout, INT);

   E_CONFIG_VAL(D, T, screensaver_wake_on_notify, INT);
   E_CONFIG_VAL(D, T, screensaver_wake_on_urgent, INT);

   E_CONFIG_VAL(D, T, suspend_connected_standby, UCHAR);

   E_CONFIG_VAL(D, T, screensaver_suspend, UCHAR);
   E_CONFIG_VAL(D, T, screensaver_hibernate, UCHAR);
   E_CONFIG_VAL(D, T, screensaver_suspend_on_ac, UCHAR);
   E_CONFIG_VAL(D, T, screensaver_suspend_delay, DOUBLE);

   E_CONFIG_VAL(D, T, dpms_enable, INT);
   E_CONFIG_VAL(D, T, dpms_standby_enable, INT);
   E_CONFIG_VAL(D, T, dpms_suspend_enable, INT);
   E_CONFIG_VAL(D, T, dpms_off_enable, INT);
   E_CONFIG_VAL(D, T, dpms_standby_timeout, INT);
   E_CONFIG_VAL(D, T, dpms_suspend_timeout, INT);
   E_CONFIG_VAL(D, T, dpms_off_timeout, INT);
   E_CONFIG_VAL(D, T, no_dpms_on_fullscreen, UCHAR);

   E_CONFIG_VAL(D, T, clientlist_group_by, INT);
   E_CONFIG_VAL(D, T, clientlist_include_all_zones, INT);
   E_CONFIG_VAL(D, T, clientlist_separate_with, INT);
   E_CONFIG_VAL(D, T, clientlist_sort_by, INT);
   E_CONFIG_VAL(D, T, clientlist_separate_iconified_apps, INT);
   E_CONFIG_VAL(D, T, clientlist_warp_to_iconified_desktop, INT);
   E_CONFIG_VAL(D, T, clientlist_limit_caption_len, INT);
   E_CONFIG_VAL(D, T, clientlist_max_caption_len, INT);

   E_CONFIG_VAL(D, T, mouse_hand, INT);

   E_CONFIG_VAL(D, T, mouse_accel, DOUBLE);
   E_CONFIG_VAL(D, T, mouse_accel_threshold, INT);
   E_CONFIG_VAL(D, T, mouse_natural_scroll, UCHAR);
   E_CONFIG_VAL(D, T, mouse_emulate_middle_button, UCHAR);

   E_CONFIG_VAL(D, T, touch_accel, DOUBLE);
   E_CONFIG_VAL(D, T, touch_natural_scroll, UCHAR);
   E_CONFIG_VAL(D, T, touch_emulate_middle_button, UCHAR);
   E_CONFIG_VAL(D, T, touch_tap_to_click, UCHAR);
   E_CONFIG_VAL(D, T, touch_clickpad, UCHAR);
   E_CONFIG_VAL(D, T, touch_scrolling_2finger, UCHAR);
   E_CONFIG_VAL(D, T, touch_scrolling_edge, UCHAR);
   E_CONFIG_VAL(D, T, touch_scrolling_circular, UCHAR);
   E_CONFIG_VAL(D, T, touch_scrolling_horiz, UCHAR);
   E_CONFIG_VAL(D, T, touch_palm_detect, UCHAR);

   E_CONFIG_VAL(D, T, border_raise_on_mouse_action, INT);
   E_CONFIG_VAL(D, T, border_raise_on_focus, INT);
   E_CONFIG_VAL(D, T, raise_on_revert_focus, INT);
   E_CONFIG_VAL(D, T, desk_flip_wrap, INT);
   E_CONFIG_VAL(D, T, fullscreen_flip, INT);
   E_CONFIG_VAL(D, T, multiscreen_flip, INT);

   E_CONFIG_VAL(D, T, icon_theme, STR);
   E_CONFIG_VAL(D, T, icon_theme_overrides, UCHAR);
   E_CONFIG_VAL(D, T, desktop_environment, STR);

   E_CONFIG_VAL(D, T, desk_flip_animate_mode, INT);
   E_CONFIG_VAL(D, T, desk_flip_animate_type, STR);
   E_CONFIG_VAL(D, T, desk_flip_animate_interpolation, INT);

   E_CONFIG_VAL(D, T, wallpaper_import_last_dev, STR);
   E_CONFIG_VAL(D, T, wallpaper_import_last_path, STR);

   E_CONFIG_VAL(D, T, theme_default_border_style, STR);

   E_CONFIG_LIST(D, T, mime_icons, _e_config_mime_icon_edd); /**/

   E_CONFIG_VAL(D, T, desk_auto_switch, INT);

   E_CONFIG_VAL(D, T, screen_limits, INT);

   E_CONFIG_VAL(D, T, thumb_nice, INT);

   E_CONFIG_VAL(D, T, gesture_open_input_devices, UCHAR);

   E_CONFIG_VAL(D, T, menu_icons_hide, UCHAR);
   E_CONFIG_VAL(D, T, menu_favorites_show, INT);
   E_CONFIG_VAL(D, T, menu_apps_show, INT);
   E_CONFIG_VAL(D, T, menu_gadcon_client_toplevel, INT);

   E_CONFIG_VAL(D, T, ping_clients_interval, INT);

   E_CONFIG_VAL(D, T, thumbscroll_enable, INT);
   E_CONFIG_VAL(D, T, thumbscroll_threshhold, INT);
   E_CONFIG_VAL(D, T, thumbscroll_momentum_threshhold, DOUBLE);
   E_CONFIG_VAL(D, T, thumbscroll_friction, DOUBLE);

   E_CONFIG_VAL(D, T, filemanager_single_click, UCHAR);
   E_CONFIG_VAL(D, T, device_desktop, INT);
   E_CONFIG_VAL(D, T, device_auto_mount, INT);
   E_CONFIG_VAL(D, T, device_auto_open, INT);
   E_CONFIG_VAL(D, T, filemanager_copy, UCHAR);
   E_CONFIG_VAL(D, T, filemanager_secure_rm, UCHAR);

   E_CONFIG_VAL(D, T, border_keyboard.timeout, DOUBLE);
   E_CONFIG_VAL(D, T, border_keyboard.move.dx, UCHAR);
   E_CONFIG_VAL(D, T, border_keyboard.move.dy, UCHAR);
   E_CONFIG_VAL(D, T, border_keyboard.resize.dx, UCHAR);
   E_CONFIG_VAL(D, T, border_keyboard.resize.dy, UCHAR);

   E_CONFIG_VAL(D, T, scale.min, DOUBLE);
   E_CONFIG_VAL(D, T, scale.max, DOUBLE);
   E_CONFIG_VAL(D, T, scale.factor, DOUBLE);
   E_CONFIG_VAL(D, T, scale.base_dpi, INT);
   E_CONFIG_VAL(D, T, scale.xapp_base_dpi, INT);
   E_CONFIG_VAL(D, T, scale.use_dpi, UCHAR);
   E_CONFIG_VAL(D, T, scale.use_custom, UCHAR);
   E_CONFIG_VAL(D, T, scale.set_xapp_dpi, UCHAR);

   E_CONFIG_VAL(D, T, show_cursor, UCHAR);
   E_CONFIG_VAL(D, T, idle_cursor, UCHAR);

   E_CONFIG_VAL(D, T, default_system_menu, STR);

   E_CONFIG_VAL(D, T, cfgdlg_normal_wins, UCHAR);

   E_CONFIG_VAL(D, T, syscon.main.icon_size, INT);
   E_CONFIG_VAL(D, T, syscon.secondary.icon_size, INT);
   E_CONFIG_VAL(D, T, syscon.extra.icon_size, INT);
   E_CONFIG_VAL(D, T, syscon.timeout, DOUBLE);
   E_CONFIG_VAL(D, T, syscon.do_input, UCHAR);
   E_CONFIG_LIST(D, T, syscon.actions, _e_config_syscon_action_edd);

   E_CONFIG_VAL(D, T, exec.expire_timeout, DOUBLE);
   E_CONFIG_VAL(D, T, exec.show_run_dialog, UCHAR);
   E_CONFIG_VAL(D, T, exec.show_exit_dialog, UCHAR);

   E_CONFIG_VAL(D, T, null_container_win, UCHAR);

   E_CONFIG_LIST(D, T, env_vars, _e_config_env_var_edd);

   E_CONFIG_VAL(D, T, backlight.normal, DOUBLE);
   E_CONFIG_VAL(D, T, backlight.dim, DOUBLE);
   E_CONFIG_VAL(D, T, backlight.transition, DOUBLE);
   E_CONFIG_VAL(D, T, backlight.timer, DOUBLE);
   E_CONFIG_VAL(D, T, backlight.battery_timer, DOUBLE);
   E_CONFIG_VAL(D, T, backlight.sysdev, STR);
   E_CONFIG_VAL(D, T, backlight.idle_dim, UCHAR);
   E_CONFIG_VAL(D, T, backlight.ddc, UCHAR);

   E_CONFIG_VAL(D, T, deskenv.load_xrdb, UCHAR);
   E_CONFIG_VAL(D, T, deskenv.load_xmodmap, UCHAR);
   E_CONFIG_VAL(D, T, deskenv.load_gnome, UCHAR);
   E_CONFIG_VAL(D, T, deskenv.load_kde, UCHAR);

   E_CONFIG_VAL(D, T, powersave.none, DOUBLE);
   E_CONFIG_VAL(D, T, powersave.low, DOUBLE);
   E_CONFIG_VAL(D, T, powersave.medium, DOUBLE);
   E_CONFIG_VAL(D, T, powersave.high, DOUBLE);
   E_CONFIG_VAL(D, T, powersave.extreme, DOUBLE);
   E_CONFIG_VAL(D, T, powersave.min, INT);
   E_CONFIG_VAL(D, T, powersave.max, INT);

   E_CONFIG_VAL(D, T, xsettings.enabled, UCHAR);
   E_CONFIG_VAL(D, T, xsettings.match_e17_theme, UCHAR);
   E_CONFIG_VAL(D, T, xsettings.match_e17_icon_theme, UCHAR);
   E_CONFIG_VAL(D, T, xsettings.xft_antialias, INT);
   E_CONFIG_VAL(D, T, xsettings.xft_hinting, INT);
   E_CONFIG_VAL(D, T, xsettings.xft_hint_style, STR);
   E_CONFIG_VAL(D, T, xsettings.xft_rgba, STR);
   E_CONFIG_VAL(D, T, xsettings.net_theme_name, STR);
   E_CONFIG_VAL(D, T, xsettings.net_icon_theme_name, STR);
   E_CONFIG_VAL(D, T, xsettings.gtk_font_name, STR);

   E_CONFIG_VAL(D, T, update.check, UCHAR);
   E_CONFIG_VAL(D, T, update.later, UCHAR);

   E_CONFIG_LIST(D, T, xkb.used_layouts, _e_config_xkb_layout_edd);
   E_CONFIG_LIST(D, T, xkb.used_options, _e_config_xkb_option_edd);
   E_CONFIG_VAL(D, T, xkb.only_label, INT);
   E_CONFIG_VAL(D, T, xkb.dont_touch_my_damn_keyboard, UCHAR);
   E_CONFIG_VAL(D, T, xkb.default_model, STR);
   E_CONFIG_VAL(D, T, xkb.use_cache, UCHAR);

   E_CONFIG_VAL(D, T, keyboard.repeat_delay, INT);
   E_CONFIG_VAL(D, T, keyboard.repeat_rate, INT);

   if (old)
     {
        E_CONFIG_SUB(D, T, xkb.current_layout, _e_config_xkb_option_edd);
        E_CONFIG_SUB(D, T, xkb.sel_layout, _e_config_xkb_option_edd);
        E_CONFIG_SUB(D, T, xkb.lock_layout, _e_config_xkb_option_edd);
     }
   else
     {
        E_CONFIG_SUB(D, T, xkb.current_layout, _e_config_xkb_layout_edd);
        E_CONFIG_SUB(D, T, xkb.sel_layout, _e_config_xkb_layout_edd);
        E_CONFIG_SUB(D, T, xkb.lock_layout, _e_config_xkb_layout_edd);
     }
   E_CONFIG_VAL(D, T, xkb.selected_layout, STR);
   E_CONFIG_VAL(D, T, xkb.cur_layout, STR);
   E_CONFIG_VAL(D, T, xkb.desklock_layout, STR);
   //E_CONFIG_VAL(D, T, xkb.cur_group, INT);

   E_CONFIG_VAL(D, T, exe_always_single_instance, UCHAR);
   E_CONFIG_VAL(D, T, explicit_date_time, UCHAR);
}

/* externally accessible functions */
EINTERN int
e_config_init(void)
{
   E_EVENT_CONFIG_ICON_THEME = ecore_event_type_new();
   E_EVENT_CONFIG_MODE_CHANGED = ecore_event_type_new();
   E_EVENT_CONFIG_LOADED = ecore_event_type_new();

   eina_lock_new(&_e_config_pending_files_lock);
   _e_config_pending_files = eina_hash_string_superfast_new(NULL);

   _e_config_thread_thq = eina_thread_queue_new();
   ecore_thread_feedback_run(_e_config_save_thread_main,
                             _e_config_save_thread_notify,
                             _e_config_save_thread_end,
                             _e_config_save_thread_cancel,
                             NULL, EINA_TRUE);

   /* if environment var set - use this profile name */
   _e_config_profile = eina_stringshare_add(getenv("E_CONF_PROFILE"));

   if (!_e_config_profile)
     {
        Eet_File *ef;
        char buf[PATH_MAX];

        /* try user profile config */
        e_user_dir_concat_static(buf, "config/profile.cfg");
        ef = eet_open(buf, EET_FILE_MODE_READ);
        if (ef)
          {
             _e_config_profile = _e_config_profile_name_get(ef);
             eet_close(ef);
             ef = NULL;
          }
        if (!_e_config_profile)
          {
             int i;

             for (i = 1; i <= _e_config_revisions; i++)
               {
                  e_user_dir_snprintf(buf, sizeof(buf), "config/profile.cfg.%i", i);
                  ef = eet_open(buf, EET_FILE_MODE_READ);
                  if (ef)
                    {
                       printf("CF: warning - falling back to %s\n", buf);
                       _e_config_profile = _e_config_profile_name_get(ef);
                       eet_close(ef);
                       ef = NULL;
                       if (_e_config_profile) break;
                       printf("CF: warning - fallback to %s can't read profile\n", buf);
                    }
                  else if (ecore_file_exists(buf))
                    printf("CF: warning - fallback to %s failing to open\n", buf);
               }
             if (!_e_config_profile)
               {
                  /* use system if no user profile config */
                  printf("CF: fallback to system profile config file\n");
                  e_prefix_data_concat_static(buf, "data/config/profile.cfg");
                  ef = eet_open(buf, EET_FILE_MODE_READ);
               }
          }
        if (ef)
          {
             _e_config_profile = _e_config_profile_name_get(ef);
             eet_close(ef);
             ef = NULL;
             if (!_e_config_profile)
               printf("CF: warning - can't read profile\n");
          }
        if (!_e_config_profile)
          {
             /* no profile config - try other means */
             char *lnk = NULL;

             /* check symlink - if default is a symlink to another dir */
             e_prefix_data_concat_static(buf, "data/config/default");
             lnk = ecore_file_readlink(buf);
             /* if so use just the filename as the profile - must be a local link */
             if (lnk)
               {
                  _e_config_profile = eina_stringshare_add(ecore_file_file_get(lnk));
                  free(lnk);
               }
             else
               _e_config_profile = eina_stringshare_add("default");
          }
        if (!getenv("E_CONF_PROFILE"))
          e_util_env_set("E_CONF_PROFILE", _e_config_profile);
     }

   _e_config_bindings_mouse_edd = E_CONFIG_DD_NEW("E_Config_Binding_Mouse",
                                                  E_Config_Binding_Mouse);
#undef T
#undef D
#define T E_Config_Binding_Mouse
#define D _e_config_bindings_mouse_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, modifiers, INT);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);
   E_CONFIG_VAL(D, T, button, UCHAR);
   E_CONFIG_VAL(D, T, any_mod, UCHAR);

   _e_config_bindings_key_edd = E_CONFIG_DD_NEW("E_Config_Binding_Key",
                                                E_Config_Binding_Key);
#undef T
#undef D
#define T E_Config_Binding_Key
#define D _e_config_bindings_key_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, modifiers, INT);
   E_CONFIG_VAL(D, T, key, STR);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);
   E_CONFIG_VAL(D, T, any_mod, UCHAR);

   _e_config_bindings_edge_edd = E_CONFIG_DD_NEW("E_Config_Binding_Edge",
                                                 E_Config_Binding_Edge);
#undef T
#undef D
#define T E_Config_Binding_Edge
#define D _e_config_bindings_edge_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, modifiers, INT);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);
   E_CONFIG_VAL(D, T, edge, UCHAR);
   E_CONFIG_VAL(D, T, any_mod, UCHAR);
   E_CONFIG_VAL(D, T, drag_only, UCHAR);
   E_CONFIG_VAL(D, T, delay, FLOAT);

   _e_config_bindings_signal_edd = E_CONFIG_DD_NEW("E_Config_Binding_Signal",
                                                   E_Config_Binding_Signal);
#undef T
#undef D
#define T E_Config_Binding_Signal
#define D _e_config_bindings_signal_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, signal, STR);
   E_CONFIG_VAL(D, T, source, STR);
   E_CONFIG_VAL(D, T, modifiers, INT);
   E_CONFIG_VAL(D, T, any_mod, UCHAR);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);

   _e_config_bindings_wheel_edd = E_CONFIG_DD_NEW("E_Config_Binding_Wheel",
                                                  E_Config_Binding_Wheel);
#undef T
#undef D
#define T E_Config_Binding_Wheel
#define D _e_config_bindings_wheel_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, direction, INT);
   E_CONFIG_VAL(D, T, z, INT);
   E_CONFIG_VAL(D, T, modifiers, INT);
   E_CONFIG_VAL(D, T, any_mod, UCHAR);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);

   _e_config_bindings_acpi_edd = E_CONFIG_DD_NEW("E_Config_Binding_Acpi",
                                                 E_Config_Binding_Acpi);
#undef T
#undef D
#define T E_Config_Binding_Acpi
#define D _e_config_bindings_acpi_edd
   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, type, INT);
   E_CONFIG_VAL(D, T, status, INT);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);

   _e_config_bindings_swipe_edd = E_CONFIG_DD_NEW("E_Config_Binding_Swipe",
                                                 E_Config_Binding_Swipe);
#undef T
#undef D
#define T E_Config_Binding_Swipe
#define D _e_config_bindings_swipe_edd

   E_CONFIG_VAL(D, T, context, INT);
   E_CONFIG_VAL(D, T, fingers, UINT);
   E_CONFIG_VAL(D, T, direction, DOUBLE);
   E_CONFIG_VAL(D, T, length, DOUBLE);
   E_CONFIG_VAL(D, T, error, DOUBLE);
   E_CONFIG_VAL(D, T, action, STR);
   E_CONFIG_VAL(D, T, params, STR);

   _e_config_edd_init(EINA_FALSE);

   _e_config_binding_edd = E_CONFIG_DD_NEW("E_Config_Bindings", E_Config_Bindings);
#undef T
#undef D
#define T E_Config_Bindings
#define D _e_config_binding_edd
   E_CONFIG_VAL(D, T, config_version, UINT); /**/
   E_CONFIG_LIST(D, T, mouse_bindings, _e_config_bindings_mouse_edd); /**/
   E_CONFIG_LIST(D, T, key_bindings, _e_config_bindings_key_edd); /**/
   E_CONFIG_LIST(D, T, edge_bindings, _e_config_bindings_edge_edd); /**/
   E_CONFIG_LIST(D, T, signal_bindings, _e_config_bindings_signal_edd); /**/
   E_CONFIG_LIST(D, T, wheel_bindings, _e_config_bindings_wheel_edd); /**/
   E_CONFIG_LIST(D, T, acpi_bindings, _e_config_bindings_acpi_edd); /**/
   E_CONFIG_LIST(D, T, swipe_bindings, _e_config_bindings_swipe_edd); /**/

   e_config_load();

   e_config_save_queue();

   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_UPDATE,
                         _e_config_cb_efreet_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_ICON_CACHE_UPDATE,
                         _e_config_cb_efreet_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_CONFIG_ICON_THEME,
                         _e_config_cb_efreet_cache_update, NULL);

   return 1;
}

EINTERN int
e_config_shutdown(void)
{
   E_FREE_LIST(handlers, ecore_event_handler_del);
   eina_stringshare_del(_e_config_profile);
   E_CONFIG_DD_FREE(_e_config_binding_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_mouse_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_key_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_edge_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_signal_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_wheel_edd);
   E_CONFIG_DD_FREE(_e_config_bindings_acpi_edd);
   _e_config_edd_shutdown();
   return 1;
}

E_API void
e_config_load(void)
{
   int reload = 0;

   e_config = e_config_domain_load("e", _e_config_edd);
   if (e_config)
     {
        /* major version change - that means wipe and restart */
        if ((e_config->config_version) < E_CONFIG_FILE_EPOCH * 1000000)
          {
             /* your config is too old - need new defaults */
             _e_config_free(e_config);
             e_config = NULL;
             reload = 1;
             ecore_timer_loop_add(1.0, _e_config_cb_timer,
                             _("Settings data needed upgrading. Your old settings have<ps/>"
                               "been wiped and a new set of defaults initialized. This<ps/>"
                               "will happen regularly during development, so don't report a<ps/>"
                               "bug. This simply means Enlightenment needs new settings<ps/>"
                               "data by default for usable functionality that your old<ps/>"
                               "settings simply lack. This new set of defaults will fix<ps/>"
                               "that by adding it in. You can re-configure things now to your<ps/>"
                               "liking. Sorry for the hiccup in your settings.<ps/>"));
          }
        /* config is too new? odd! suspect corruption? */
        else if (e_config->config_version > E_CONFIG_FILE_VERSION)
          {
             /* your config is too new - what the fuck??? */
             _e_config_free(e_config);
             e_config = NULL;
             reload = 1;
             ecore_timer_loop_add(1.0, _e_config_cb_timer,
                             _("Your settings are NEWER than Enlightenment. This is very<ps/>"
                               "strange. This should not happen unless you downgraded<ps/>"
                               "Enlightenment or copied the settings from a place where<ps/>"
                               "a newer version of Enlightenment was running. This is bad and<ps/>"
                               "as a precaution your settings have been now restored to<ps/>"
                               "defaults. Sorry for the inconvenience.<ps/>"));
          }
        if (reload)
          {
             e_config_profile_del(e_config_profile_get());
             e_config_profile_set("default");
             e_config = e_config_domain_load("e", _e_config_edd);
          }
     }
   while (!e_config)
     {
        _e_config_edd_shutdown();
        _e_config_edd_init(EINA_TRUE);
        e_config = e_config_domain_load("e", _e_config_edd);
        /* I made a c&p error here and fucked the world, so this ugliness
         * will be my public mark of shame until E :/
         * -zmike, 2013
         */
        if (e_config)
          {
             Eina_List *l;
             E_Config_XKB_Layout *cl;
             int set = 0;

             /* this is essentially CONFIG_VERSION_CHECK(7) */
             INF("Performing config upgrade to %d.%d", 1, 7);
             _e_config_edd_shutdown();
             _e_config_edd_init(EINA_FALSE);
             set += !!e_config->xkb.current_layout;
             set += !!e_config->xkb.sel_layout;
             set += !!e_config->xkb.lock_layout;
             EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
               {
                  if (e_config->xkb.current_layout && (e_config->xkb.current_layout->name == cl->name))
                    {
                       e_config->xkb.current_layout->model = eina_stringshare_ref(cl->model);
                       e_config->xkb.current_layout->variant = eina_stringshare_ref(cl->variant);
                       set--;
                    }
                  if (e_config->xkb.sel_layout && (e_config->xkb.sel_layout->name == cl->name))
                    {
                       e_config->xkb.sel_layout->model = eina_stringshare_ref(cl->model);
                       e_config->xkb.sel_layout->variant = eina_stringshare_ref(cl->variant);
                       set--;
                    }
                  if (e_config->xkb.lock_layout && (e_config->xkb.lock_layout->name == cl->name))
                    {
                       e_config->xkb.lock_layout->model = eina_stringshare_ref(cl->model);
                       e_config->xkb.lock_layout->variant = eina_stringshare_ref(cl->variant);
                       set--;
                    }
                  if (!set) break;
               }
             break;
          }
#undef T
#undef D
        e_config_profile_set("default");
        if (!reload) e_config_profile_del(e_config_profile_get());
        e_config_save_block_set(1);
        exit(111);
        //e_sys_action_do(E_SYS_RESTART, NULL);
        return;
     }

#define CONFIG_VERSION_CHECK(VERSION) \
  if ((e_config->config_version - (E_CONFIG_FILE_EPOCH * 1000000)) < (VERSION))

#define CONFIG_VERSION_UPDATE_INFO(VERSION) \
  INF("Performing config upgrade for %d.%d", E_CONFIG_FILE_EPOCH, VERSION);

   /* this should be 6, an xkb fix fucked up the ordering and this is now really broken */
   CONFIG_VERSION_CHECK(8)
     {
        /* e_bindings config domain didn't exist before this version, so we have to do this
         * check before we load or else we wipe configs :(
         */
#undef SET
#define SET(X) e_bindings->X = e_config->X, e_config->X = NULL

        //CONFIG_VERSION_UPDATE_INFO(6);
        if (e_config->mouse_bindings || e_config->key_bindings || e_config->edge_bindings ||
            e_config->signal_bindings || e_config->wheel_bindings || e_config->acpi_bindings)
          {
             e_bindings = E_NEW(E_Config_Bindings, 1);
             SET(mouse_bindings);
             SET(key_bindings);
             SET(edge_bindings);
             SET(signal_bindings);
             SET(wheel_bindings);
             SET(acpi_bindings);
             e_config_domain_save("e_bindings", _e_config_binding_edd, e_bindings);
#undef SET
          }
        else
          e_bindings = e_config_domain_load("e_bindings", _e_config_binding_edd);
     }
   else
     e_bindings = e_config_domain_load("e_bindings", _e_config_binding_edd);

   if ((!e_bindings) || (e_bindings->config_version != E_CONFIG_BINDINGS_VERSION))
     {
        Eina_Stringshare *prof;

        e_config_bindings_free(e_bindings);
        prof = eina_stringshare_ref(e_config_profile_get());
        e_config_profile_set("standard");
        e_bindings = e_config_domain_system_load("e_bindings", _e_config_binding_edd);
        e_config_profile_set(prof);
        eina_stringshare_del(prof);
        ecore_timer_loop_add(1.0, _e_config_cb_timer,
                        _("Your bindings settings version does not match the current settings version.<ps/>"
                          "As a result, all bindings have been reloaded from defaults.<ps/>"
                          "Sorry for the inconvenience.<ps/>"));
     }

   if (e_config->config_version < E_CONFIG_FILE_VERSION)
     {
        CONFIG_VERSION_CHECK(5)
          {
             E_Config_XKB_Layout *cl;
             Eina_List *l;

            CONFIG_VERSION_UPDATE_INFO(5);
            if (e_config->xkb.cur_layout || e_config->xkb.selected_layout || e_config->xkb.desklock_layout)
              {
                 EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
                   {
                      if (cl->name == e_config->xkb.cur_layout)
                        e_config->xkb.current_layout = e_config_xkb_layout_dup(cl);
                      if (cl->name == e_config->xkb.selected_layout)
                        e_config->xkb.sel_layout = e_config_xkb_layout_dup(cl);
                      if (cl->name == e_config->xkb.desklock_layout)
                        e_config->xkb.lock_layout = e_config_xkb_layout_dup(cl);
                      if (((!!e_config->xkb.current_layout) == (!!e_config->xkb.cur_layout)) &&
                          ((!!e_config->xkb.sel_layout) == (!!e_config->xkb.selected_layout)) &&
                          ((!!e_config->xkb.lock_layout) == (!!e_config->xkb.desklock_layout)))
                        break;
                   }
              }
          }
/* this gets done above but I'm leaving it here so it can be seen
        CONFIG_VERSION_CHECK(6)
          {
             CONFIG_VERSION_UPDATE_INFO(6);
             e_bindings = E_NEW(E_Config_Bindings, 1);
#undef SET
#define SET(X) e_bindings->X = e_config->X, e_config->X = NULL

             SET(mouse_bindings);
             SET(key_bindings);
             SET(edge_bindings);
             SET(signal_bindings);
             SET(wheel_bindings);
             SET(acpi_bindings);
#undef SET
             e_config_domain_save("e_bindings", _e_config_binding_edd, e_bindings);
          }
*/
        CONFIG_VERSION_CHECK(8)
          {
             CONFIG_VERSION_UPDATE_INFO(8);
             if (!e_config->config_type)
               {
                  /* I guess this probably isn't great, but whatever */
                  if (eina_list_count(e_bindings->key_bindings) > 2)
                    e_config->config_type = E_CONFIG_PROFILE_TYPE_DESKTOP;
                  else
                    e_config->config_type = E_CONFIG_PROFILE_TYPE_TABLET;
               }
          }
        CONFIG_VERSION_CHECK(10)
          {
             int do_conf = 0;
             Eina_List *l, *ll;
             E_Config_Module *em;
             int enabled = 0;

             CONFIG_VERSION_UPDATE_INFO(10);
             EINA_LIST_FOREACH_SAFE(e_config->modules, l, ll, em)
               {
                  Eina_Bool do_free = EINA_FALSE;

                  if (!e_util_strcmp(em->name, "comp"))
                    do_free = EINA_TRUE;
                  else if ((!e_util_strcmp(em->name, "conf_keybindings")) || (!e_util_strcmp(em->name, "conf_edgebindings")))
                    {
                       do_conf += do_free = EINA_TRUE;
                       enabled |= em->enabled;
                    }
                  if (do_free)
                    {
                       e_config->modules = eina_list_remove_list(e_config->modules, l);
                       eina_stringshare_del(em->name);
                       free(em);
                    }
                  if (do_conf == 2) break;
               }
             if (do_conf)
               {
                  em = E_NEW(E_Config_Module, 1);
                  em->name = eina_stringshare_add("conf_bindings");
                  em->enabled = !!enabled;
                  e_config->modules = eina_list_append(e_config->modules, em);
               }
          }
        CONFIG_VERSION_CHECK(11)
          {
             CONFIG_VERSION_UPDATE_INFO(11);
             e_config->pointer_warp_speed = e_config->winlist_warp_speed;
             e_config->winlist_warp_speed = 0;

             if (e_config->focus_policy == E_FOCUS_CLICK)
               {
                  if (e_config->border_raise_on_focus)
                    {
                       /* approximate expected behavior from removed option */
                       e_config->always_click_to_focus = 1;
                       e_config->always_click_to_raise = 1;
                    }
               }
          }
        CONFIG_VERSION_CHECK(12)
          {
             CONFIG_VERSION_UPDATE_INFO(12);
             switch (e_config->desk_flip_animate_mode)
               {
                case 1: //pane
                  e_config->desk_flip_animate_type = eina_stringshare_add("auto/pane");
                  break;
                case 2: //zoom, now known as diagonal
                  e_config->desk_flip_animate_type = eina_stringshare_add("auto/diagonal");
                  break;
                default:
                  break;
               }
          }
        CONFIG_VERSION_CHECK(14)
          {
             Eina_List *files, *l;
             Eina_Bool fail = EINA_FALSE;
             E_Config_Shelf *cf_es;
             E_Remember *rem;
             char buf[PATH_MAX], buf2[PATH_MAX], *f;

             CONFIG_VERSION_UPDATE_INFO(14);

             EINA_LIST_FOREACH(e_config->shelves, l, cf_es)
               {
                  if (cf_es->popup)
                    {
                       if (cf_es->layer)
                         cf_es->layer = E_LAYER_CLIENT_ABOVE;
                       else
                         cf_es->layer = E_LAYER_CLIENT_DESKTOP;
                    }
                  else if (!cf_es->layer)
                    cf_es->layer = E_LAYER_DESKTOP; //redundant, but whatever
                  cf_es->popup = 0;
               }

             /* E layer values are higher */
             EINA_LIST_FOREACH(e_config->remembers, l, rem)
               if (rem->apply & E_REMEMBER_APPLY_LAYER)
                 rem->prop.layer += 100;

             // copy all of ~/.e/e/themes/* into ~/.elementary/themes
             // and delete original data in ~/.e/e/themes
             ecore_file_mkpath(elm_theme_user_dir_get());
             snprintf(buf, sizeof(buf), "%s/themes", e_user_dir_get());
             files = ecore_file_ls(buf);
             EINA_LIST_FREE(files, f)
               {
                  snprintf(buf, sizeof(buf), "%s/themes/%s",
                           e_user_dir_get(), f);
                  snprintf(buf2, sizeof(buf2), "%s/%s",
                           elm_theme_user_dir_get(), f);
                  if (!ecore_file_cp(buf, buf2)) fail = EINA_TRUE;
               }
             if (!fail)
               {
                  snprintf(buf, sizeof(buf), "%s/themes", e_user_dir_get());
                  ecore_file_recursive_rm(buf);
               }
          }
        CONFIG_VERSION_CHECK(15)
          {
             E_Config_Module *em;
             Eina_List *l;
             Eina_Bool found = EINA_FALSE;

             CONFIG_VERSION_UPDATE_INFO(15);

             EINA_LIST_FOREACH(e_config->modules, l, em)
               if (!strcmp(em->name, "lokker"))
                 {
                    found = EINA_TRUE;
                    break;
                 }
             if (!found)
               {
                  /* add new desklock module */
                  em = E_NEW(E_Config_Module, 1);
                  em->name = eina_stringshare_add("lokker");
                  em->enabled = 1;
                  e_config->modules = eina_list_append(e_config->modules, em);
               }
          }
        CONFIG_VERSION_CHECK(17)
          {
             E_Config_Module *em;
             Eina_List *l;

             CONFIG_VERSION_UPDATE_INFO(17);

             EINA_LIST_FOREACH(e_config->modules, l, em)
               if (!strcmp(em->name, "pager16"))
                 {
                    eina_stringshare_replace(&em->name, "pager");
                    break;
                 }
          }
        CONFIG_VERSION_CHECK(18)
          {
             E_Color_Class *ecc;

             CONFIG_VERSION_UPDATE_INFO(18);
             EINA_LIST_FREE(e_config->color_classes, ecc)
               {
                  elm_config_color_overlay_set(ecc->name, ecc->r, ecc->g, ecc->b, ecc->a, ecc->r2, ecc->g2, ecc->b2, ecc->a2, ecc->r3, ecc->g3, ecc->b3, ecc->a3);
                  eina_stringshare_del(ecc->name);
                  free(ecc);
               }
          }
        CONFIG_VERSION_CHECK(19)
          {
             CONFIG_VERSION_UPDATE_INFO(19);

             /* set (400, 25) as the default values of repeat delay, rate */
             e_config->keyboard.repeat_delay = 400;
             e_config->keyboard.repeat_rate = 25;
          }
        CONFIG_VERSION_CHECK(20)
          {
             CONFIG_VERSION_UPDATE_INFO(20);
             e_config_save_queue();
          }

          CONFIG_VERSION_CHECK(21)
            {
               CONFIG_VERSION_UPDATE_INFO(21);

               e_config->window_maximize_animate = 1;
               e_config->window_maximize_transition = E_EFX_EFFECT_SPEED_SINUSOIDAL;
               e_config->window_maximize_time = 0.15;
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(22)
            { // added luncher - not around anymore
            }
          CONFIG_VERSION_CHECK(23)
            { // added sysinfo - not around anymore
            }
          CONFIG_VERSION_CHECK(24)
            {
               CONFIG_VERSION_UPDATE_INFO(24);

               if (!elm_config_profile_exists(_e_config_profile))
                 elm_config_profile_save(_e_config_profile);
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(25)
            {
               Eina_List *l;
               E_Config_Binding_Edge *ebe;

               CONFIG_VERSION_UPDATE_INFO(25);
               EINA_LIST_FOREACH(e_bindings->edge_bindings, l, ebe)
                 {
                    if ((ebe->context == E_BINDING_CONTEXT_ZONE) &&
                        (ebe->modifiers == 0) &&
                        (ebe->edge >= 1) && (ebe->edge <= 4) &&
                        (!ebe->any_mod) &&
                        (ebe->action) &&
                        (!strcmp(ebe->action, "desk_flip_in_direction")))
                      {
                         ebe->any_mod = 1;
                      }
                 }
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(26)
            {
               // move from bluez4 to bluez5 automatically. you can manually
               // move back if that works better for you...
               Eina_List *l, *ll;
               E_Config_Module *em;
               E_Config_Gadcon *gc;
               E_Config_Gadcon_Client *gcc;

               CONFIG_VERSION_UPDATE_INFO(26);
               EINA_LIST_FOREACH(e_config->modules, l, em)
                 {
                    if (!em->enabled) continue;
                    if (!em->name) continue;
                    if (eina_streq(em->name, "bluez4"))
                      {
                         eina_stringshare_replace(&(em->name), "bluez5");
                      }
                 }
               EINA_LIST_FOREACH(e_config->gadcons, l, gc)
                 {
                    EINA_LIST_FOREACH(gc->clients, ll, gcc)
                      {
                         if (!gcc->name) continue;
                         if (!strncmp(gcc->name, "bluez4", 6))
                           {
                              char *tmp = strdup(gcc->name);
                              if (tmp)
                                {
                                   tmp[5] = '5';
                                   eina_stringshare_replace(&(gcc->name), tmp);
                                   free(tmp);
                                }
                           }
                      }
                 }
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(28)
            {
               CONFIG_VERSION_UPDATE_INFO(28);
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(29)
            {
               Eina_List *l;
               E_Config_Module *em, *module;
               Eina_Bool mod_loaded = EINA_FALSE;

               CONFIG_VERSION_UPDATE_INFO(29);

               EINA_LIST_FOREACH(e_config->modules, l, em)
                 {
                    if (!em->enabled) continue;
                    if (eina_streq(em->name, "polkit"))
                      mod_loaded = EINA_TRUE;
                 }
               if (!mod_loaded)
                 {
                    module = E_NEW(E_Config_Module, 1);
                    module->name = eina_stringshare_add("polkit");
                    module->enabled = 1;
                    e_config->modules = eina_list_append(e_config->modules, module);
                 }
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(30)
            {
               Eina_List *l;
               E_Config_Module *em;

               CONFIG_VERSION_UPDATE_INFO(30);
               EINA_LIST_FOREACH(e_config->modules, l, em)
                 {
                    if (!em->enabled) continue;
                    if (!em->name) continue;
                    if (eina_streq(em->name, "pager_plain"))
                      {
                         eina_stringshare_replace(&(em->name), "pager");
                      }
                 }
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(32)
            {
               CONFIG_VERSION_UPDATE_INFO(32);
               e_config->winlist_large_size = 0.6666;
               e_config->winlist_list_size = 0.3333;
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(33)
            {
               CONFIG_VERSION_UPDATE_INFO(33);
               e_config->mouse_emulate_middle_button = 1;
               e_config->touch_emulate_middle_button = 1;
               e_config->touch_clickpad = 1;
               e_config->touch_scrolling_2finger = 1;
               e_config->touch_scrolling_edge = 0;
               e_config->touch_scrolling_horiz = 1;
               e_config->touch_palm_detect = 1;
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(34)
            {
               CONFIG_VERSION_UPDATE_INFO(34);
               e_config->scale.xapp_base_dpi = 75;
               e_config->scale.set_xapp_dpi = 1;
               e_config_save_queue();
            }
          CONFIG_VERSION_CHECK(35)
            {
               CONFIG_VERSION_UPDATE_INFO(35);
               e_config->backlight.ddc = 1;
               e_config_save_queue();
            }
     }
   elm_config_profile_set(_e_config_profile);
   if (!e_config->remember_internal_fm_windows)
     e_config->remember_internal_fm_windows = !!(e_config->remember_internal_windows & E_REMEMBER_INTERNAL_FM_WINS);

   e_bindings->config_version = E_CONFIG_BINDINGS_VERSION;
   e_config->config_version = E_CONFIG_FILE_VERSION;

   /* limit values so they are sane */
   E_CONFIG_LIMIT(e_config->menus_scroll_speed, 1.0, 20000.0);
   E_CONFIG_LIMIT(e_config->menus_fast_mouse_move_threshhold, 1.0, 2000.0);
   E_CONFIG_LIMIT(e_config->menus_click_drag_timeout, 0.0, 10.0);
   E_CONFIG_LIMIT(e_config->window_maximize_animate, 0, 1);
   E_CONFIG_LIMIT(e_config->window_maximize_transition, 0, E_EFX_EFFECT_SPEED_SINUSOIDAL);
   E_CONFIG_LIMIT(e_config->window_maximize_time, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->border_shade_animate, 0, 1);
   E_CONFIG_LIMIT(e_config->border_shade_transition, 0, 8);
   E_CONFIG_LIMIT(e_config->border_shade_speed, 1.0, 20000.0);
   E_CONFIG_LIMIT(e_config->priority, 0, 19);
   E_CONFIG_LIMIT(e_config->zone_desks_x_count, 1, 64);
   E_CONFIG_LIMIT(e_config->zone_desks_y_count, 1, 64);
   E_CONFIG_LIMIT(e_config->show_desktop_icons, 0, 1);
   E_CONFIG_LIMIT(e_config->edge_flip_dragging, 0, 1);
   E_CONFIG_LIMIT(e_config->window_placement_policy, E_WINDOW_PLACEMENT_SMART, E_WINDOW_PLACEMENT_MANUAL);
   E_CONFIG_LIMIT(e_config->window_grouping, 0, 1);
   E_CONFIG_LIMIT(e_config->focus_policy, 0, 2);
   E_CONFIG_LIMIT(e_config->focus_setting, 0, 3);
   E_CONFIG_LIMIT(e_config->pass_click_on, 0, 1);
   E_CONFIG_LIMIT(e_config->window_activehint_policy, E_ACTIVEHINT_POLICY_IGNORE, E_ACTIVEHINT_POLICY_LAST - 1);
   E_CONFIG_LIMIT(e_config->always_click_to_raise, 0, 1);
   E_CONFIG_LIMIT(e_config->always_click_to_focus, 0, 1);
   E_CONFIG_LIMIT(e_config->use_auto_raise, 0, 1);
   E_CONFIG_LIMIT(e_config->auto_raise_delay, 0.0, 9.9);
   E_CONFIG_LIMIT(e_config->use_resist, 0, 1);
   E_CONFIG_LIMIT(e_config->drag_resist, 0, 100);
   E_CONFIG_LIMIT(e_config->desk_resist, 0, 100);
   E_CONFIG_LIMIT(e_config->window_resist, 0, 100);
   E_CONFIG_LIMIT(e_config->gadget_resist, 0, 100);
   E_CONFIG_LIMIT(e_config->geometry_auto_move, 0, 1);
   E_CONFIG_LIMIT(e_config->geometry_auto_resize_limit, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_warp_while_selecting, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_warp_at_end, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_no_warp_on_direction, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_warp_speed, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->winlist_scroll_animate, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_scroll_speed, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->winlist_list_show_iconified, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_show_other_desk_iconified, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_show_other_screen_iconified, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_show_other_desk_windows, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_show_other_screen_windows, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_uncover_while_selecting, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_list_jump_desk_while_selecting, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_mode, 0, 1);
   E_CONFIG_LIMIT(e_config->winlist_large_size, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->winlist_list_size, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->maximize_policy, E_MAXIMIZE_FULLSCREEN, E_MAXIMIZE_DIRECTION);
   E_CONFIG_LIMIT(e_config->allow_manip, 0, 1);
   E_CONFIG_LIMIT(e_config->border_fix_on_shelf_toggle, 0, 1);
   E_CONFIG_LIMIT(e_config->allow_above_fullscreen, 0, 1);
   E_CONFIG_LIMIT(e_config->kill_if_close_not_possible, 0, 1);
   E_CONFIG_LIMIT(e_config->kill_process, 0, 1);
   E_CONFIG_LIMIT(e_config->kill_timer_wait, 0.0, 120.0);
   E_CONFIG_LIMIT(e_config->ping_clients, 0, 1);
   E_CONFIG_LIMIT(e_config->move_info_follows, 0, 1);
   E_CONFIG_LIMIT(e_config->resize_info_follows, 0, 1);
   E_CONFIG_LIMIT(e_config->move_info_visible, 0, 1);
   E_CONFIG_LIMIT(e_config->resize_info_visible, 0, 1);
   E_CONFIG_LIMIT(e_config->focus_last_focused_per_desktop, 0, 1);
   E_CONFIG_LIMIT(e_config->focus_revert_on_hide_or_close, 0, 1);
   E_CONFIG_LIMIT(e_config->focus_revert_allow_sticky, 0, 1);
   E_CONFIG_LIMIT(e_config->pointer_slide, 0, 1);
   E_CONFIG_LIMIT(e_config->disable_all_pointer_warps, 0, 1);
   E_CONFIG_LIMIT(e_config->pointer_warp_speed, 0.0, 1.0);
   E_CONFIG_LIMIT(e_config->show_cursor, 0, 1);
   E_CONFIG_LIMIT(e_config->use_e_cursor, 0, 1);
   E_CONFIG_LIMIT(e_config->cursor_size, 0, 1024);
   E_CONFIG_LIMIT(e_config->menu_autoscroll_margin, 0, 50);
   E_CONFIG_LIMIT(e_config->menu_autoscroll_cursor_margin, 0, 50);
   E_CONFIG_LIMIT(e_config->menu_eap_name_show, 0, 1);
   E_CONFIG_LIMIT(e_config->menu_eap_generic_show, 0, 1);
   E_CONFIG_LIMIT(e_config->menu_eap_comment_show, 0, 1);
   E_CONFIG_LIMIT(e_config->use_app_icon, 0, 1);
   E_CONFIG_LIMIT(e_config->cnfmdlg_disabled, 0, 1);
   E_CONFIG_LIMIT(e_config->cfgdlg_auto_apply, 0, 1);
   E_CONFIG_LIMIT(e_config->cfgdlg_default_mode, 0, 1);
   E_CONFIG_LIMIT(e_config->font_hinting, 0, 2);
   E_CONFIG_LIMIT(e_config->desklock_login_box_zone, -2, 1000);
   E_CONFIG_LIMIT(e_config->desklock_autolock_screensaver, 0, 1);
   E_CONFIG_LIMIT(e_config->desklock_post_screensaver_time, 0.0, 300.0);
   E_CONFIG_LIMIT(e_config->desklock_ask_presentation, 0, 1);
   E_CONFIG_LIMIT(e_config->desklock_ask_presentation_timeout, 1.0, 300.0);
   E_CONFIG_LIMIT(e_config->border_raise_on_mouse_action, 0, 1);
   E_CONFIG_LIMIT(e_config->border_raise_on_focus, 0, 1);
   E_CONFIG_LIMIT(e_config->raise_on_revert_focus, 0, 1);
   E_CONFIG_LIMIT(e_config->desk_flip_wrap, 0, 1);
   E_CONFIG_LIMIT(e_config->fullscreen_flip, 0, 1);
   E_CONFIG_LIMIT(e_config->icon_theme_overrides, 0, 1);
   E_CONFIG_LIMIT(e_config->remember_internal_windows, 0, 3);
   E_CONFIG_LIMIT(e_config->remember_internal_fm_windows, 0, 1);
   E_CONFIG_LIMIT(e_config->remember_internal_fm_windows_globally, 0, 1);
   E_CONFIG_LIMIT(e_config->desk_auto_switch, 0, 1);

   E_CONFIG_LIMIT(e_config->screen_limits, 0, 2);
   E_CONFIG_LIMIT(e_config->thumb_nice, -20, 20);
   E_CONFIG_LIMIT(e_config->gesture_open_input_devices, 0, 1);

   E_CONFIG_LIMIT(e_config->dpms_enable, 0, 1);
   E_CONFIG_LIMIT(e_config->dpms_standby_enable, 0, 1);
   E_CONFIG_LIMIT(e_config->dpms_suspend_enable, 0, 1);
   E_CONFIG_LIMIT(e_config->dpms_off_enable, 0, 1);
   E_CONFIG_LIMIT(e_config->dpms_standby_timeout, 5, 5400);
   E_CONFIG_LIMIT(e_config->dpms_suspend_timeout, 5, 5400);
   E_CONFIG_LIMIT(e_config->dpms_off_timeout, 5, 5400);

   E_CONFIG_LIMIT(e_config->scale.min, 0.1, 40.0);
   E_CONFIG_LIMIT(e_config->scale.max, 0.1, 40.0);
   E_CONFIG_LIMIT(e_config->scale.factor, 0.1, 40.0);
   E_CONFIG_LIMIT(e_config->scale.base_dpi, 10, 4000);
   E_CONFIG_LIMIT(e_config->scale.xapp_base_dpi, 10, 4000);

   E_CONFIG_LIMIT(e_config->backlight.timer, 1, 3600);

   E_CONFIG_LIMIT(e_config->screensaver_timeout, 6, 5400);
   E_CONFIG_LIMIT(e_config->screensaver_interval, 0, 5400);
   E_CONFIG_LIMIT(e_config->screensaver_blanking, 0, 2);
   E_CONFIG_LIMIT(e_config->screensaver_expose, 0, 2);
   E_CONFIG_LIMIT(e_config->screensaver_ask_presentation, 0, 1);
   E_CONFIG_LIMIT(e_config->screensaver_ask_presentation_timeout, 1.0, 300.0);

   E_CONFIG_LIMIT(e_config->screensaver_wake_on_notify, 0, 1);
   E_CONFIG_LIMIT(e_config->screensaver_wake_on_urgent, 0, 1);

   E_CONFIG_LIMIT(e_config->clientlist_group_by, 0, 2);
   E_CONFIG_LIMIT(e_config->clientlist_include_all_zones, 0, 1);
   E_CONFIG_LIMIT(e_config->clientlist_separate_with, 0, 2);
   E_CONFIG_LIMIT(e_config->clientlist_sort_by, 0, 3);
   E_CONFIG_LIMIT(e_config->clientlist_separate_iconified_apps, 0, 2);
   E_CONFIG_LIMIT(e_config->clientlist_warp_to_iconified_desktop, 0, 1);
   E_CONFIG_LIMIT(e_config->mouse_hand, 0, 1);
   E_CONFIG_LIMIT(e_config->clientlist_limit_caption_len, 0, 1);
   E_CONFIG_LIMIT(e_config->clientlist_max_caption_len, 2, E_CLIENTLIST_MAX_CAPTION_LEN);

   E_CONFIG_LIMIT(e_config->mouse_accel, -1.0, 1.0);
   E_CONFIG_LIMIT(e_config->mouse_accel_threshold, 0, 10);
   E_CONFIG_LIMIT(e_config->touch_tap_to_click, 0, 1);

   E_CONFIG_LIMIT(e_config->mouse_natural_scroll, 0, 1);
   E_CONFIG_LIMIT(e_config->mouse_emulate_middle_button, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_clickpad, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_scrolling_2finger, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_scrolling_edge, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_scrolling_circular, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_scrolling_horiz, 0, 1);
   E_CONFIG_LIMIT(e_config->touch_palm_detect, 0, 1);

   E_CONFIG_LIMIT(e_config->menu_favorites_show, 0, 1);
   E_CONFIG_LIMIT(e_config->menu_apps_show, 0, 1);
   E_CONFIG_LIMIT(e_config->menu_gadcon_client_toplevel, 0, 1);

   E_CONFIG_LIMIT(e_config->ping_clients_interval, 16, 1024);

   E_CONFIG_LIMIT(e_config->exec.expire_timeout, 0.1, 1000);
   E_CONFIG_LIMIT(e_config->exec.show_run_dialog, 0, 1);
   E_CONFIG_LIMIT(e_config->exec.show_exit_dialog, 0, 1);

   E_CONFIG_LIMIT(e_config->null_container_win, 0, 1);

   E_CONFIG_LIMIT(e_config->powersave.none, 0.01, 5400.00);
   E_CONFIG_LIMIT(e_config->powersave.low, 0.01, 5400.00);
   E_CONFIG_LIMIT(e_config->powersave.medium, 0.01, 5400.00);
   E_CONFIG_LIMIT(e_config->powersave.high, 0.01, 5400.00);
   E_CONFIG_LIMIT(e_config->powersave.extreme, 0.01, 5400.00);
   E_CONFIG_LIMIT(e_config->powersave.min, E_POWERSAVE_MODE_NONE, E_POWERSAVE_MODE_EXTREME);
   E_CONFIG_LIMIT(e_config->powersave.max, E_POWERSAVE_MODE_NONE, E_POWERSAVE_MODE_EXTREME);

   E_CONFIG_LIMIT(e_config->border_keyboard.move.dx, 1, 255);
   E_CONFIG_LIMIT(e_config->border_keyboard.move.dy, 1, 255);
   E_CONFIG_LIMIT(e_config->border_keyboard.resize.dx, 1, 255);
   E_CONFIG_LIMIT(e_config->border_keyboard.resize.dy, 1, 255);

   E_CONFIG_LIMIT(e_config->multiscreen_flip, 0, 1);

   E_CONFIG_LIMIT(e_config->backlight.normal, 0.05, 1.0);
   E_CONFIG_LIMIT(e_config->backlight.dim, 0.05, 1.0);
   E_CONFIG_LIMIT(e_config->backlight.idle_dim, 0, 1);
   E_CONFIG_LIMIT(e_config->backlight.ddc, 0, 1);

   E_CONFIG_LIMIT(e_config->keyboard.repeat_delay, -1, 1000); // 1 second
   E_CONFIG_LIMIT(e_config->keyboard.repeat_rate, -1, 1000); // 1 second

   if (!e_config->icon_theme)
     e_config->icon_theme = eina_stringshare_add("hicolor");  // FDO default

   /* FIXME: disabled auto apply because it causes problems */
   e_config->cfgdlg_auto_apply = 0;

   ecore_event_add(E_EVENT_CONFIG_LOADED, NULL, NULL, NULL);
}

E_API int
e_config_save(void)
{
   E_FREE_FUNC(_e_config_save_defer, e_powersave_deferred_action_del);
   _e_config_save_cb(NULL);
   return e_config_domain_save("e", _e_config_edd, e_config);
}

E_API void
e_config_save_flush(void)
{
   if (_e_config_save_defer)
     {
        e_powersave_deferred_action_del(_e_config_save_defer);
        _e_config_save_defer = NULL;
        _e_config_save_cb(NULL);
     }
   if (!e_main_loop_running)
     {
        _e_config_save_thread_send(E_CONFIG_SAVE_THREAD_QUIT, NULL, NULL);
        // wait for save thread to exit...
        ecore_main_loop_begin();
     }
}

E_API void
e_config_save_queue(void)
{
   if (_e_config_save_defer)
     e_powersave_deferred_action_del(_e_config_save_defer);
   _e_config_save_defer = e_powersave_deferred_action_add(_e_config_save_cb,
                                                          NULL);
}

E_API const char *
e_config_profile_get(void)
{
   return _e_config_profile;
}

E_API void
e_config_profile_set(const char *prof)
{
   eina_stringshare_replace(&_e_config_profile, prof);
   e_util_env_set("E_CONF_PROFILE", _e_config_profile);
}

E_API char *
e_config_profile_dir_get(const char *prof)
{
   char buf[PATH_MAX];

   e_user_dir_snprintf(buf, sizeof(buf), "config/%s", prof);
   if (ecore_file_is_dir(buf)) return strdup(buf);
   e_prefix_data_snprintf(buf, sizeof(buf), "data/config/%s", prof);
   if (ecore_file_is_dir(buf)) return strdup(buf);
   return NULL;
}

static int
_cb_sort_files(char *f1, char *f2)
{
   return strcmp(f1, f2);
}

E_API Eina_List *
e_config_profile_list(void)
{
   Eina_List *files;
   char buf[PATH_MAX], *p;
   Eina_List *flist = NULL;
   size_t len;

   len = e_user_dir_concat_static(buf, "config");
   if (len + 1 >= (int)sizeof(buf))
     return NULL;

   files = ecore_file_ls(buf);

   buf[len] = '/';
   len++;

   p = buf + len;
   len = sizeof(buf) - len;
   if (files)
     {
        char *file;

        files = eina_list_sort(files, 0, (Eina_Compare_Cb)_cb_sort_files);
        EINA_LIST_FREE(files, file)
          {
             if (eina_strlcpy(p, file, len) >= len)
               {
                  free(file);
                  continue;
               }
             if (ecore_file_is_dir(buf))
               flist = eina_list_append(flist, file);
             else
               free(file);
          }
     }
   len = e_prefix_data_concat_static(buf, "data/config");
   if (len >= sizeof(buf))
     return NULL;

   files = ecore_file_ls(buf);

   buf[len] = '/';
   len++;

   p = buf + len;
   len = sizeof(buf) - len;
   if (files)
     {
        char *file;
        files = eina_list_sort(files, 0, (Eina_Compare_Cb)_cb_sort_files);
        EINA_LIST_FREE(files, file)
          {
             if (eina_strlcpy(p, file, len) >= len)
               {
                  free(file);
                  continue;
               }
             if (ecore_file_is_dir(buf))
               {
                  const Eina_List *l;
                  const char *tmp;
                  EINA_LIST_FOREACH(flist, l, tmp)
                    if (!strcmp(file, tmp)) break;

                  if (!l) flist = eina_list_append(flist, file);
                  else free(file);
               }
             else
               free(file);
          }
     }
   return flist;
}

E_API void
e_config_profile_add(const char *prof)
{
   char buf[4096];
   if (e_user_dir_snprintf(buf, sizeof(buf), "config/%s", prof) >= sizeof(buf))
     return;
   ecore_file_mkdir(buf);
}

E_API void
e_config_profile_del(const char *prof)
{
   char buf[4096];
   if (e_user_dir_snprintf(buf, sizeof(buf), "config/%s", prof) >= sizeof(buf))
     return;
   ecore_file_recursive_rm(buf);
}

E_API void
e_config_save_block_set(int block)
{
   _e_config_save_block = block;
}

E_API int
e_config_save_block_get(void)
{
   return _e_config_save_block;
}

/**
 * Loads configurations from file located in the working profile
 * The configurations are stored in a struct declated by the
 * macros E_CONFIG_DD_NEW and E_CONFIG_<b>TYPE</b>
 *
 * @param domain of the configuration file.
 * @param edd to struct definition
 * @return returns allocated struct on success, if unable to find config returns null
 */
E_API void *
e_config_domain_load(const char *domain, E_Config_DD *edd)
{
   Eet_File *ef;
   char buf[4096];
   void *data = NULL;
   int i;

   e_user_dir_snprintf(buf, sizeof(buf), "config/%s/%s.cfg",
                       _e_config_profile, domain);
   ef = eet_open(buf, EET_FILE_MODE_READ);
   if (ef)
     {
        data = eet_data_read(ef, edd, "config");
        eet_close(ef);
        if (data) return data;
     }

   for (i = 1; i <= _e_config_revisions; i++)
     {
        e_user_dir_snprintf(buf, sizeof(buf), "config/%s/%s.cfg.%i",
                            _e_config_profile, domain, i);
        ef = eet_open(buf, EET_FILE_MODE_READ);
        if (ef)
          {
             printf("CF: warning - falling back to %s\n", buf);
             data = eet_data_read(ef, edd, "config");
             eet_close(ef);
             if (data) return data;
             printf("CF: warning - fallback to %s can't read config\n", buf);
          }
        else if (ecore_file_exists(buf))
          printf("CF: warning - fallback to %s failing to open\n", buf);
     }
   printf("CF: fallback to system config config file for [%s]\n", domain);
   return e_config_domain_system_load(domain, edd);
}

E_API void *
e_config_domain_system_load(const char *domain, E_Config_DD *edd)
{
   Eet_File *ef;
   char buf[4096];
   void *data = NULL;

   e_prefix_data_snprintf(buf, sizeof(buf), "data/config/%s/%s.cfg",
                          _e_config_profile, domain);
   ef = eet_open(buf, EET_FILE_MODE_READ);
   if (ef)
     {
        data = eet_data_read(ef, edd, "config");
        eet_close(ef);
        return data;
     }
   printf("CF: system config load for %s failed\n", buf);

   return data;
}

E_API int
e_config_profile_save(void)
{
   Eet_File *ef;
   char buf[4096], buf2[4096];
   const char *s;
   int ok = 0;
   static signed char nosave = -1;

   if (nosave == -1)
     {
        if ((s = getenv("E_CONF_PROFILE_NOSAVE")) && atoi(s)) nosave = 1;
        else nosave = 0;
     }
   if (nosave == 1) return 1;

   /* FIXME: check for other sessions fo E running */
   e_user_dir_concat_static(buf, "config/profile.cfg");
   e_user_dir_concat_static(buf2, "config/profile.cfg.tmp");

   ef = _e_config_pending_file_find(buf2);
   if (ef)
     {
        ok = eet_write(ef, "config", _e_config_profile,
                       strlen(_e_config_profile), 0);
        _e_config_save_thread_send(E_CONFIG_SAVE_THREAD_SAVE, buf2, buf);
     }
   return ok;
}

/**
 * Saves configurations to file located in the working profile
 * The configurations are read from a struct declated by the
 * macros E_CONFIG_DD_NEW and E_CONFIG_<b>TYPE</b>
 *
 * @param domain  name of the configuration file.
 * @param edd pointer to struct definition
 * @param data struct to save as configuration file
 * @return 1 if save success, 0 on failure
 */
E_API int
e_config_domain_save(const char *domain, E_Config_DD *edd, const void *data)
{
   Eet_File *ef;
   char buf[4096], buf2[4096];
   int ok = 0;
   size_t len, len2;

   if (_e_config_save_block) return 0;
   /* FIXME: check for other sessions fo E running */
   len = e_user_dir_snprintf(buf, sizeof(buf), "config/%s", _e_config_profile);
   if (len + 1 >= sizeof(buf)) return 0;

   ecore_file_mkdir(buf);

   buf[len] = '/';
   len++;

   len2 = eina_strlcpy(buf + len, domain, sizeof(buf) - len);
   if (len2 + sizeof(".cfg") >= sizeof(buf) - len) return 0;

   len += len2;

   memcpy(buf + len, ".cfg", sizeof(".cfg"));
   len += sizeof(".cfg") - 1;

   if (len + sizeof(".tmp") >= sizeof(buf)) return 0;
   memcpy(buf2, buf, len);
   memcpy(buf2 + len, ".tmp", sizeof(".tmp"));

   ef = _e_config_pending_file_find(buf2);
   if (ef)
     {
        ok = eet_data_write(ef, edd, "config", data,
                            EET_COMPRESSION_SUPERFAST);
        _e_config_save_thread_send(E_CONFIG_SAVE_THREAD_SAVE, buf2, buf);
     }
   return ok;
}

E_API E_Config_Binding_Mouse *
e_config_binding_mouse_match(E_Config_Binding_Mouse *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Mouse *eb;

   EINA_LIST_FOREACH(e_bindings->mouse_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->button == eb_in->button) &&
            (eb->modifiers == eb_in->modifiers) &&
            (eb->any_mod == eb_in->any_mod) &&
            (((eb->action) && (eb_in->action) && (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) && (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API E_Config_Binding_Key *
e_config_binding_key_match(E_Config_Binding_Key *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Key *eb;

   EINA_LIST_FOREACH(e_bindings->key_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->modifiers == eb_in->modifiers) &&
            (eb->any_mod == eb_in->any_mod) &&
            (((eb->key) && (eb_in->key) && (!strcmp(eb->key, eb_in->key))) ||
             ((!eb->key) && (!eb_in->key))) &&
            (((eb->action) && (eb_in->action) && (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) && (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API E_Config_Binding_Edge *
e_config_binding_edge_match(E_Config_Binding_Edge *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Edge *eb;

   EINA_LIST_FOREACH(e_bindings->edge_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->modifiers == eb_in->modifiers) &&
            (eb->any_mod == eb_in->any_mod) &&
            (eb->edge == eb_in->edge) &&
            EINA_FLT_EQ(eb->delay, eb_in->delay) &&
            (eb->drag_only == eb_in->drag_only) &&
            (((eb->action) && (eb_in->action) && (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) && (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API E_Config_Binding_Signal *
e_config_binding_signal_match(E_Config_Binding_Signal *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Signal *eb;

   EINA_LIST_FOREACH(e_bindings->signal_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->modifiers == eb_in->modifiers) &&
            (eb->any_mod == eb_in->any_mod) &&
            (((eb->signal) && (eb_in->signal) && (!strcmp(eb->signal, eb_in->signal))) ||
             ((!eb->signal) && (!eb_in->signal))) &&
            (((eb->source) && (eb_in->source) && (!strcmp(eb->source, eb_in->source))) ||
             ((!eb->source) && (!eb_in->source))) &&
            (((eb->action) && (eb_in->action) && (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) && (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API E_Config_Binding_Wheel *
e_config_binding_wheel_match(E_Config_Binding_Wheel *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Wheel *eb;

   EINA_LIST_FOREACH(e_bindings->wheel_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->direction == eb_in->direction) &&
            (eb->z == eb_in->z) &&
            (eb->modifiers == eb_in->modifiers) &&
            (eb->any_mod == eb_in->any_mod) &&
            (((eb->action) && (eb_in->action) && (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) && (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API E_Config_Binding_Acpi *
e_config_binding_acpi_match(E_Config_Binding_Acpi *eb_in)
{
   Eina_List *l;
   E_Config_Binding_Acpi *eb;

   EINA_LIST_FOREACH(e_bindings->acpi_bindings, l, eb)
     {
        if ((eb->context == eb_in->context) &&
            (eb->type == eb_in->type) &&
            (eb->status == eb_in->status) &&
            (((eb->action) && (eb_in->action) &&
              (!strcmp(eb->action, eb_in->action))) ||
             ((!eb->action) && (!eb_in->action))) &&
            (((eb->params) && (eb_in->params) &&
              (!strcmp(eb->params, eb_in->params))) ||
             ((!eb->params) && (!eb_in->params))))
          return eb;
     }
   return NULL;
}

E_API void
e_config_mode_changed(void)
{
   ecore_event_add(E_EVENT_CONFIG_MODE_CHANGED, NULL, NULL, NULL);
}

E_API void
e_config_binding_swipe_free(E_Config_Binding_Swipe *eba)
{
   if (!eba) return;
   eina_stringshare_del(eba->action);
   eina_stringshare_del(eba->params);
   free(eba);
}


E_API void
e_config_binding_acpi_free(E_Config_Binding_Acpi *eba)
{
   if (!eba) return;
   eina_stringshare_del(eba->action);
   eina_stringshare_del(eba->params);
   free(eba);
}

E_API void
e_config_binding_key_free(E_Config_Binding_Key *ebk)
{
   if (!ebk) return;
   eina_stringshare_del(ebk->key);
   eina_stringshare_del(ebk->action);
   eina_stringshare_del(ebk->params);
   free(ebk);
}

E_API void
e_config_binding_edge_free(E_Config_Binding_Edge *ebe)
{
   if (!ebe) return;
   eina_stringshare_del(ebe->action);
   eina_stringshare_del(ebe->params);
   free(ebe);
}

E_API void
e_config_binding_mouse_free(E_Config_Binding_Mouse *ebm)
{
   if (!ebm) return;
   eina_stringshare_del(ebm->action);
   eina_stringshare_del(ebm->params);
   free(ebm);
}

E_API void
e_config_binding_wheel_free(E_Config_Binding_Wheel *ebw)
{
   if (!ebw) return;
   eina_stringshare_del(ebw->action);
   eina_stringshare_del(ebw->params);
   free(ebw);
}

E_API void
e_config_binding_signal_free(E_Config_Binding_Signal *ebs)
{
   if (!ebs) return;
   eina_stringshare_del(ebs->signal);
   eina_stringshare_del(ebs->source);
   eina_stringshare_del(ebs->action);
   eina_stringshare_del(ebs->params);
   free(ebs);
}

E_API void
e_config_bindings_free(E_Config_Bindings *ecb)
{
   if (!ecb) return;
   E_FREE_LIST(ecb->mouse_bindings, e_config_binding_mouse_free);
   E_FREE_LIST(ecb->key_bindings, e_config_binding_key_free);
   E_FREE_LIST(ecb->edge_bindings, e_config_binding_edge_free);
   E_FREE_LIST(ecb->signal_bindings, e_config_binding_signal_free);
   E_FREE_LIST(ecb->wheel_bindings, e_config_binding_wheel_free);
   E_FREE_LIST(ecb->acpi_bindings, e_config_binding_acpi_free);
   free(ecb);
}

/* local subsystem functions */
static void
_e_config_save_cb(void *data EINA_UNUSED)
{
   e_config_profile_save();
   e_module_save_all();
   elm_config_save();
   e_config_domain_save("e", _e_config_edd, e_config);
   e_config_domain_save("e_bindings", _e_config_binding_edd, e_bindings);
   _e_config_save_defer = NULL;
}

static void
_e_config_free(E_Config *ecf)
{
   E_Config_Syscon_Action *sca;
   E_Font_Fallback *eff;
   E_Config_Module *em;
   E_Font_Default *efd;
   E_Color_Class *cc;
   E_Path_Dir *epd;
   E_Remember *rem;
   E_Config_Env_Var *evr;
   E_Config_XKB_Option *op;
   E_Int_Menu_Applications *ema;

   if (!ecf) return;

   eina_stringshare_del(ecf->xkb.default_model);

   E_FREE_LIST(ecf->xkb.used_layouts, e_config_xkb_layout_free);
   EINA_LIST_FREE(ecf->xkb.used_options, op)
     {
        eina_stringshare_del(op->name);
        E_FREE(op);
     }

   EINA_LIST_FREE(ecf->modules, em)
     {
        if (em->name) eina_stringshare_del(em->name);
        E_FREE(em);
     }
   EINA_LIST_FREE(ecf->font_fallbacks, eff)
     {
        if (eff->name) eina_stringshare_del(eff->name);
        E_FREE(eff);
     }
   EINA_LIST_FREE(ecf->font_defaults, efd)
     {
        if (efd->text_class) eina_stringshare_del(efd->text_class);
        if (efd->font) eina_stringshare_del(efd->font);
        E_FREE(efd);
     }
   EINA_LIST_FREE(ecf->path_append_data, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_images, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_fonts, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_init, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_icons, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_modules, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_backgrounds, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->path_append_messages, epd)
     {
        if (epd->dir) eina_stringshare_del(epd->dir);
        E_FREE(epd);
     }
   EINA_LIST_FREE(ecf->remembers, rem)
     {
        if (rem->name) eina_stringshare_del(rem->name);
        if (rem->class) eina_stringshare_del(rem->class);
        if (rem->title) eina_stringshare_del(rem->title);
        if (rem->role) eina_stringshare_del(rem->role);
        if (rem->prop.border) eina_stringshare_del(rem->prop.border);
        if (rem->prop.command) eina_stringshare_del(rem->prop.command);
        eina_stringshare_del(rem->uuid);
        E_FREE(rem);
     }
   EINA_LIST_FREE(ecf->menu_applications, ema)
     {
        if (ema->orig_path) eina_stringshare_del(ema->orig_path);
        if (ema->try_exec) eina_stringshare_del(ema->try_exec);
        if (ema->exec) eina_stringshare_del(ema->exec);
        E_FREE(ema);
     }
   EINA_LIST_FREE(ecf->color_classes, cc)
     {
        if (cc->name) eina_stringshare_del(cc->name);
        E_FREE(cc);
     }
   if (ecf->desktop_default_background) eina_stringshare_del(ecf->desktop_default_background);
   if (ecf->language) eina_stringshare_del(ecf->language);
   eina_stringshare_del(ecf->desklock_language);
   eina_stringshare_del(ecf->xkb.selected_layout);
   eina_stringshare_del(ecf->xkb.cur_layout);
   eina_stringshare_del(ecf->xkb.desklock_layout);
   e_config_xkb_layout_free(ecf->xkb.current_layout);
   e_config_xkb_layout_free(ecf->xkb.sel_layout);
   e_config_xkb_layout_free(ecf->xkb.lock_layout);
   eina_stringshare_del(ecf->desk_flip_animate_type);
   if (ecf->transition_start) eina_stringshare_del(ecf->transition_start);
   if (ecf->transition_desk) eina_stringshare_del(ecf->transition_desk);
   if (ecf->transition_change) eina_stringshare_del(ecf->transition_change);
   if (ecf->input_method) eina_stringshare_del(ecf->input_method);
   if (ecf->exebuf_term_cmd) eina_stringshare_del(ecf->exebuf_term_cmd);
   if (ecf->icon_theme) eina_stringshare_del(ecf->icon_theme);
   if (ecf->desktop_environment) eina_stringshare_del(ecf->desktop_environment);
   if (ecf->wallpaper_import_last_dev) eina_stringshare_del(ecf->wallpaper_import_last_dev);
   if (ecf->wallpaper_import_last_path) eina_stringshare_del(ecf->wallpaper_import_last_path);
   if (ecf->theme_default_border_style) eina_stringshare_del(ecf->theme_default_border_style);
   EINA_LIST_FREE(ecf->syscon.actions, sca)
     {
        if (sca->action) eina_stringshare_del(sca->action);
        if (sca->params) eina_stringshare_del(sca->params);
        if (sca->button) eina_stringshare_del(sca->button);
        if (sca->icon) eina_stringshare_del(sca->icon);
        E_FREE(sca);
     }
   EINA_LIST_FREE(ecf->env_vars, evr)
     {
        if (evr->var) eina_stringshare_del(evr->var);
        if (evr->val) eina_stringshare_del(evr->val);
        E_FREE(evr);
     }
   if (ecf->xsettings.net_icon_theme_name)
     eina_stringshare_del(ecf->xsettings.net_icon_theme_name);
   if (ecf->xsettings.net_theme_name)
     eina_stringshare_del(ecf->xsettings.net_theme_name);
   if (ecf->xsettings.gtk_font_name)
     eina_stringshare_del(ecf->xsettings.gtk_font_name);
   if (ecf->backlight.sysdev)
     eina_stringshare_del(ecf->backlight.sysdev);

   E_FREE(ecf);
}

static Eina_Bool
_e_config_cb_timer(void *data)
{
   e_util_dialog_show(_("Settings Upgraded"), "%s", (char *)data);
   return 0;
}
#include <Eina.h>
#include <Eet.h>
#include "e_macros.h"
#define E_TYPEDEFS
#include "e_config_data.h"
#undef E_TYPEDEFS
#include "e_config_data.h"

static Eina_Hash *config_hash = NULL;

E_API void
e_config_descriptor_free(E_Config_DD *edd)
{
   eina_hash_del_by_key(config_hash, eet_data_descriptor_name_get((Eet_Data_Descriptor*)edd));
   eet_data_descriptor_free((Eet_Data_Descriptor*)edd);
}

E_API E_Config_DD *
e_config_descriptor_new(const char *name, int size)
{
   Eet_Data_Descriptor_Class eddc;
   E_Config_DD *edd;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof (eddc), name, size))
     return NULL;

   /* FIXME: We can directly map string inside an Eet_File and reuse it.
      But this need a break in all user of config every where in E.
    */

   edd = (E_Config_DD *)eet_data_descriptor_stream_new(&eddc);

   if (!config_hash) config_hash = eina_hash_string_superfast_new(NULL);
   eina_hash_set(config_hash, name, edd);
   return edd;
}

E_API E_Config_DD *
e_config_descriptor_find(const char *name)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL);
   return eina_hash_find(config_hash, name);
}
#include "e.h"

/* local subsystem functions */
static void      _e_config_dialog_free(E_Config_Dialog *cfd);
static void      _e_config_dialog_go(E_Config_Dialog *cfd, E_Config_Dialog_CFData_Type type);
static Eina_Bool _e_config_dialog_cb_auto_apply_timer(void *data);
static void      _e_config_dialog_cb_dialog_del(void *obj);
static void      _e_config_dialog_cb_ok(void *data, E_Dialog *dia);
static void      _e_config_dialog_cb_apply(void *data, E_Dialog *dia);
static void      _e_config_dialog_cb_advanced(void *data, void *data2);
static void      _e_config_dialog_cb_basic(void *data, void *data2);
static int       _e_config_dialog_check_changed(E_Config_Dialog *cfd, unsigned char def);
static void      _e_config_dialog_cb_changed(void *data, Evas_Object *obj);
static void      _e_config_dialog_cb_close(void *data, E_Dialog *dia);

/* local subsystem globals */
static Eina_List *_e_config_dialog_list = NULL;

/* externally accessible functions */

/**
 * Creates a new dialog
 *
 * @param parent the parent dialog
 * @param title to display for the dialog
 * @param name the name used to register the window in e
 * @param class the call used to register the window in e
 * @param icon the path to the icon file
 * @param icon_size is of the width and height of the icon
 * @param view the callbacks used to create the dialog and save the settings
 * @param data additional data to attach to the dialog, will be passed to the callbacks
 * @return returns the created dialog. Null on failure
 */
E_API E_Config_Dialog *
e_config_dialog_new(Evas_Object *parent, const char *title, const char *name, const char *class, const char *icon, int icon_size, E_Config_Dialog_View *view, void *data)
{
   E_Config_Dialog *cfd;

   cfd = E_OBJECT_ALLOC(E_Config_Dialog, E_CONFIG_DIALOG_TYPE,
                        _e_config_dialog_free);
   cfd->view = view;
   cfd->parent = parent;
   cfd->title = eina_stringshare_add(title);
   cfd->name = eina_stringshare_add(name);
   cfd->class = eina_stringshare_add(class);
   if (icon)
     {
        cfd->icon = eina_stringshare_add(icon);
        cfd->icon_size = icon_size;
     }
   cfd->data = data;
   cfd->hide_buttons = 1;
   cfd->cfg_changed = 0;
   cfd->cfg_changed_auto = 1;

   if (cfd->view->override_auto_apply)
     {
        /* Dialog Requested To Not Auto-Apply */
        if ((cfd->view->basic.apply_cfdata) ||
            (cfd->view->advanced.apply_cfdata))
          cfd->hide_buttons = 0;
     }
   else
     {
        /* Ok To Override, Or Not Specified. Use Config Value */
        if (e_config->cfgdlg_auto_apply)
          cfd->hide_buttons = 1;
        else
          {
             if ((cfd->view->basic.apply_cfdata) ||
                 (cfd->view->advanced.apply_cfdata))
               cfd->hide_buttons = 0;
          }
     }

   switch (e_config->cfgdlg_default_mode)
     {
      case E_CONFIG_DIALOG_CFDATA_TYPE_BASIC:
        if (cfd->view->basic.create_widgets)
          _e_config_dialog_go(cfd, E_CONFIG_DIALOG_CFDATA_TYPE_BASIC);
        break;

      case E_CONFIG_DIALOG_CFDATA_TYPE_ADVANCED:
        if (cfd->view->advanced.create_widgets)
          _e_config_dialog_go(cfd, E_CONFIG_DIALOG_CFDATA_TYPE_ADVANCED);
        else if (cfd->view->basic.create_widgets)
          _e_config_dialog_go(cfd, E_CONFIG_DIALOG_CFDATA_TYPE_BASIC);
        break;
     }
   _e_config_dialog_list = eina_list_append(_e_config_dialog_list, cfd);

   return cfd;
}

E_API int
e_config_dialog_find(const char *name, const char *class)
{
   Eina_List *l;
   E_Config_Dialog *cfd;

   EINA_LIST_FOREACH(_e_config_dialog_list, l, cfd)
     {
        if ((!e_util_strcmp(name, cfd->name)) &&
            (!e_util_strcmp(class, cfd->class)))
          {
             E_Zone *z;
             E_Client *ec;

             z = e_zone_current_get();
             ec = e_win_client_get(cfd->dia->win);
             e_client_uniconify(ec);
             elm_win_raise(cfd->dia->win);
             ec->hidden = 0;
             e_client_desk_set(ec, e_desk_current_get(z));
             if (ec->shaded || ec->shading)
               e_client_unshade(ec, ec->shade_dir);
             if ((e_config->focus_setting == E_FOCUS_NEW_DIALOG) ||
                 (e_config->focus_setting == E_FOCUS_NEW_WINDOW))
               evas_object_focus_set(ec->frame, 1);
             return 1;
          }
     }
   return 0;
}

E_API E_Config_Dialog *
e_config_dialog_get(const char *name, const char *class)
{
   Eina_List *l;
   E_Config_Dialog *cfd;

   EINA_LIST_FOREACH(_e_config_dialog_list, l, cfd)
     {
        if (!cfd) continue;
        if ((!e_util_strcmp(name, cfd->name)) &&
            (!e_util_strcmp(class, cfd->class)))
          {
             return cfd;
          }
     }
   return NULL;
}

/* local subsystem functions */
static void
_e_config_dialog_free(E_Config_Dialog *cfd)
{
   _e_config_dialog_list = eina_list_remove(_e_config_dialog_list, cfd);
   if (cfd->auto_apply_timer) _e_config_dialog_cb_auto_apply_timer(cfd);
   if (cfd->title) eina_stringshare_del(cfd->title);
   if (cfd->name) eina_stringshare_del(cfd->name);
   if (cfd->class) eina_stringshare_del(cfd->class);
   if (cfd->icon) eina_stringshare_del(cfd->icon);
   if (cfd->view->free_cfdata)
     {
        cfd->view->free_cfdata(cfd, cfd->cfdata);
        cfd->cfdata = NULL;
     }
   if (cfd->dia)
     {
        e_object_del_attach_func_set(E_OBJECT(cfd->dia), NULL);
        e_object_del(E_OBJECT(cfd->dia));
        cfd->dia = NULL;
     }
   E_FREE(cfd->view);
   E_FREE(cfd);
}

static void
_e_config_dialog_go(E_Config_Dialog *cfd, E_Config_Dialog_CFData_Type type)
{
   Evas *evas;
   E_Dialog *pdia;
   Evas_Object *o, *ob, *sf;
   char buf[256];
   int mw = -1, mh = -1;
   void *cfdata;

   pdia = cfd->dia;
   /* FIXME: get name/class form new call and use here */
   /* if (type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
    *   snprintf(buf, sizeof(buf), "%s...%s", cfd->class, "BASIC");
    * else
    *   snprintf(buf, sizeof(buf), "%s...%s", cfd->class, "ADVANCED"); */
   snprintf(buf, sizeof(buf), "_config::%s", cfd->class);

   if (!pdia)  /* creating window for the first time */
     {
        if ((cfd->view->normal_win) || (e_config->cfgdlg_normal_wins))
          cfd->dia = e_dialog_normal_win_new(cfd->parent, cfd->name, buf);
        else
          cfd->dia = e_dialog_new(cfd->parent, cfd->name, buf);
        e_object_del_attach_func_set(E_OBJECT(cfd->dia),
                                     _e_config_dialog_cb_dialog_del);
     } /* window was created before - deleting content only */
   else if (cfd->dia->content_object)
     evas_object_del(cfd->dia->content_object);

   cfd->view_type = type;
   cfd->dia->data = cfd;
   e_dialog_title_set(cfd->dia, cfd->title);
   cfdata = cfd->cfdata;
   if (cfd->view->create_cfdata && (!cfd->cfdata))
     cfd->cfdata = cfd->view->create_cfdata(cfd);

   evas = evas_object_evas_get(cfd->dia->win);
   if (type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
     {
        if (cfd->view->advanced.create_widgets)
          {
             o = e_widget_list_add(evas, 0, 0);
             ob = cfd->view->basic.create_widgets(cfd, evas, cfd->cfdata);
             if (cfd->view->scroll)
               {
                  e_widget_size_min_resize(ob);
                  sf = e_widget_scrollframe_simple_add(evas, ob);
                  e_widget_list_object_append(o, sf, 1, 1, 0.0);
               }
             else
               e_widget_list_object_append(o, ob, 1, 1, 0.0);
             ob = e_widget_button_add(evas,
                                      _("Advanced"), "go-next",
                                      _e_config_dialog_cb_advanced, cfd, NULL);
             e_widget_list_object_append(o, ob, 0, 0, 1.0);
          }
        else
          {
             o = cfd->view->basic.create_widgets(cfd, evas, cfd->cfdata);
             if (cfd->view->scroll)
               {
                  e_widget_size_min_resize(o);
                  o = e_widget_scrollframe_simple_add(evas, o);
               }
          }
     }
   else
     {
        if (cfd->view->basic.create_widgets)
          {
             o = e_widget_list_add(evas, 0, 0);
             ob = cfd->view->advanced.create_widgets(cfd, evas, cfd->cfdata);
             if (cfd->view->scroll)
               {
                  e_widget_size_min_resize(ob);
                  sf = e_widget_scrollframe_simple_add(evas, ob);
                  e_widget_list_object_append(o, sf, 1, 1, 0.0);
               }
             else
               e_widget_list_object_append(o, ob, 1, 1, 0.0);
             ob = e_widget_button_add(evas,
                                      _("Basic"), "go-next",
                                      _e_config_dialog_cb_basic, cfd, NULL);
             e_widget_list_object_append(o, ob, 0, 0, 1.0);
          }
        else
          {
             o = cfd->view->advanced.create_widgets(cfd, evas, cfd->cfdata);
             if (cfd->view->scroll)
               {
                  e_widget_size_min_resize(o);
                  o = e_widget_scrollframe_simple_add(evas, o);
               }
          }
     }

   e_widget_on_change_hook_set(o, _e_config_dialog_cb_changed, cfd);
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(cfd->dia, o, mw, mh);

   if (!pdia) /* dialog window was created in this function call - need to create buttons once */
     {
        if (!cfd->hide_buttons)
          {
             e_dialog_button_add(cfd->dia, _("OK"), NULL,
                                 _e_config_dialog_cb_ok, cfd);
             e_dialog_button_add(cfd->dia, _("Apply"), NULL,
                                 _e_config_dialog_cb_apply, cfd);
             if (!cfd->cfg_changed)
               {
                  e_dialog_button_disable_num_set(cfd->dia, 0, 1);
                  e_dialog_button_disable_num_set(cfd->dia, 1, 1);
               }
          }
        e_dialog_button_add(cfd->dia, _("Close"), NULL,
                            _e_config_dialog_cb_close, cfd);
     }

   if (cfdata && cfd->cfg_changed_auto)
     {
        int changed;

        changed = _e_config_dialog_check_changed(cfd, 0);
        e_config_dialog_changed_set(cfd, changed);
     }

   e_dialog_show(cfd->dia);
   if (cfd->icon) e_dialog_border_icon_set(cfd->dia, cfd->icon);
}

static Eina_Bool
_e_config_dialog_cb_auto_apply_timer(void *data)
{
   E_Config_Dialog *cfd;

   cfd = data;

   if (cfd->auto_apply_timer) ecore_timer_del(cfd->auto_apply_timer);
   cfd->auto_apply_timer = NULL;

   if (cfd->view_type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
     {
        if (cfd->view->basic.apply_cfdata)
          cfd->view->basic.apply_cfdata(cfd, cfd->cfdata);
     }
   else
     {
        if (cfd->view->advanced.apply_cfdata)
          cfd->view->advanced.apply_cfdata(cfd, cfd->cfdata);
     }
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_config_dialog_cb_dialog_del(void *obj)
{
   E_Dialog *dia;
   E_Config_Dialog *cfd;

   dia = obj;
   cfd = dia->data;
   if (cfd->auto_apply_timer) _e_config_dialog_cb_auto_apply_timer(cfd);
   cfd->dia = NULL;
   e_object_del(E_OBJECT(cfd));
}

static void
_e_config_dialog_cb_ok(void *data EINA_UNUSED, E_Dialog *dia)
{
   E_Config_Dialog *cfd;
   int ok = 0;

   cfd = dia->data;
   if (cfd->view_type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
     {
        if (cfd->view->basic.apply_cfdata)
          ok = cfd->view->basic.apply_cfdata(cfd, cfd->cfdata);
     }
   else
     {
        if (cfd->view->advanced.apply_cfdata)
          ok = cfd->view->advanced.apply_cfdata(cfd, cfd->cfdata);
     }
   if (ok)
     e_util_defer_object_del(E_OBJECT(cfd));
}

static void
_e_config_dialog_cb_apply(void *data EINA_UNUSED, E_Dialog *dia)
{
   E_Config_Dialog *cfd;
   int ok = 0;

   cfd = dia->data;
   if (cfd->view_type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
     {
        if (cfd->view->basic.apply_cfdata)
          ok = cfd->view->basic.apply_cfdata(cfd, cfd->cfdata);
     }
   else
     {
        if (cfd->view->advanced.apply_cfdata)
          ok = cfd->view->advanced.apply_cfdata(cfd, cfd->cfdata);
     }
   if ((ok) && (!cfd->hide_buttons))
     {
        cfd->cfg_changed = 0;
        e_dialog_button_disable_num_set(cfd->dia, 0, 1);
        e_dialog_button_disable_num_set(cfd->dia, 1, 1);
     }
}

static void
_e_config_dialog_cb_advanced(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog *cfd;

   cfd = data;
   if (cfd->auto_apply_timer) _e_config_dialog_cb_auto_apply_timer(cfd);
   _e_config_dialog_go(cfd, E_CONFIG_DIALOG_CFDATA_TYPE_ADVANCED);
}

static void
_e_config_dialog_cb_basic(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog *cfd;

   cfd = data;
   if (cfd->auto_apply_timer) _e_config_dialog_cb_auto_apply_timer(cfd);
   _e_config_dialog_go(cfd, E_CONFIG_DIALOG_CFDATA_TYPE_BASIC);
}

static void
_e_config_dialog_changed(E_Config_Dialog *cfd)
{
   if (!cfd->hide_buttons)
     {
        cfd->cfg_changed = 1;
        e_dialog_button_disable_num_set(cfd->dia, 0, 0);
        e_dialog_button_disable_num_set(cfd->dia, 1, 0);
     }
   else
     {
        if (cfd->auto_apply_timer) ecore_timer_del(cfd->auto_apply_timer);
        cfd->auto_apply_timer = NULL;
        cfd->auto_apply_timer =
          ecore_timer_loop_add(0.5, _e_config_dialog_cb_auto_apply_timer, cfd);
     }
}

static void
_e_config_dialog_unchanged(E_Config_Dialog *cfd)
{
   if (!cfd->hide_buttons)
     {
        e_dialog_button_disable_num_set(cfd->dia, 0, 1);
        e_dialog_button_disable_num_set(cfd->dia, 1, 1);
     }
   else
     {
        if (cfd->auto_apply_timer)
          {
             ecore_timer_del(cfd->auto_apply_timer);
             cfd->auto_apply_timer = NULL;
          }
     }
}

static int
_e_config_dialog_check_changed(E_Config_Dialog *cfd, unsigned char def)
{
   int changed = 0;

   if (cfd->view_type == E_CONFIG_DIALOG_CFDATA_TYPE_BASIC)
     {
        if (cfd->view->basic.check_changed)
          changed = cfd->view->basic.check_changed(cfd, cfd->cfdata);
        else
          changed = def;
     }
   else if (cfd->view_type == E_CONFIG_DIALOG_CFDATA_TYPE_ADVANCED)
     {
        if (cfd->view->advanced.check_changed)
          changed = cfd->view->advanced.check_changed(cfd, cfd->cfdata);
        else
          changed = def;
     }

   return changed;
}

static void
_e_config_dialog_cb_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog *cfd;
   int changed;

   cfd = data;
   if (!cfd->cfg_changed_auto) return;
   changed = _e_config_dialog_check_changed(cfd, 1);

   e_config_dialog_changed_set(cfd, changed);
}

static void
_e_config_dialog_cb_close(void *data EINA_UNUSED, E_Dialog *dia)
{
   E_Config_Dialog *cfd;
   int ok = 1;

   cfd = dia->data;
   if (cfd->auto_apply_timer) _e_config_dialog_cb_auto_apply_timer(cfd);
   if (cfd->view->close_cfdata)
     ok = cfd->view->close_cfdata(cfd, cfd->cfdata);
   if (ok) e_util_defer_object_del(E_OBJECT(cfd));
}

E_API void
e_config_dialog_changed_auto_set(E_Config_Dialog *cfd, unsigned char value)
{
   if (!cfd) return;
   cfd->cfg_changed_auto = !!value;
}

E_API void
e_config_dialog_changed_set(E_Config_Dialog *cfd, unsigned char value)
{
   if (!cfd) return;
   cfd->cfg_changed = !!value;
   if (cfd->cfg_changed)
     _e_config_dialog_changed(cfd);
   else
     _e_config_dialog_unchanged(cfd);
}

#include "e.h"

static void      _e_configure_menu_module_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void      _e_configure_menu_add(void *data, E_Menu *m);
static void      _e_configure_efreet_desktop_cleanup(void);
static void      _e_configure_efreet_desktop_update(void);
static Eina_Bool _e_configure_cb_efreet_desktop_cache_update(void *data, int type, void *event);
static void      _e_configure_registry_item_full_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon, E_Config_Dialog *(*func)(Evas_Object *parent, const char *params), void (*generic_func)(Evas_Object *parent, const char *params), Efreet_Desktop *desktop, const char *params);
static void      _e_configure_registry_item_free(E_Configure_It *eci);

static void      _configure_job(void *data);
static Eina_Bool _configure_init_timer(void *data);

E_API Eina_List *e_configure_registry = NULL;

static Eina_List *handlers = NULL;
static E_Int_Menu_Augmentation *maug = NULL;
static Ecore_Job *update_job = NULL;

static struct
{
   void        (*func)(const void *data, const char *params, Efreet_Desktop *desktop);
   const char *data;
} custom_desktop_exec = { NULL, NULL };

EINTERN void
e_configure_init(void)
{
   e_configure_registry_category_add("extensions", 90, _("Extensions"), NULL, "preferences-extensions");
   e_configure_registry_item_add("extensions/modules", 10, _("Modules"), NULL, "preferences-plugin", e_int_config_modules);
   e_configure_registry_category_add("appearance", 10, _("Look"), NULL,
                                     "preferences-look");
   e_configure_registry_item_add("appearance/comp", 120, _("Compositor"), NULL, "preferences-composite", e_int_config_comp);
   e_configure_registry_category_add("internal", -1, _("Internal"),
                                     NULL, "enlightenment/internal");
   e_configure_registry_item_add("internal/comp_matches", -1, _("Composite Style Settings"),
                                 NULL, "preferences-composite", e_int_config_comp_match);
   maug = e_int_menus_menu_augmentation_add_sorted
       ("config/1", _("Modules"), _e_configure_menu_add, NULL, NULL, NULL);

   if (update_job)
     {
        ecore_job_del(update_job);
        update_job = NULL;
     }
   ecore_timer_loop_add(0.0, _configure_init_timer, NULL);
}

E_API void
e_configure_registry_call(const char *path, Evas_Object *parent, const char *params)
{
   E_Configure_Cat *ecat;
   Eina_List *l;
   char *cat;
   const char *item;

   /* path is "category/item" */
   cat = ecore_file_dir_get(path);
   if (!cat) return;
   item = ecore_file_file_get(path);
   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     if (!strcmp(cat, ecat->cat))
       {
          E_Configure_It *eci;
          Eina_List *ll;

          EINA_LIST_FOREACH(ecat->items, ll, eci)
            if (!strcmp(item, eci->item))
              {
                 if (!params) params = eci->params;

                 if (eci->func) eci->func(parent, params);
                 else if (eci->generic_func)
                   eci->generic_func(parent, params);
                 else if (eci->desktop)
                   {
                      if (custom_desktop_exec.func)
                        custom_desktop_exec.func(custom_desktop_exec.data,
                                                 params, eci->desktop);
                      else
                        e_exec(e_zone_current_get(),
                               eci->desktop, NULL, NULL, "config");
                   }
                 break;
              }
          break;
       }
   free(cat);
}

E_API void
e_configure_registry_item_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon, E_Config_Dialog *(*func)(Evas_Object *parent, const char *params))
{
   _e_configure_registry_item_full_add(path, pri, label, icon_file, icon, func, NULL, NULL, NULL);
}

E_API void
e_configure_registry_generic_item_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon, void (*generic_func)(Evas_Object *parent, const char *params))
{
   _e_configure_registry_item_full_add(path, pri, label, icon_file, icon, NULL, generic_func, NULL, NULL);
}

E_API void
e_configure_registry_item_params_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon, E_Config_Dialog *(*func)(Evas_Object *parent, const char *params), const char *params)
{
   _e_configure_registry_item_full_add(path, pri, label, icon_file, icon, func, NULL, NULL, params);
}

/**
 *Delete an item in the configuration panel.
 *
 *@param path location the item to delete
 */
E_API void
e_configure_registry_item_del(const char *path)
{
   E_Configure_Cat *ecat;
   Eina_List *l;
   const char *item;
   char *cat;

   /* path is "category/item" */
   cat = ecore_file_dir_get(path);
   if (!cat) return;
   item = ecore_file_file_get(path);

   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     if (!strcmp(cat, ecat->cat))
       {
          E_Configure_It *eci;
          Eina_List *ll;

          EINA_LIST_FOREACH(ecat->items, ll, eci)
            if (!strcmp(item, eci->item))
              {
                 ecat->items = eina_list_remove_list(ecat->items, ll);

                 _e_configure_registry_item_free(eci);
                 break;
              }
          break;
       }
   free(cat);
}

/**
 *Add a category to the configuration panel.
 *
 *@param path location the new category
 *@param pri the priority for sorting the category in the category list
 *@param label the name the user will see in configuration panel
 *@param icon_file the edje file that holds the icon for the category.
 *Can be null to use current theme.
 *@param icon the name of the edje group to use as icon
 */
static int
_E_configure_category_pri_cb(E_Configure_Cat *ecat, E_Configure_Cat *ecat2)
{
   if (ecat->pri == ecat2->pri)
     return strcmp(ecat->label, ecat2->label);
   return ecat->pri - ecat2->pri;
}

E_API void
e_configure_registry_category_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon)
{
   E_Configure_Cat *ecat2;
   E_Configure_Cat *ecat;
   Eina_List *l;

   /* if it exists - ignore this */
   EINA_LIST_FOREACH(e_configure_registry, l, ecat2)
     if (!strcmp(ecat2->cat, path)) return;

   ecat = E_NEW(E_Configure_Cat, 1);
   if (!ecat) return;

   ecat->cat = eina_stringshare_add(path);
   ecat->pri = pri;
   ecat->label = eina_stringshare_add(label);
   if (icon_file) ecat->icon_file = eina_stringshare_add(icon_file);
   if (icon) ecat->icon = eina_stringshare_add(icon);
   e_configure_registry = eina_list_sorted_insert(e_configure_registry,
                                                  EINA_COMPARE_CB(_E_configure_category_pri_cb),
                                                  ecat);
}

/**
 *Delete a category in the configuration panel.
 *
 *@param path location the category to delete
 */
E_API void
e_configure_registry_category_del(const char *path)
{
   E_Configure_Cat *ecat;
   Eina_List *l;
   char *cat;

   cat = ecore_file_dir_get(path);
   if (!cat) return;
   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     if (!strcmp(cat, ecat->cat))
       {
          if (ecat->items) break;
          e_configure_registry = eina_list_remove_list(e_configure_registry, l);
          eina_stringshare_del(ecat->cat);
          eina_stringshare_del(ecat->label);
          if (ecat->icon) eina_stringshare_del(ecat->icon);
          if (ecat->icon_file) eina_stringshare_del(ecat->icon_file);
          free(ecat);
          break;
       }
   free(cat);
}

/**
 *Add a item to the configuration panel.
 *
 *@param path location the location to place configuration item
 *@param pri the priority for sorting the item in the category list
 *@param label the name the user will see in configuration panel
 *@param icon_file the edje file that holds the icon for the category.
 *Can be null to use current theme.
 *@param icon the name of the edje group to use as icon
 *@param func the callback to use when the configuration item is clicked
 */

E_API void
e_configure_registry_custom_desktop_exec_callback_set(void (*func)(const void *data, const char *params, Efreet_Desktop *desktop), const void *data)
{
   custom_desktop_exec.func = func;
   custom_desktop_exec.data = data;
}

E_API int
e_configure_registry_exists(const char *path)
{
   E_Configure_Cat *ecat;
   Eina_List *l;
   char *cat;
   const char *item;
   int ret = 0;

   /* path is "category/item" */
   cat = ecore_file_dir_get(path);
   if (!cat) return 0;
   item = ecore_file_file_get(path);
   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     if (!strcmp(cat, ecat->cat))
       {
          E_Configure_It *eci;
          Eina_List *ll;

          if (!item)
            {
               ret = 1;
               break;
            }
          EINA_LIST_FOREACH(ecat->items, ll, eci)
            if (!strcmp(item, eci->item))
              {
                 ret = 1;
                 break;
              }
          break;
       }

   free(cat);
   return ret;
}

static void
_e_configure_menu_module_item_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_int_config_modules(NULL, NULL);
}

static void
_e_configure_menu_add(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu_Item *mi;

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Modules"));
   e_util_menu_item_theme_icon_set(mi, "preferences-plugin");
   e_menu_item_callback_set(mi, _e_configure_menu_module_item_cb, NULL);
}

static void
_configure_job(void *data EINA_UNUSED)
{
   _e_configure_efreet_desktop_update();
   update_job = NULL;
}

static Eina_Bool
_configure_init_timer(void *data EINA_UNUSED)
{
   handlers = eina_list_append
       (handlers, ecore_event_handler_add
         (EFREET_EVENT_DESKTOP_CACHE_UPDATE, _e_configure_cb_efreet_desktop_cache_update, NULL));
   if (update_job) ecore_job_del(update_job);
   update_job = ecore_job_add(_configure_job, NULL);
   return EINA_FALSE;
}

static void
_e_configure_efreet_desktop_cleanup(void)
{
   Eina_List *l;
   E_Configure_Cat *ecat;

//   printf("_e_configure_efreet_desktop_cleanup\n");
/* remove anything with a desktop entry */
   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     {
        E_Configure_It *eci;
        Eina_List *ll, *ln;

        EINA_LIST_FOREACH_SAFE(ecat->items, ll, ln, eci)
          if (eci->desktop)
            {
               _e_configure_registry_item_free(eci);
               ecat->items = eina_list_remove_list(ecat->items, ll);
            }
     }
}

static void
_e_configure_efreet_desktop_update(void)
{
   Eina_List *settings_desktops, *system_desktops;
   Efreet_Desktop *desktop;
   Eina_List *l;
   char buf[1024];

   /* get desktops */
   settings_desktops = efreet_util_desktop_category_list("Settings");
   system_desktops = efreet_util_desktop_category_list("System");
   if ((!settings_desktops) || (!system_desktops))
     {
        EINA_LIST_FREE(settings_desktops, desktop)
          efreet_desktop_free(desktop);
        EINA_LIST_FREE(system_desktops, desktop)
          efreet_desktop_free(desktop);
        return;
     }

   /* get ones in BOTH lists */
   EINA_LIST_FOREACH(settings_desktops, l, desktop)
     {
        char *s;
        char *cfg_cat_name;
        const char *cfg_cat_icon;
        char *cfg_cat;
        char *cfg_cat_cfg;
        const char *cfg_icon;
        char *label;
        int cfg_pri;
        int dopref;

        dopref = 0;
        cfg_cat = NULL;
        cfg_icon = NULL;
        cfg_cat_cfg = NULL;
        cfg_pri = 1000;
        cfg_cat_name = NULL;
        cfg_cat_icon = NULL;
        label = NULL;
        if (!eina_list_data_find(system_desktops, desktop))
          {
             /* settings desktop but not in system -> put in preferences */
             dopref = 1;
          }
        if (desktop->x)
          {
             cfg_cat_cfg = eina_hash_find(desktop->x, "X-Enlightenment-Config-Category");
             s = eina_hash_find(desktop->x, "X-Enlightenment-Config-Priority");
             if (s) cfg_pri = atoi(s);
             cfg_cat_name = eina_hash_find(desktop->x, "X-Enlightenment-Config-Category-Name");
             cfg_cat_icon = eina_hash_find(desktop->x, "X-Enlightenment-Config-Category-Icon");
             if ((cfg_cat_icon) && (cfg_cat_icon[0] != '/'))
               cfg_cat_icon = efreet_icon_path_find(e_config->icon_theme,
                                                    cfg_cat_icon, 64);
          }
        if (desktop->icon)
          {
             if (desktop->icon[0] == '/')
               cfg_icon = desktop->icon;
             else
               cfg_icon = efreet_icon_path_find(e_config->icon_theme,
                                                desktop->icon, 64);
          }
        if (desktop->name) label = desktop->name;
        else if (desktop->generic_name)
          label = desktop->generic_name;
        else label = "???";
        if (!cfg_cat_cfg)
          {
             const char *ic = cfg_cat_icon;

             if (dopref)
               {
                  snprintf(buf, sizeof(buf), "preferences/%s", label);
                  if (!ic) ic = "preferences-preferences";
                  e_configure_registry_category_add("preferences", 900,
                                                    _("Preferences"),
                                                    NULL, ic);
               }
             else
               {
                  snprintf(buf, sizeof(buf), "system/%s", label);
                  if (!ic) ic = "preferences-system";
                  e_configure_registry_category_add("system", 1000,
                                                    _("System"),
                                                    NULL, ic);
               }
             cfg_cat_cfg = buf;
          }
        else
          {
             cfg_cat = ecore_file_dir_get(cfg_cat_cfg);
             if (!cfg_cat) cfg_cat = strdup(cfg_cat_cfg);
             if (cfg_cat)
               {
                  if (!cfg_cat_name) cfg_cat_name = cfg_cat;
                  e_configure_registry_category_add(cfg_cat,
                                                    1000, cfg_cat_name,
                                                    NULL, cfg_cat_icon);
                  free(cfg_cat);
                  cfg_cat = NULL;
               }
          }
        _e_configure_registry_item_full_add(cfg_cat_cfg, cfg_pri, label,
                                            NULL, cfg_icon,
                                            NULL, NULL, desktop, NULL);
     }
   EINA_LIST_FREE(settings_desktops, desktop)
     efreet_desktop_free(desktop);
   EINA_LIST_FREE(system_desktops, desktop)
     efreet_desktop_free(desktop);
}

static Eina_Bool
_e_configure_cb_efreet_desktop_cache_update(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   _e_configure_efreet_desktop_cleanup();
   if (update_job) ecore_job_del(update_job);
   update_job = ecore_job_add(_configure_job, NULL);
   return 1;
}

static int
_e_configure_compare_cb(E_Configure_It *eci, E_Configure_It *eci2)
{
   return e_util_strcasecmp(eci->label, eci2->label);
}

static int
_e_configure_compare_pri_cb(E_Configure_It *eci, E_Configure_It *eci2)
{
   if (eci->pri == eci2->pri)
     return strcmp(eci->label, eci2->label);
   return eci->pri - eci2->pri;
}

static void
_e_configure_registry_item_full_add(const char *path, int pri, const char *label, const char *icon_file, const char *icon, E_Config_Dialog *(*func)(Evas_Object *parent, const char *params), void (*generic_func)(Evas_Object *parent, const char *params), Efreet_Desktop *desktop, const char *params)
{
   Eina_List *l;
   char *cat;
   const char *item;
   E_Configure_It *eci;
   E_Configure_Cat *ecat;
   Eina_Bool external;

   EINA_SAFETY_ON_NULL_RETURN(path);
   EINA_SAFETY_ON_NULL_RETURN(label);
   /* path is "category/item" */
   cat = ecore_file_dir_get(path);
   if (!cat) return;

   EINA_LIST_FOREACH(e_configure_registry, l, ecat)
     {
        if (strcmp(cat, ecat->cat)) continue;
        item = ecore_file_file_get(path);
        eci = E_NEW(E_Configure_It, 1);

        eci->item = eina_stringshare_add(item);
        eci->pri = pri;
        eci->label = eina_stringshare_add(label);
        if (icon_file) eci->icon_file = eina_stringshare_add(icon_file);
        if (icon) eci->icon = eina_stringshare_add(icon);
        if (params) eci->params = eina_stringshare_add(params);
        eci->func = func;
        eci->generic_func = generic_func;
        eci->desktop = desktop;
        if (eci->desktop) efreet_desktop_ref(eci->desktop);
        external = !strncmp(path, "preferences/", sizeof("preferences/") - 1);
        if (!external) external = !strncmp(path, "system/", sizeof("system/") - 1);
        if (external)
          ecat->items = eina_list_sorted_insert(ecat->items, EINA_COMPARE_CB(_e_configure_compare_cb), eci);
        else
          ecat->items = eina_list_sorted_insert(ecat->items, EINA_COMPARE_CB(_e_configure_compare_pri_cb), eci);
        break;
     }
   free(cat);
}

static void
_e_configure_registry_item_free(E_Configure_It *eci)
{
   eina_stringshare_del(eci->item);
   eina_stringshare_del(eci->label);
   eina_stringshare_del(eci->icon);
   if (eci->icon_file) eina_stringshare_del(eci->icon_file);
   if (eci->desktop) efreet_desktop_free(eci->desktop);
   if (eci->params) eina_stringshare_del(eci->params);
   free(eci);
}

#include "e.h"

/******** private function definitions **********/
static void _e_confirm_dialog_free(E_Confirm_Dialog *cd);
static void _e_confirm_dialog_delete(E_Dialog *dia, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _e_confirm_dialog_yes(void *data, E_Dialog *dia);
static void _e_confirm_dialog_no(void *data, E_Dialog *dia);

static void
_dia_del(void *data)
{
   E_Dialog *dia = data;

   e_object_del(dia->data);
}

/********** externally accessible functions ****************/
E_API E_Confirm_Dialog *
e_confirm_dialog_show(const char *title, const char *icon, const char *text,
                      const char *button_text, const char *button2_text,
                      void (*func)(void *data), void (*func2)(void *data),
                      void *data, void *data2,
                      void (*del_func)(void *data), void *del_data)
{
   E_Confirm_Dialog *cd;
   E_Dialog *dia;

   cd = E_OBJECT_ALLOC(E_Confirm_Dialog, E_CONFIRM_DIALOG_TYPE, _e_confirm_dialog_free);
   cd->yes.func = func;
   cd->yes.data = data;
   cd->no.func = func2;
   cd->no.data = data2;
   cd->del.func = del_func;
   cd->del.data = del_data;

   dia = e_dialog_new(NULL, "E", "_confirm_dialog");
   if (!dia)
     {
        e_object_del(E_OBJECT(cd));
        return NULL;
     }
   dia->data = cd;
   cd->dia = dia;

   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL, (Evas_Object_Event_Cb)_e_confirm_dialog_delete, dia);
   e_object_del_attach_func_set(E_OBJECT(dia), _dia_del);

   if (title) e_dialog_title_set(dia, title);
   if (icon) e_dialog_icon_set(dia, icon, 64);
   if (text) e_dialog_text_set(dia, text);

   e_dialog_button_add(dia, !button_text ? _("Yes") : button_text, NULL, _e_confirm_dialog_yes, cd);
   e_dialog_button_add(dia, !button2_text ? _("No") : button2_text, NULL, _e_confirm_dialog_no, cd);

   e_dialog_button_focus_num(dia, 0);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);

   return cd;
}

/********* private function bodies ************/
static void
_e_confirm_dialog_free(E_Confirm_Dialog *cd)
{
   if (!cd->no_run)
     {
        e_object_ref(E_OBJECT(cd));
        cd->no_run = EINA_TRUE;
        if (cd->no.func) cd->no.func(cd->no.data);
        e_object_unref(E_OBJECT(cd));
        return;
     }
   if (cd->del.func) cd->del.func(cd->del.data);
   e_object_del(E_OBJECT(cd->dia));
   free(cd);
}

static void
_e_confirm_dialog_yes(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Confirm_Dialog *cd;

   cd = data;
   e_object_ref(data);
   cd->no_run = EINA_TRUE;
   if (cd->yes.func) cd->yes.func(cd->yes.data);
   e_object_del(data);
   e_object_unref(data);
}

static void
_e_confirm_dialog_no(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Confirm_Dialog *cd;

   cd = data;
   e_object_ref(data);
   cd->no_run = EINA_TRUE;
   if (cd->no.func) cd->no.func(cd->no.data);
   e_object_del(data);
   e_object_unref(data);
}

static void
_e_confirm_dialog_delete(E_Dialog *dia, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_del(dia->data);
}

#include "e.h"

/* local subsystem functions */

/* local subsystem globals */
static Eina_Hash *store = NULL;

/* externally accessible functions */
E_API void
e_datastore_set(char *key, void *data)
{
   if (!store) store = eina_hash_string_superfast_new(NULL);
   eina_hash_del(store, key, NULL);
   eina_hash_add(store, key, data);
}

E_API void *
e_datastore_get(char *key)
{
   return eina_hash_find(store, key);
}

E_API void
e_datastore_del(char *key)
{
   eina_hash_del(store, key, NULL);
   if (eina_hash_population(store)) return;
   eina_hash_free(store);
   store = NULL;
}

/* local subsystem functions */
#include "e.h"

#define DBUS_MENU_IFACE "com.canonical.dbusmenu"

struct _E_DBusMenu_Ctx
{
   Eldbus_Proxy              *proxy;
   E_DBusMenu_Item          *root_menu;
   void                     *data;
   E_DBusMenu_Pop_Request_Cb pop_request_cb;
   E_DBusMenu_Update_Cb      update_cb;
   Eina_Bool  hacks E_BITFIELD;
};

static const char *Menu_Item_Type_Names[] =
{
   "standard", "separator"
};

static const char *Menu_Item_Toggle_Type_Names[] =
{
   "", "checkmark", "radio"
};

static const char *Menu_Item_Dispostion_Names[] =
{
   "normal", "informative", "warning", "alert"
};

static const char *Menu_Item_Event_Names[] =
{
   "clicked", "hovered", "opened", "closed"
};

static void proxy_init(E_DBusMenu_Ctx *ctx);

static int
id_find(const char *text, const char *array_of_names[], unsigned max)
{
   unsigned int i;
   for (i = 0; i < max; i++)
     {
        if (strcmp(text, array_of_names[i])) continue;
        return i;
     }
   return 0;
}

static void
dbus_menu_prop_dict_cb(void *data, const void *key, Eldbus_Message_Iter *var)
{
   E_DBusMenu_Item *m = data;
   if (!strcmp(key, "label"))
     {
        const char *label;
        Eina_Strbuf *label_buf = eina_strbuf_new();
        int i;

        eldbus_message_iter_arguments_get(var, "s", &label);
        for (i = 0; label[i]; i++)
          {
             if (label[i] != '_')
               {
                  eina_strbuf_append_char(label_buf, label[i]);
                  continue;
               }

             if (label[i + 1] == '_')
               {
                  eina_strbuf_append_char(label_buf, label[i]);
                  i++;
                  continue;
               }
          }
        eina_stringshare_replace(&m->label, eina_strbuf_string_get(label_buf));
        eina_strbuf_free(label_buf);
     }
   else if (!strcmp(key, "type"))
     {
        const char *type;
        eldbus_message_iter_arguments_get(var, "s", &type);
        m->type = id_find(type, Menu_Item_Type_Names, E_DBUSMENU_ITEM_TYPE_LAST);
     }
   else if (!strcmp(key, "icon-data"))
     {
        Eldbus_Message_Iter *array;
        int size;
        const unsigned char *img_data;

        eldbus_message_iter_arguments_get(var, "ay", &array);
        eldbus_message_iter_fixed_array_get(array, 'y', &img_data, &size);
        if (!size) return;
        m->icon_data = malloc(sizeof(unsigned char) * size);
        EINA_SAFETY_ON_FALSE_RETURN(m->icon_data);
        memcpy(m->icon_data, img_data, size);
        m->icon_data_size = size;
     }
   else if (!strcmp(key, "icon-name"))
     {
        const char *icon_name;
        eldbus_message_iter_arguments_get(var, "s", &icon_name);
        eina_stringshare_replace(&m->icon_name, icon_name);
     }
   else if (!strcmp(key, "toggle-type"))
     {
        const char *toggle_type;
        eldbus_message_iter_arguments_get(var, "s", &toggle_type);
        m->toggle_type = id_find(toggle_type, Menu_Item_Toggle_Type_Names,
                                 E_DBUSMENU_ITEM_TOGGLE_TYPE_LAST);
     }
   else if (!strcmp(key, "toggle-state"))
     {
        int state;
        eldbus_message_iter_arguments_get(var, "i", &state);
        if (state == 1) m->toggle_state = EINA_TRUE;
        else m->toggle_state = EINA_FALSE;
     }
   else if (!strcmp(key, "children-display"))
     {
        const char *display;
        eldbus_message_iter_arguments_get(var, "s", &display);
        if (!strcmp(display, "submenu")) m->is_submenu = EINA_TRUE;
        else m->is_submenu = EINA_FALSE;
     }
   else if (!strcmp(key, "disposition"))
     {
        const char *disposition;
        eldbus_message_iter_arguments_get(var, "s", &disposition);
        m->disposition = id_find(disposition, Menu_Item_Dispostion_Names,
                                 E_DBUSMENU_ITEM_DISPOSTION_LAST);
     }
   else if (!strcmp(key, "enabled"))
     eldbus_message_iter_arguments_get(var, "b", &m->enabled);
   else if (!strcmp(key, "visible"))
     eldbus_message_iter_arguments_get(var, "b", &m->visible);
}

static E_DBusMenu_Item *
parse_layout(Eldbus_Message_Iter *layout, E_DBusMenu_Item *parent, E_DBusMenu_Ctx *ctx)
{
   Eldbus_Message_Iter *menu_item_prop, *sub_menu_items_prop, *var;
   E_DBusMenu_Item *m = calloc(1, sizeof(E_DBusMenu_Item));
   EINA_SAFETY_ON_NULL_RETURN_VAL(m, NULL);
   m->references = 1;
   m->ctx = ctx;
   m->enabled = EINA_TRUE;
   m->visible = EINA_TRUE;

   if (!eldbus_message_iter_arguments_get(layout, "ia{sv}av", &m->id,
                                         &menu_item_prop, &sub_menu_items_prop))
     {
        ERR("Error reading message");
        free(m);
        return NULL;
     }

   eldbus_message_iter_dict_iterate(menu_item_prop, "sv", dbus_menu_prop_dict_cb, m);

   while (eldbus_message_iter_get_and_next(sub_menu_items_prop, 'v', &var))
     {
        Eldbus_Message_Iter *st;
        if (!eldbus_message_iter_arguments_get(var, "(ia{sv}av)", &st))
          {
             ERR("Error readding message.");
             continue;
          }
        parse_layout(st, m, ctx);
     }

   if (!parent) return m;

   parent->sub_items = eina_inlist_append(parent->sub_items, EINA_INLIST_GET(m));
   m->parent = parent;
   return NULL;
}

static void
dbus_menu_free(E_DBusMenu_Item *m)
{
   Eina_Inlist *inlist;
   E_DBusMenu_Item *child;

   EINA_INLIST_FOREACH_SAFE(m->sub_items, inlist, child)
     {
        e_dbusmenu_item_unref(child);
     }
   EINA_INLIST_FREE(m->sub_items, child)
     {
        m->sub_items = eina_inlist_remove
          (m->sub_items, EINA_INLIST_GET(child));
        child->parent = NULL;
     }
   if (m->icon_name) eina_stringshare_del(m->icon_name);
   if (m->label) eina_stringshare_del(m->label);
   if (m->parent) m->parent->sub_items = eina_inlist_remove
     (m->parent->sub_items, EINA_INLIST_GET(m));
   if (m->icon_data_size) free(m->icon_data);
   free(m);
}

static Eina_Bool
attempt_hacks(E_DBusMenu_Ctx *ctx)
{
   /* https://phab.enlightenment.org/T3139 */
   Eldbus_Object *obj;
   Eldbus_Connection *conn;
   const char *bus, *p;
   int n;
   char buf[1024] = {0}, buf2[1024 + 12] = {0};

   if (ctx->hacks) return EINA_FALSE;
   obj = eldbus_proxy_object_get(ctx->proxy);
   conn = eldbus_object_connection_get(obj);
   bus = eldbus_object_bus_name_get(obj);
   if (bus[0] != ':') return EINA_FALSE;
   /* if this is a qt5 app, menu bus is $bus + 2
    * ...probably
    */

   p = strchr(bus + 1, '.');
   if (!p) return EINA_FALSE;
   p++;
   if (!p[0]) return EINA_FALSE;
   n = strtol(p, NULL, 10);
   if (n == -1) return EINA_FALSE;
   n += 2;
   if ((unsigned int)(p - bus) > sizeof(buf) - 1) return EINA_FALSE;
   strncpy(buf, bus, p - bus);
   snprintf(buf2, sizeof(buf2), "%s%d", buf, n);
   E_FREE_FUNC(ctx->root_menu, e_dbusmenu_item_unref);
   eldbus_proxy_unref(ctx->proxy);
   eldbus_object_unref(obj);

   obj = eldbus_object_get(conn, buf2, "/MenuBar");
   ctx->proxy = eldbus_proxy_get(obj, DBUS_MENU_IFACE);
   proxy_init(ctx);
   ctx->hacks = 1;
   return EINA_TRUE;
}

static void
layout_get_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
   E_DBusMenu_Item *m;
   const char *error, *error_msg;
   Eldbus_Message_Iter *layout;
   unsigned revision;
   E_DBusMenu_Ctx *ctx = data;

   if (eldbus_message_error_get(msg, &error, &error_msg))
     {
        ERR("%s %s", error, error_msg);
        return;
     }

   if (!eldbus_message_arguments_get(msg, "u(ia{sv}av)", &revision, &layout))
     {
        ERR("Error reading message");
        return;
     }

   m = parse_layout(layout, NULL, ctx);
   m->revision = revision;
   if (m->is_submenu && (!m->parent) && (!m->sub_items))
     {
        if (attempt_hacks(ctx))
          {
             e_dbusmenu_item_unref(m);
             return;
          }
     }

   if (ctx->update_cb) ctx->update_cb(ctx->data, m);
   if (ctx->root_menu) e_dbusmenu_item_unref(ctx->root_menu);
   ctx->root_menu = m;
}

static E_DBusMenu_Item *
dbus_menu_find(E_DBusMenu_Ctx *ctx, int id)
{
   E_DBusMenu_Item *m;
   if (!ctx)
     return NULL;

   EINA_INLIST_FOREACH(ctx->root_menu, m)
     {
        E_DBusMenu_Item *child, *found;
        if (m->id == id) return m;
        EINA_INLIST_FOREACH(m->sub_items, child)
          {
             found = dbus_menu_find(ctx, id);
             if (found) return found;
          }
     }
   return NULL;
}

static void
menu_pop_request(void *data, const Eldbus_Message *msg)
{
   E_DBusMenu_Ctx *ctx = data;
   int id;
   unsigned timestamp;
   E_DBusMenu_Item *m;

   if (!eldbus_message_arguments_get(msg, "iu", &id, &timestamp))
     {
        ERR("Error reading values.");
        return;
     }

   m = dbus_menu_find(ctx, id);
   if (!m) return;
   if (ctx->pop_request_cb) ctx->pop_request_cb(ctx->data, m);
}

static void
prop_changed_cb(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
   const char *interface, *propname;
   Eldbus_Message_Iter *variant, *array;

   if (!eldbus_message_arguments_get(msg, "ssv", &interface, &propname, &variant))
     {
        ERR("Error getting values");
        return;
     }

   if (strcmp(propname, "IconThemePath")) return;

   if (!eldbus_message_iter_arguments_get(variant, "as", &array))
     {
        //TODO
     }
}

static void
layout_update(E_DBusMenu_Ctx *ctx)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *main_iter, *array;

   msg = eldbus_proxy_method_call_new(ctx->proxy, "GetLayout");
   main_iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_arguments_append(main_iter, "iias", 0, -1, &array);
   eldbus_message_iter_container_close(main_iter, array);
   eldbus_proxy_send(ctx->proxy, msg, layout_get_cb, ctx, -1);
}

static void
layout_updated_cb(void *data, const Eldbus_Message *msg EINA_UNUSED)
{
   E_DBusMenu_Ctx *ctx = data;
   layout_update(ctx);
}

static void
proxy_init(E_DBusMenu_Ctx *ctx)
{
   layout_update(ctx);
   eldbus_proxy_signal_handler_add(ctx->proxy,
                                  "ItemActivationRequested",
                                  menu_pop_request, ctx);

   eldbus_proxy_properties_changed_callback_add(ctx->proxy,
                                               prop_changed_cb, ctx);

   eldbus_proxy_signal_handler_add(ctx->proxy, "ItemsPropertiesUpdated",
                                  layout_updated_cb, ctx);
   eldbus_proxy_signal_handler_add(ctx->proxy, "LayoutUpdated",
                                  layout_updated_cb, ctx);
}

E_API E_DBusMenu_Ctx *
e_dbusmenu_load(Eldbus_Connection *conn, const char *bus, const char *path, const void *data)
{
   Eldbus_Object *obj;
   E_DBusMenu_Ctx *ctx;
   EINA_SAFETY_ON_NULL_RETURN_VAL(bus, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL);

   ctx = calloc(1, sizeof(E_DBusMenu_Ctx));
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);

   ctx->data = (void *)data;

   eldbus_connection_ref(conn);
   obj = eldbus_object_get(conn, bus, path);
   ctx->proxy = eldbus_proxy_get(obj, DBUS_MENU_IFACE);
   proxy_init(ctx);
   return ctx;
}

E_API void
e_dbusmenu_event_send(E_DBusMenu_Item *m, E_DBusMenu_Item_Event event)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *main_iter, *var;
   unsigned int timestamp = (unsigned int)time(NULL);

   EINA_SAFETY_ON_NULL_RETURN(m);
   EINA_SAFETY_ON_FALSE_RETURN(event < E_DBUSMENU_ITEM_EVENT_LAST);
   EINA_SAFETY_ON_NULL_RETURN(m->ctx);
   m->ctx->proxy = eldbus_proxy_ref(m->ctx->proxy);

   msg = eldbus_proxy_method_call_new(m->ctx->proxy, "Event");
   main_iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_arguments_append(main_iter, "is", m->id,
                                       Menu_Item_Event_Names[event]);

   var = eldbus_message_iter_container_new(main_iter, 'v', "s");
   /* dummy data */
   eldbus_message_iter_arguments_append(var, "s", "");
   eldbus_message_iter_container_close(main_iter, var);

   eldbus_message_iter_arguments_append(main_iter, "u", timestamp);

   eldbus_proxy_send(m->ctx->proxy, msg, NULL, NULL, -1);
}

E_API void
e_dbusmenu_item_ref(E_DBusMenu_Item *m)
{
   m->references++;
}

E_API void
e_dbusmenu_item_unref(E_DBusMenu_Item *m)
{
   m->references--;
   if (m->references == 0) dbus_menu_free(m);
}

E_API void
e_dbusmenu_unload(E_DBusMenu_Ctx *ctx)
{
   Eldbus_Connection *conn;
   Eldbus_Object *obj;
   EINA_SAFETY_ON_NULL_RETURN(ctx);

   if (ctx->root_menu) e_dbusmenu_item_unref(ctx->root_menu);
   obj = eldbus_proxy_object_get(ctx->proxy);
   conn = eldbus_object_connection_get(obj);
   eldbus_proxy_unref(ctx->proxy);
   eldbus_object_unref(obj);
   eldbus_connection_unref(conn);
   free(ctx);
}

E_API void
e_dbusmenu_pop_request_cb_set(E_DBusMenu_Ctx *ctx, E_DBusMenu_Pop_Request_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(ctx);
   ctx->pop_request_cb = cb;
}

E_API void
e_dbusmenu_update_cb_set(E_DBusMenu_Ctx *ctx, E_DBusMenu_Update_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(ctx);
   ctx->update_cb = cb;
}

#include "e.h"

/* E_Desk is a child object of E_Zone. A desk is essentially a background
 * and an associated set of client windows. Each zone can have an arbitrary
 * number of desktops.
 */

static void      _e_desk_free(E_Desk *desk);
static void      _e_desk_event_desk_show_free(void *data, void *ev);
static void      _e_desk_event_desk_before_show_free(void *data, void *ev);
static void      _e_desk_event_desk_after_show_free(void *data, void *ev);
static void      _e_desk_event_desk_deskshow_free(void *data, void *ev);
static void      _e_desk_event_desk_name_change_free(void *data, void *ev);
static void      _e_desk_show_begin(E_Desk *desk, int dx, int dy);
static void      _e_desk_hide_begin(E_Desk *desk, int dx, int dy);
static void      _e_desk_event_desk_window_profile_change_free(void *data, void *ev);
static void      _e_desk_window_profile_change_protocol_set(void);

static E_Desk_Flip_Cb _e_desk_flip_cb = NULL;
static void *_e_desk_flip_data = NULL;

E_API int E_EVENT_DESK_SHOW = 0;
E_API int E_EVENT_DESK_BEFORE_SHOW = 0;
E_API int E_EVENT_DESK_AFTER_SHOW = 0;
E_API int E_EVENT_DESK_DESKSHOW = 0;
E_API int E_EVENT_DESK_NAME_CHANGE = 0;
E_API int E_EVENT_DESK_WINDOW_PROFILE_CHANGE = 0;

EINTERN int
e_desk_init(void)
{
   E_EVENT_DESK_SHOW = ecore_event_type_new();
   E_EVENT_DESK_BEFORE_SHOW = ecore_event_type_new();
   E_EVENT_DESK_AFTER_SHOW = ecore_event_type_new();
   E_EVENT_DESK_DESKSHOW = ecore_event_type_new();
   E_EVENT_DESK_NAME_CHANGE = ecore_event_type_new();
   E_EVENT_DESK_WINDOW_PROFILE_CHANGE = ecore_event_type_new();
   return 1;
}

EINTERN int
e_desk_shutdown(void)
{
   return 1;
}

static void
_do_profile(E_Desk *desk, E_Randr2_Screen *sc)
{
   int scale = 100;
   char buf[PATH_MAX], buf2[PATH_MAX + 256];

   eina_stringshare_del(desk->window_profile);
   desk->window_profile = NULL;

   if (sc->config.scale_multiplier > 0.0)
     {
        scale = (int)(sc->config.scale_multiplier * 100.0);
     }
   else if (e_config->scale.use_dpi)
     {
        double dpi = e_randr2_screen_dpi_get(sc);

        if ((dpi > 0.0) && (e_config->scale.base_dpi > 0))
          scale = (int)((100.0 * dpi) / (double)e_config->scale.base_dpi);
     }

   if (scale == 100)
     snprintf(buf, sizeof(buf), "%s", sc->config.profile);
   else
     {
        snprintf(buf, sizeof(buf), ".scale-%04i-%s",
                 scale, sc->config.profile);
        if (!elm_config_profile_exists(buf))
          {
             snprintf(buf2, sizeof(buf2),
                      "%s/enlightenment/utils/enlightenment_elm_cfgtool "
                      "set %s %s 'scale-mul %i'",
                      e_prefix_lib_get(), sc->config.profile, buf, scale);
             if (system(buf2) != 0)
               ERR("Error code from trying to run \"%s\"", buf2);
          }
     }
   desk->window_profile = eina_stringshare_add(buf);
}

E_API E_Desk *
e_desk_new(E_Zone *zone, int x, int y)
{
   E_Desk *desk;
   Eina_List *l;
   E_Config_Desktop_Name *cfname;
   int ok = 0;

   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);

   desk = E_OBJECT_ALLOC(E_Desk, E_DESK_TYPE, _e_desk_free);
   if (!desk) return NULL;

   _e_desk_window_profile_change_protocol_set();

   desk->zone = zone;
   desk->x = x;
   desk->y = y;

   /* Get current desktop's name */
   EINA_LIST_FOREACH(e_config->desktop_names, l, cfname)
     {
        if ((cfname->zone >= 0) &&
            ((int)zone->num != cfname->zone)) continue;
        if ((cfname->desk_x != desk->x) || (cfname->desk_y != desk->y))
          continue;
        desk->name = eina_stringshare_ref(cfname->name);
        ok = 1;
        break;
     }

   if (!ok)
     {
        char buff[PATH_MAX];

        snprintf(buff, sizeof(buff), _("Desktop %d,%d"), desk->x, desk->y);
        desk->name = eina_stringshare_add(buff);
     }

   /* Get window profile name for current desktop */
   if (zone->randr2_id)
     {
        E_Randr2_Screen *sc = e_randr2_screen_id_find(zone->randr2_id);

        if ((sc) && (sc->config.profile)) _do_profile(desk, sc);
     }

   return desk;
}

E_API E_Client *
e_desk_client_top_visible_get(const E_Desk *desk)
{
   E_Client *ec;

   E_OBJECT_CHECK_RETURN(desk, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(desk, E_DESK_TYPE, NULL);

   E_CLIENT_REVERSE_FOREACH(ec)
     if (e_client_util_desk_visible(ec, desk) && evas_object_visible_get(ec->frame)) return ec;
   return NULL;
}

E_API void
e_desk_name_set(E_Desk *desk, const char *name)
{
   E_Event_Desk_Name_Change *ev;

   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);

   eina_stringshare_replace(&desk->name, name);

   ev = E_NEW(E_Event_Desk_Name_Change, 1);
   ev->desk = desk;
   e_object_ref(E_OBJECT(desk));
   ecore_event_add(E_EVENT_DESK_NAME_CHANGE, ev,
                   _e_desk_event_desk_name_change_free, NULL);
}

E_API void
e_desk_name_add(int zone, int desk_x, int desk_y, const char *name)
{
   E_Config_Desktop_Name *cfname;

   e_desk_name_del(zone, desk_x, desk_y);

   cfname = E_NEW(E_Config_Desktop_Name, 1);
   cfname->zone = zone;
   cfname->desk_x = desk_x;
   cfname->desk_y = desk_y;
   if (name)
     cfname->name = eina_stringshare_add(name);
   else
     {
        char buff[PATH_MAX];

        snprintf(buff, sizeof(buff), _("Desktop %d,%d"),
                 cfname->desk_x, cfname->desk_y);
        cfname->name = eina_stringshare_add(buff);
     }
   e_config->desktop_names = eina_list_append(e_config->desktop_names, cfname);
}

E_API void
e_desk_name_del(int zone, int desk_x, int desk_y)
{
   Eina_List *l = NULL;
   E_Config_Desktop_Name *cfname = NULL;

   EINA_LIST_FOREACH(e_config->desktop_names, l, cfname)
     {
        if ((cfname->zone == zone) &&
            (cfname->desk_x == desk_x) && (cfname->desk_y == desk_y))
          {
             e_config->desktop_names =
               eina_list_remove_list(e_config->desktop_names, l);
             if (cfname->name) eina_stringshare_del(cfname->name);
             E_FREE(cfname);
             break;
          }
     }
}

E_API void
e_desk_name_update(void)
{
   const Eina_List *z, *l;
   E_Zone *zone;
   E_Desk *desk;
   E_Config_Desktop_Name *cfname;
   int d_x, d_y, ok;

   EINA_LIST_FOREACH(e_comp->zones, z, zone)
     {
        for (d_x = 0; d_x < zone->desk_x_count; d_x++)
          {
             for (d_y = 0; d_y < zone->desk_y_count; d_y++)
               {
                  desk = zone->desks[d_x + zone->desk_x_count * d_y];
                  ok = 0;

                  EINA_LIST_FOREACH(e_config->desktop_names, l, cfname)
                    {
                       if ((cfname->zone >= 0) &&
                           ((int)zone->num != cfname->zone)) continue;
                       if ((cfname->desk_x != d_x) ||
                           (cfname->desk_y != d_y)) continue;
                       if (cfname->name)
                         e_desk_name_set(desk, cfname->name);
                       else
                         {
                            char buff[PATH_MAX];

                            snprintf(buff, sizeof(buff), _("Desktop %d,%d"),
                                     cfname->desk_x, cfname->desk_y);
                            e_desk_name_set(desk, buff);
                         }
                       ok = 1;
                       break;
                    }

                  if (!ok) e_desk_name_set(desk, "");
               }
          }
     }
}

E_API void
e_desk_show(E_Desk *desk)
{
   E_Event_Desk_Show *ev;
   E_Event_Desk_Before_Show *eev;
   E_Event_Desk_After_Show *eeev;
   Edje_Message_Float_Set *msg;
   Eina_List *l;
   E_Shelf *es;
   E_Desk *desk2;
   int was_zone = 0, dx = 0, dy = 0;

   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);
   if (desk->visible) return;
   if (e_client_action_get() && e_client_util_resizing_get(e_client_action_get())) return;

   desk2 = e_desk_at_xy_get(desk->zone, desk->zone->desk_x_current, desk->zone->desk_y_current);
   if ((!starting) && (!desk2->visible)) return;
   eev = E_NEW(E_Event_Desk_Before_Show, 1);
   eev->desk = e_desk_current_get(desk->zone);
   e_object_ref(E_OBJECT(eev->desk));
   ecore_event_add(E_EVENT_DESK_BEFORE_SHOW, eev,
                   _e_desk_event_desk_before_show_free, NULL);

   if (desk2->visible)
     {
        desk2->visible = 0;
        if (e_config->desk_flip_wrap)
          {
             /* current desk (desk2) is last desk, switching to first desk (desk) */
             if ((!desk->x) && (!desk->y) && (desk2->x + 1 == desk->zone->desk_x_count) && (desk2->y + 1 == desk->zone->desk_y_count))
               {
                  dx = (desk->x != desk2->x) ? 1 : 0;
                  dy = (desk->y != desk2->y) ? 1 : 0;
               }
             /* current desk (desk2) is first desk, switching to last desk (desk) */
             else if ((!desk2->x) && (!desk2->y) && (desk->x + 1 == desk->zone->desk_x_count) && (desk->y + 1 == desk->zone->desk_y_count))
               {
                  dx = (desk->x != desk2->x) ? -1 : 0;
                  dy = (desk->y != desk2->y) ? -1 : 0;
               }
          }
        if ((!dx) && (!dy))
          {
             dx = desk->x - desk2->x;
             dy = desk->y - desk2->y;
          }
        _e_desk_hide_begin(desk2, dx, dy);
        if (desk2->obstacles || desk->obstacles)
          e_zone_useful_geometry_dirty(desk->zone);
     }

   desk->zone->desk_x_prev = desk->zone->desk_x_current;
   desk->zone->desk_y_prev = desk->zone->desk_y_current;
   desk->zone->desk_x_current = desk->x;
   desk->zone->desk_y_current = desk->y;
   desk->visible = 1;

   msg = alloca(sizeof(Edje_Message_Float_Set) + (4 * sizeof(double)));
   msg->count = 5;
   msg->val[0] = 0.2 * (!!e_config->desk_flip_animate_mode);//e_config->desk_flip_animate_time;
   msg->val[1] = desk->x;
   msg->val[2] = desk->zone->desk_x_count;
   msg->val[3] = desk->y;
   msg->val[4] = desk->zone->desk_y_count;
   if (desk->zone->bg_object)
     edje_object_message_send(desk->zone->bg_object, EDJE_MESSAGE_FLOAT_SET, 0, msg);

   if (desk->zone->bg_object) was_zone = 1;
   _e_desk_show_begin(desk, dx, dy);

   if (was_zone)
     e_bg_zone_update(desk->zone, E_BG_TRANSITION_DESK);
   else
     e_bg_zone_update(desk->zone, E_BG_TRANSITION_START);

   ev = E_NEW(E_Event_Desk_Show, 1);
   ev->desk = desk;
   e_object_ref(E_OBJECT(desk));
   ecore_event_add(E_EVENT_DESK_SHOW, ev, _e_desk_event_desk_show_free, NULL);

   EINA_LIST_FOREACH(e_shelf_list(), l, es)
     {
        if (es->zone != desk->zone) continue;
        if (e_shelf_desk_visible(es, desk))
          e_shelf_show(es);
        else
          e_shelf_hide(es);
     }

   if (e_config->desk_flip_animate_mode == 0)
     {
        eeev = E_NEW(E_Event_Desk_After_Show, 1);
        eeev->desk = e_desk_current_get(desk->zone);
        e_object_ref(E_OBJECT(eeev->desk));
        ecore_event_add(E_EVENT_DESK_AFTER_SHOW, eeev,
                        _e_desk_event_desk_after_show_free, NULL);
     }
   e_zone_edge_flip_eval(desk->zone);
}

E_API void
e_desk_deskshow(E_Zone *zone)
{
   E_Client *ec;
   E_Desk *desk;
   E_Event_Desk_Show *ev;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   desk = e_desk_current_get(zone);
   if (desk->deskshow_toggle)
     {
        /* uniconify raises windows and changes stacking order
         * go top-down to avoid skipping windows
         */
        E_CLIENT_REVERSE_FOREACH(ec)
          {
             if (e_client_util_ignored_get(ec)) continue;
             if (ec->desk != desk) continue;
             if (ec->deskshow)
               {
                  ec->deskshow = 0;
                  e_client_uniconify(ec);
               }
          }
     }
   else
     {
        /*
         * iconify raises, so we have to start from the bottom so we are going forward
         */
        E_CLIENT_FOREACH(ec)
          {
             if (e_client_util_ignored_get(ec)) continue;
             if (ec->desk != desk) continue;
             if (ec->iconic) continue;
             if (ec->netwm.state.skip_taskbar) continue;
             if (ec->user_skip_winlist) continue;
             ec->deskshow = 1;
             e_client_iconify(ec);
          }
     }
   desk->deskshow_toggle = !desk->deskshow_toggle;
   ev = E_NEW(E_Event_Desk_Show, 1);
   ev->desk = desk;
   e_object_ref(E_OBJECT(desk));
   ecore_event_add(E_EVENT_DESK_DESKSHOW, ev,
                   _e_desk_event_desk_deskshow_free, NULL);
}

E_API E_Client *
e_desk_last_focused_focus(E_Desk *desk)
{
   Eina_List *l = NULL;
   E_Client *ec, *ecs = NULL;

   EINA_LIST_FOREACH(e_client_focus_stack_get(), l, ec)
     {
        if ((!ec->iconic) && (evas_object_visible_get(ec->frame) || ec->changes.visible) &&
            ((desk &&
            ((ec->desk == desk) || ((ec->zone == desk->zone) && ec->sticky))) ||
            ((!desk) && ec->desk->visible)) &&
            (ec->icccm.accepts_focus || ec->icccm.take_focus) &&
            (ec->netwm.type != E_WINDOW_TYPE_DOCK) &&
            (ec->netwm.type != E_WINDOW_TYPE_TOOLBAR) &&
            (ec->netwm.type != E_WINDOW_TYPE_MENU) &&
            (ec->netwm.type != E_WINDOW_TYPE_SPLASH) &&
            (ec->netwm.type != E_WINDOW_TYPE_DESKTOP) &&
            (!e_object_is_del(E_OBJECT(ec))))
          {
             /* this was the window last focused in this desktop */
             if (!ec->lock_focus_out)
               {
                  if (ec->sticky && (!e_config->focus_revert_allow_sticky))
                    {
                       ecs = ec;
                       continue;
                    }
                  if (ec->changes.visible && (!evas_object_visible_get(ec->frame)))
                    ec->want_focus = ec->take_focus = 1;
                  else
                    e_client_focus_set_with_pointer(ec);
                  if (e_config->raise_on_revert_focus)
                    evas_object_raise(ec->frame);
                  return ec;
               }
          }
     }
   if (ecs)
     {
        if (ecs->changes.visible && (!evas_object_visible_get(ecs->frame)))
          ecs->want_focus = ecs->take_focus = 1;
        else
          e_client_focus_set_with_pointer(ecs);
        if (e_config->raise_on_revert_focus)
          evas_object_raise(ecs->frame);
        return ecs;
     }
   if (e_client_focused_get()) {
     evas_object_focus_set(e_client_focused_get()->frame, 0);
   }
   return NULL;
}

E_API void
e_desk_row_add(E_Zone *zone)
{
   e_zone_desk_count_set(zone, zone->desk_x_count, zone->desk_y_count + 1);
}

E_API void
e_desk_row_remove(E_Zone *zone)
{
   e_zone_desk_count_set(zone, zone->desk_x_count, zone->desk_y_count - 1);
}

E_API void
e_desk_col_add(E_Zone *zone)
{
   e_zone_desk_count_set(zone, zone->desk_x_count + 1, zone->desk_y_count);
}

E_API void
e_desk_col_remove(E_Zone *zone)
{
   e_zone_desk_count_set(zone, zone->desk_x_count - 1, zone->desk_y_count);
}

E_API E_Desk *
e_desk_current_get(E_Zone *zone)
{
   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);

   return e_desk_at_xy_get(zone, zone->desk_x_current, zone->desk_y_current);
}

E_API E_Desk *
e_desk_at_xy_get(const E_Zone *zone, int x, int y)
{
   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);

   if ((x >= zone->desk_x_count) || (y >= zone->desk_y_count))
     return NULL;
   else if ((x < 0) || (y < 0))
     return NULL;

   if (!zone->desks) return NULL;
   return zone->desks[x + (y * zone->desk_x_count)];
}

E_API E_Desk *
e_desk_at_pos_get(E_Zone *zone, int pos)
{
   int x, y;

   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);

   y = pos / zone->desk_x_count;
   x = pos - (y * zone->desk_x_count);

   if ((x >= zone->desk_x_count) || (y >= zone->desk_y_count))
     return NULL;

   return zone->desks[x + (y * zone->desk_x_count)];
}

E_API void
e_desk_xy_get(E_Desk *desk, int *x, int *y)
{
   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);

   if (x) *x = desk->x;
   if (y) *y = desk->y;
}

E_API void
e_desk_next(E_Zone *zone)
{
   int x, y;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if ((zone->desk_x_count < 2) && (zone->desk_y_count < 2))
     return;

   x = zone->desk_x_current;
   y = zone->desk_y_current;

   x++;
   if (x >= zone->desk_x_count)
     {
        x = 0;
        y++;
        if (y >= zone->desk_y_count) y = 0;
     }

   e_desk_show(e_desk_at_xy_get(zone, x, y));
}

E_API void
e_desk_prev(E_Zone *zone)
{
   int x, y;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if ((zone->desk_x_count < 2) && (zone->desk_y_count < 2))
     return;

   x = zone->desk_x_current;
   y = zone->desk_y_current;

   x--;
   if (x < 0)
     {
        x = zone->desk_x_count - 1;
        y--;
        if (y < 0) y = zone->desk_y_count - 1;
     }
   e_desk_show(e_desk_at_xy_get(zone, x, y));
}

E_API void
e_desk_window_profile_set(E_Desk *desk,
                          const char *profile)
{
   E_Event_Desk_Window_Profile_Change *ev;

   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);

   eina_stringshare_replace(&desk->window_profile, profile);

   ev = E_NEW(E_Event_Desk_Window_Profile_Change, 1);
   ev->desk = desk;
   e_object_ref(E_OBJECT(desk));
   ecore_event_add(E_EVENT_DESK_WINDOW_PROFILE_CHANGE, ev,
                   _e_desk_event_desk_window_profile_change_free, NULL);
}

E_API void
e_desk_window_profile_update(E_Zone *zone)
{
   if (zone->randr2_id)
     {
        E_Randr2_Screen *sc = e_randr2_screen_id_find(zone->randr2_id);

        if ((sc) && (sc->config.profile))
          {
             int x, y;

             for (y = 0; y < zone->desk_y_count; y++)
               {
                  for (x = 0; x < zone->desk_x_count; x++)
                    _do_profile(zone->desks[x + (y * zone->desk_x_count)], sc);
               }
          }
     }
}

E_API void
e_desk_flip_cb_set(E_Desk_Flip_Cb cb, const void *data)
{
   _e_desk_flip_cb = cb;
   _e_desk_flip_data = (void*)data;
}

E_API void
e_desk_flip_end(E_Desk *desk)
{
   E_Event_Desk_After_Show *ev;
   E_Client *ec;
   Eina_Bool do_global_focus = EINA_FALSE;

   ev = E_NEW(E_Event_Desk_After_Show, 1);
   ev->desk = desk;
   e_object_ref(E_OBJECT(ev->desk));
   ecore_event_add(E_EVENT_DESK_AFTER_SHOW, ev,
                   _e_desk_event_desk_after_show_free, NULL);

   e_comp_shape_queue();
   if (desk->zone->desk_flip_sync)
     {
        Eina_List *l;
        E_Zone *zone;
        Eina_Bool sync = EINA_FALSE;

        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          {
             if (zone != desk->zone)
               sync |= zone->desk_flip_sync;
          }
        do_global_focus = !sync;
        desk->zone->desk_flip_sync = 0;
        if (!do_global_focus) return;
     }
   if (!e_config->focus_last_focused_per_desktop) return;
   if ((e_config->focus_policy == E_FOCUS_MOUSE) ||
       (e_config->focus_policy == E_FOCUS_SLOPPY))
     {
        ec = e_client_focused_get();
        /* only set focus/warp pointer if currently focused window
         * is on same screen (user hasn't switched screens during transition)
         */
        if (do_global_focus)
          {
             if (ec && e_client_util_desk_visible(ec, ec->desk)) return;
          }
        else
          {
             if (ec && ec->desk && (ec->desk->zone != desk->zone)) return;
          }
     }
   if (starting) return;
   ec = e_desk_last_focused_focus(do_global_focus ? NULL : desk);
   if ((e_config->focus_policy != E_FOCUS_MOUSE) && (!ec))
     {
        /* we didn't previously have a focused window on this desk
         * but we should, so this is probably the first time the
         * user has flipped to this desk. let's be helpful and
         * focus a random window!
         */
         E_CLIENT_REVERSE_FOREACH(ec)
           {
              /* start with top and go down... */
              if (e_client_util_ignored_get(ec)) continue;
              if (!evas_object_visible_get(ec->frame)) continue;
              if (ec->iconic) continue;
              evas_object_focus_set(ec->frame, 1);
              if (e_config->raise_on_revert_focus)
                evas_object_raise(ec->frame);
              return;
           }
     }
}

E_API unsigned int
e_desks_count(void)
{
   Eina_List *l;
   E_Zone *zone;
   unsigned int count = 0;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        int cx = 0, cy = 0;

        e_zone_desk_count_get(zone, &cx, &cy);
        count += cx * cy;
     }
   return count;
}

static void
_e_desk_free(E_Desk *desk)
{
   while (desk->obstacles)
     {
        E_Object *obs = (void*)EINA_INLIST_CONTAINER_GET(desk->obstacles, E_Zone_Obstacle);
        e_object_del(obs);
     }
   eina_stringshare_del(desk->name);
   desk->name = NULL;
   free(desk);
}

static void
_e_desk_event_desk_show_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_Show *ev;

   ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_desk_event_desk_before_show_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_Before_Show *ev;

   ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_desk_event_desk_after_show_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_After_Show *ev;

   ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_desk_event_desk_deskshow_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_Show *ev;

   ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_desk_event_desk_name_change_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_Name_Change *ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   free(ev);
}

static void
_e_desk_event_desk_window_profile_change_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Desk_Window_Profile_Change *ev = event;
   e_object_unref(E_OBJECT(ev->desk));
   E_FREE(ev);
}

static Eina_Bool
_e_desk_transition_setup(E_Client *ec, int dx, int dy, int state)
{
   e_comp_object_effect_set(ec->frame, e_config->desk_flip_animate_type ?: "none");
   if (e_config->desk_flip_animate_type)
     {
        /* set geoms */
        e_comp_object_effect_params_set(ec->frame, 1, (int[]){ec->x - ec->zone->x, ec->y - ec->zone->y, ec->w, ec->h, ec->zone->w, ec->zone->h, dx, dy}, 8);
        e_comp_object_effect_params_set(ec->frame, 0, (int[]){state}, 1);
     }

   return !!e_config->desk_flip_animate_type;
}

static void
_e_desk_show_end(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Client *ec = data;

   ec->desk->animate_count--;
   e_client_comp_hidden_set(ec, ec->shaded);
   e_comp_object_effect_unclip(ec->frame);
   ec->hidden = 0;
   if (!ec->visible) evas_object_show(ec->frame);
   if (ec->desk != e_desk_current_get(ec->zone)) return;
   if (!ec->desk->animate_count) e_desk_flip_end(ec->desk);
}

static void
_e_desk_hide_end(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Client *ec = data;

   ec->desk->animate_count--;
   ec->hidden = 1;
   evas_object_hide(ec->frame);
}

static void
_e_desk_show_begin(E_Desk *desk, int dx, int dy)
{
   E_Client *ec;

   if (dx < 0) dx = -1;
   if (dx > 0) dx = 1;
   if (dy < 0) dy = -1;
   if (dy > 0) dy = 1;

   desk->animate_count = 0;
   if (_e_desk_flip_cb && e_config->desk_flip_animate_type)
     {
        _e_desk_flip_cb(_e_desk_flip_data, desk, dx, dy, 1);
        return;
     }
   E_CLIENT_FOREACH(ec)
     {
        if (e_client_util_ignored_get(ec) || (ec->desk->zone != desk->zone) || (ec->iconic)) continue;
        if (ec->moving)
          {
             e_client_desk_set(ec, desk);
             evas_object_show(ec->frame);
             continue;
          }
        if ((ec->desk != desk) || (ec->sticky)) continue;
        if ((!starting) && (!ec->new_client) && _e_desk_transition_setup(ec, dx, dy, 1))
          {
             e_comp_object_effect_stop(ec->frame, _e_desk_hide_end);
             e_comp_object_effect_start(ec->frame, _e_desk_show_end, ec);
             desk->animate_count++;
          }
        else
          ec->hidden = 0;

        e_client_comp_hidden_set(ec, ec->hidden || ec->shaded);
        evas_object_show(ec->frame);
     }
   if ((!e_config->desk_flip_animate_type) || (!desk->animate_count))
     e_desk_flip_end(desk);
}

static void
_e_desk_hide_begin(E_Desk *desk, int dx, int dy)
{
   E_Client *ec;

   if (dx < 0) dx = -1;
   if (dx > 0) dx = 1;
   if (dy < 0) dy = -1;
   if (dy > 0) dy = 1;

   desk->animate_count = 0;
   if (_e_desk_flip_cb && e_config->desk_flip_animate_type)
     {
        _e_desk_flip_cb(_e_desk_flip_data, desk, dx, dy, 0);
        return;
     }
   E_CLIENT_FOREACH(ec)
     {
        if (e_client_util_ignored_get(ec) || (ec->desk->zone != desk->zone) || (ec->iconic)) continue;
        if (ec->moving) continue;
        if ((ec->desk != desk) || (ec->sticky)) continue;
        if ((!starting) && (!ec->new_client) && _e_desk_transition_setup(ec, -dx, -dy, 0))
          {
             e_comp_object_effect_stop(ec->frame, _e_desk_show_end);
             e_comp_object_effect_start(ec->frame, _e_desk_hide_end, ec);
             desk->animate_count++;
          }
        else
          {
             ec->hidden = 1;
             evas_object_show(ec->frame);
             ec->changes.visible = 0;
             evas_object_hide(ec->frame);
          }
        e_client_comp_hidden_set(ec, EINA_TRUE);
     }
}

static void
_e_desk_window_profile_change_protocol_set(void)
{
#ifndef HAVE_WAYLAND_ONLY
   static Eina_Bool is_set = EINA_FALSE;

   if (!is_set)
     {
        if (e_comp->root)
          {
             is_set = EINA_TRUE;
             ecore_x_e_window_profile_supported_set(e_comp->root, EINA_TRUE);
          }
     }
#endif
}
#include "e.h"

EINTERN int
e_deskenv_init(void)
{
#ifndef HAVE_WAYLAND_ONLY
   char buf[PATH_MAX], buf2[PATH_MAX + sizeof("xrdb -override ")];

   // run xdrb -load .Xdefaults & .Xresources
   // NOTE: one day we should replace this with an e based config + service
   if (e_config->deskenv.load_xrdb)
     {
        Eina_Bool exists = EINA_FALSE;

        e_user_homedir_concat(buf, sizeof(buf), ".Xdefaults");
        exists = ecore_file_exists(buf);
        if (!exists)
          {
             e_user_homedir_concat(buf, sizeof(buf), ".Xresources");
             exists = ecore_file_exists(buf);
          }
        if (exists)
          {
#if 1
             snprintf(buf2, sizeof(buf2), "xrdb -override %s", buf);
             if (system(buf2) != 0)
               fprintf(stderr, "Execution of [%s] did not exit cleanly\n", buf2);
#else
             // while this SHOULD work.. it ends up with mysterious problems
             // inside xlib that i seem to not be able to trap easily...
             ecore_x_rersource_load(buf);
             ecore_x_resource_db_flush();
#endif
          }
     }

   e_deskenv_xmodmap_run();
   // make gnome apps happy
   // NOTE: one day we should replace this with an e based config + service
   if (e_config->deskenv.load_gnome)
     {
        ecore_exe_run("gnome-settings-daemon", NULL);
     }

   // make kde apps happy
   // NOTE: one day we should replace this with an e based config + service ??
   if (e_config->deskenv.load_kde)
     {
        ecore_exe_run("kdeinit", NULL);
     }
#endif
   return 1;
}

EINTERN int
e_deskenv_shutdown(void)
{
   return 1;
}

E_API void
e_deskenv_xmodmap_run(void)
{
#ifndef HAVE_WAYLAND_ONLY
   char buf[PATH_MAX], buf2[PATH_MAX + sizeof("xmodmap ")];
   // load ~/.Xmodmap
   // NOTE: one day we should replace this with an e based config + service
   if (!e_config->deskenv.load_xmodmap) return;
   e_user_homedir_concat(buf, sizeof(buf), ".Xmodmap");
   if (!ecore_file_exists(buf)) return;
   snprintf(buf2, sizeof(buf2), "xmodmap %s", buf);
   ecore_exe_run(buf2, NULL);
#endif
}

#include "e.h"

/**************************** private data ******************************/

typedef struct _E_Desklock_Run E_Desklock_Run;


struct _E_Desklock_Run
{
   E_Order *desk_run;
   int      position;
};

static Ecore_Event_Handler *_e_desklock_run_handler = NULL;
static Ecore_Event_Handler *_e_desklock_randr_handler = NULL;
static Ecore_Job *job = NULL;
static Eina_List *tasks = NULL;

static Eina_List *show_hooks = NULL;
static Eina_List *hide_hooks = NULL;

static Evas_Object *block_rects[32] = {NULL};
static Eina_Bool block_zone[32] = {EINA_FALSE};

static Eina_List *desklock_ifaces = NULL;
static E_Desklock_Interface *current_iface = NULL;
static Eina_Bool demo = EINA_FALSE;

static Eina_Bool _e_desklock_want = EINA_FALSE;
static int _e_desklock_block = 0;
static Eina_Bool desklock_manual = EINA_FALSE;

/***********************************************************************/
static Eina_Bool _e_desklock_cb_run(void *data, int type, void *event);
static Eina_Bool _e_desklock_cb_randr(void *data, int type, void *event);

static Eina_Bool _e_desklock_state = EINA_FALSE;

E_API int E_EVENT_DESKLOCK = 0;

EINTERN int
e_desklock_init(void)
{
   Eina_List *l;
   E_Config_Desklock_Background *bg;

   EINA_LIST_FOREACH(e_config->desklock_backgrounds, l, bg)
     e_filereg_register(bg->file);

   E_EVENT_DESKLOCK = ecore_event_type_new();

   _e_desklock_run_handler = ecore_event_handler_add(E_EVENT_DESKLOCK,
                                                     _e_desklock_cb_run, NULL);

   _e_desklock_randr_handler = ecore_event_handler_add(E_EVENT_RANDR_CHANGE,
                                                       _e_desklock_cb_randr, NULL);
   return 1;
}

EINTERN int
e_desklock_shutdown(void)
{
   E_Desklock_Run *task;
   Eina_List *l;
   E_Config_Desklock_Background *bg;

   desklock_ifaces = eina_list_free(desklock_ifaces);
   if (!x_fatal)
     e_desklock_hide();

   ecore_event_handler_del(_e_desklock_run_handler);
   _e_desklock_run_handler = NULL;
   ecore_event_handler_del(_e_desklock_randr_handler);
   _e_desklock_randr_handler = NULL;

   if (job) ecore_job_del(job);
   job = NULL;

   EINA_LIST_FOREACH(e_config->desklock_backgrounds, l, bg)
     e_filereg_deregister(bg->file);

   EINA_LIST_FREE(tasks, task)
     {
        e_object_del(E_OBJECT(task->desk_run));
        free(task);
     }

   return 1;
}

E_API Eina_Stringshare *
e_desklock_user_wallpaper_get(E_Zone *zone)
{
   const E_Config_Desktop_Background *cdbg;
   const Eina_List *l;
   E_Desk *desk;

   desk = e_desk_current_get(zone);
   EINA_LIST_FOREACH(e_config->desktop_backgrounds, l, cdbg)
     {
        if ((cdbg->zone > -1) && (cdbg->zone != (int)zone->num)) continue;
        if ((cdbg->desk_x > -1) && (cdbg->desk_x != desk->x)) continue;
        if ((cdbg->desk_y > -1) && (cdbg->desk_y != desk->y)) continue;
        if (cdbg->file) return cdbg->file;
     }

   if (e_config->desktop_default_background)
     return e_config->desktop_default_background;

   return e_theme_edje_file_get("base/theme/desklock", "e/desklock/background");
}

E_API void
e_desklock_interface_append(E_Desklock_Interface *iface)
{
   EINA_SAFETY_ON_NULL_RETURN(iface);
   EINA_SAFETY_ON_NULL_RETURN(iface->show);
   EINA_SAFETY_ON_NULL_RETURN(iface->name);
   EINA_SAFETY_ON_TRUE_RETURN(iface->active); // fucking casuals
   /* make sure our module is first so it gets tried last */
   if (!e_util_strcmp(iface->name, "lokker"))
     desklock_ifaces = eina_list_prepend(desklock_ifaces, (void*)iface);
   else
     desklock_ifaces = eina_list_append(desklock_ifaces, (void*)iface);
   if (_e_desklock_state && (!current_iface))
     {
        if (iface->show())
          {
             iface->active = EINA_TRUE;
             current_iface = iface;
          }
     }
}

EINTERN E_Desklock_Interface *
e_desklock_interface_current_get(void)
{
   return current_iface;
}

E_API void
e_desklock_interface_remove(E_Desklock_Interface *iface)
{
   E_Desklock_Interface *diface;
   Eina_List *l;

   EINA_SAFETY_ON_NULL_RETURN(iface);
   if (!desklock_ifaces) return;
   desklock_ifaces = eina_list_remove(desklock_ifaces, (void*)iface);
   if (!iface->active) return;
   /* if it was active, hide it */
   if (iface->hide) iface->hide();
   iface->active = EINA_FALSE;
   current_iface = NULL;
   /* then try to find a replacement locker */
   EINA_LIST_REVERSE_FOREACH(desklock_ifaces, l, diface)
     {
        if (!diface->show()) continue;
        diface->active = EINA_TRUE;
        current_iface = diface;
        break;
     }
   /* XXX: if none of the remaining lockers can lock, we're left with a black screen.
    * I again blame the user for magically unloading the current locker module DURING
    * desklock, but I refuse to unlock their system.
   if (!current_iface) e_desklock_hide();
    */
}

E_API void
e_desklock_show_hook_add(E_Desklock_Show_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(cb);
   show_hooks = eina_list_append(show_hooks, cb);
}

E_API void
e_desklock_show_hook_del(E_Desklock_Show_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(cb);
   show_hooks = eina_list_remove(show_hooks, cb);
}

E_API void
e_desklock_hide_hook_add(E_Desklock_Hide_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(cb);
   hide_hooks = eina_list_append(hide_hooks, cb);
}

E_API void
e_desklock_hide_hook_del(E_Desklock_Hide_Cb cb)
{
   EINA_SAFETY_ON_NULL_RETURN(cb);
   hide_hooks = eina_list_remove(hide_hooks, cb);
}

E_API int
e_desklock_show_autolocked(void)
{
   return e_desklock_show(EINA_FALSE);
}

E_API Eina_Bool
e_desklock_demo(void)
{
   E_Desklock_Interface *iface;
   Eina_List *l;

   EINA_LIST_REVERSE_FOREACH(desklock_ifaces, l, iface)
     {
        if (iface->show())
          {
             demo = iface->active = EINA_TRUE;
             current_iface = iface;
             e_comp_shape_queue();
             return EINA_TRUE;
          }
     }
   return EINA_FALSE;
}

static int
_desklock_show_internal(Eina_Bool suspend)
{
   const Eina_List *l;
   E_Event_Desklock *ev;
   E_Desklock_Show_Cb show_cb;
   E_Desklock_Hide_Cb hide_cb;
   E_Zone *zone;

#if !defined(HAVE_PAM) && !defined(__FreeBSD__)  && !defined(__OpenBSD__)
   if (!e_desklock_is_personal())
     {
        e_util_dialog_show(_("Error - no PAM support"),
                           _("No PAM support was built into Enlightenment, so<ps/>"
                             "desk locking is disabled."));
        return EINA_FALSE;
     }
#endif
   if (_e_desklock_state) return EINA_TRUE;

   if (e_desklock_is_personal())
     {
        if (!e_config->desklock_passwd)
          {
             zone = e_zone_current_get();
             if (zone)
               e_configure_registry_call("screen/screen_lock", NULL, NULL);
             return 0;
          }
     }

   e_menu_hide_all();
   EINA_LIST_FOREACH(show_hooks, l, show_cb)
     {
        if (!show_cb()) goto fail;
     }

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        Evas_Object *o;

        if (zone->num >= EINA_C_ARRAY_LENGTH(block_rects))
          {
             CRI("> %lu screens connected????",
                 (unsigned long)EINA_C_ARRAY_LENGTH(block_rects));
             break;
          }
        o = evas_object_rectangle_add(e_comp->evas);
        block_rects[zone->num] = o;
        evas_object_color_set(o, 0, 0, 0, 0);
        evas_object_geometry_set(o, zone->x, zone->y, zone->w, zone->h);
        evas_object_layer_set(o, E_LAYER_DESKLOCK);
        if (!block_zone[zone->num])
          evas_object_show(o);
     }
   if (e_config->desklock_language)
     e_intl_language_set(e_config->desklock_language);

   if (e_config->xkb.lock_layout)
     e_xkb_layout_set(e_config->xkb.lock_layout);

   if (demo)
     {
        if (current_iface->hide)
          current_iface->hide();
        current_iface->active = demo = EINA_FALSE;
        current_iface = NULL;
     }

   {
      E_Desklock_Interface *iface;
      Eina_Bool success = EINA_TRUE;

      EINA_LIST_REVERSE_FOREACH(desklock_ifaces, l, iface)
        {
           success = iface->show();
           if (success)
             {
                iface->active = EINA_TRUE;
                current_iface = iface;
                break;
             }
        }
      /* FIXME: if someone doesn't have a locking module loaded and has
       * lock-on-startup, this will result in a permanent black screen.
       * I blame the user in this case since lokker is a default module
       * which is really hard to disable (you have to work at it or be a gentoo user).
       */
      if (!success) goto lang_fail;
   }

   ev = E_NEW(E_Event_Desklock, 1);
   ev->on = 1;
   ev->suspend = suspend;
   ecore_event_add(E_EVENT_DESKLOCK, ev, NULL, NULL);

   if (getenv("E_START_MANAGER")) kill(getppid(), SIGUSR2);
   _e_desklock_state = EINA_TRUE;
   e_bindings_disabled_set(1);
   e_screensaver_update();
   e_dpms_force_update();
   return 1;
lang_fail:
   if (e_config->desklock_language)
     e_intl_language_set(e_config->language);

   if (e_config_xkb_layout_eq(e_config->xkb.current_layout, e_config->xkb.lock_layout))
     {
        if (e_config->xkb.sel_layout)
          e_xkb_layout_set(e_config->xkb.sel_layout);
     }
fail:
   EINA_LIST_FOREACH(hide_hooks, l, hide_cb)
     hide_cb();
   return 0;
}

E_API int
e_desklock_show(Eina_Bool suspend)
{
   _e_desklock_want = EINA_TRUE;
   if ((_e_desklock_block > 0) && (!desklock_manual)) return EINA_FALSE;
   return _desklock_show_internal(suspend);
}

static void
_desklock_hide_internal(void)
{
   Eina_List *l;
   E_Event_Desklock *ev;
   E_Desklock_Hide_Cb hide_cb;

   if (demo && current_iface)
     {
        if (current_iface->hide)
          current_iface->hide();
        demo = current_iface->active = EINA_FALSE;
        current_iface = NULL;
        return;
     }
   demo = EINA_FALSE;

   if (!_e_desklock_state) return;

   e_comp_override_del();
   e_comp_shape_queue();
   {
      unsigned int n;

      for (n = 0; n < EINA_C_ARRAY_LENGTH(block_rects); n++)
        {
           E_FREE_FUNC(block_rects[n], evas_object_del);
           block_zone[n] = EINA_FALSE;
        }
   }
   //e_comp_block_window_del();
   if (e_config->desklock_language)
     e_intl_language_set(e_config->language);

   if (e_config_xkb_layout_eq(e_config->xkb.current_layout, e_config->xkb.lock_layout))
     {
        if (e_config->xkb.sel_layout)
          e_xkb_layout_set(e_config->xkb.sel_layout);
     }

   _e_desklock_state = EINA_FALSE;
   e_bindings_disabled_set(0);
   ev = E_NEW(E_Event_Desklock, 1);
   ev->on = 0;
   ev->suspend = 1;
   ecore_event_add(E_EVENT_DESKLOCK, ev, NULL, NULL);

   e_screensaver_update();
   e_dpms_force_update();

   EINA_LIST_FOREACH(hide_hooks, l, hide_cb)
     hide_cb();

   if (current_iface)
     {
        if (current_iface->hide)
          current_iface->hide();
        current_iface->active = EINA_FALSE;
        current_iface = NULL;
     }

   if (getenv("E_START_MANAGER")) kill(getppid(), SIGHUP);

   e_pointer_reset(e_comp->pointer);
}

E_API int
e_desklock_show_manual(Eina_Bool suspend)
{
   desklock_manual = EINA_TRUE;
   return e_desklock_show(suspend);
}

E_API Eina_Bool
e_desklock_manual_get(void)
{
   return desklock_manual;
}

E_API void
e_desklock_hide(void)
{
   desklock_manual = EINA_FALSE;
   _e_desklock_want = EINA_FALSE;
   _desklock_hide_internal();
}

E_API Eina_Bool
e_desklock_state_get(void)
{
   return _e_desklock_state;
}

E_API void
e_desklock_block(void)
{
   _e_desklock_block++;
   if (_e_desklock_block == 1)
     {
        if (!desklock_manual)
          {
             if (_e_desklock_state) _desklock_hide_internal();
          }
     }
}

E_API void
e_desklock_unblock(void)
{
   _e_desklock_block--;
   if (_e_desklock_block == 0)
     {
        if (_e_desklock_want) e_desklock_show(EINA_FALSE);
     }
   else if (_e_desklock_block < 0)
     {
        ERR("desklock block going below zero");
     }
}

static Eina_Bool
_e_desklock_run(E_Desklock_Run *task)
{
   Efreet_Desktop *desktop;

   desktop = eina_list_nth(task->desk_run->desktops, task->position++);
   if (!desktop)
     {
        e_object_del(E_OBJECT(task->desk_run));
        free(task);
        return EINA_FALSE;
     }

   e_exec(NULL, desktop, NULL, NULL, NULL);
   return EINA_TRUE;
}

static void
_e_desklock_job(void *data EINA_UNUSED)
{
   E_Desklock_Run *task;

   job = NULL;
   if (!tasks) return;

   task = eina_list_data_get(tasks);
   if (!_e_desklock_run(task))
     tasks = eina_list_remove_list(tasks, tasks);

   if (tasks) job = ecore_job_add(_e_desklock_job, NULL);
}

static Eina_Bool
_e_desklock_cb_run(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Desklock_Run *task;
   E_Event_Desklock *ev = event;
   E_Order *desk_run;
   char buf[PATH_MAX];

   if (!ev->suspend) return ECORE_CALLBACK_PASS_ON;

   if (ev->on)
     {
        e_user_dir_concat_static(buf, "applications/desk-lock/.order");
        if (!ecore_file_exists(buf))
          e_prefix_data_concat_static(buf, "applications/desk-lock/.order");
     }
   else
     {
        e_user_dir_concat_static(buf, "applications/desk-unlock/.order");
        if (!ecore_file_exists(buf))
          e_prefix_data_concat_static(buf, "applications/desk-unlock/.order");
     }

   desk_run = e_order_new(buf);
   if (!desk_run) return ECORE_CALLBACK_PASS_ON;

   task = calloc(1, sizeof (E_Desklock_Run));
   if (!task)
     {
        e_object_del(E_OBJECT(desk_run));
        return ECORE_CALLBACK_PASS_ON;
     }

   task->desk_run = desk_run;
   tasks = eina_list_append(tasks, task);

   if (!job) ecore_job_add(_e_desklock_job, NULL);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_desklock_cb_randr(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (!_e_desklock_state) return ECORE_CALLBACK_PASS_ON;
   e_desklock_hide();
   e_desklock_show(EINA_FALSE);
   return ECORE_CALLBACK_PASS_ON;
}

E_API void
e_desklock_zone_block_set(const E_Zone *zone, Eina_Bool block)
{
   EINA_SAFETY_ON_NULL_RETURN(zone);
   if (zone->num >= EINA_C_ARRAY_LENGTH(block_rects))
     {
        CRI("> %lu screens connected????",
            (unsigned long)EINA_C_ARRAY_LENGTH(block_rects));
        return;
     }
   block_zone[zone->num] = !!block;
   if (!block_rects[zone->num]) return;
   if (block)
     evas_object_show(block_rects[zone->num]);
   else
     evas_object_hide(block_rects[zone->num]);
}
#include "e.h"

#define SMART_NAME "e_deskmirror"

#define INTERNAL_ENTRY E_Smart_Data *sd; sd = evas_object_smart_data_get(obj); if (!sd) return;

#define API_ENTRY(X)   E_Smart_Data *sd; \
                       sd = evas_object_smart_data_get(X); \
                       if ((!X) || (!sd) || (e_util_strcmp(evas_object_type_get(X), SMART_NAME))) return

typedef struct E_Smart_Data
{
   Evas *e;
   Evas_Object *obj;
   Evas_Object *clip;
   Evas_Object *bgpreview;
   Evas_Object *layout;
   Evas_Object *events;
   Eina_Inlist *mirrors;
   Eina_Hash *mirror_hash;

   Eina_List *handlers;

   Evas_Coord x, y;
   int w, h;

   E_Desk *desk;
   E_Object_Delfn *desk_delfn;

   Eina_Bool pager E_BITFIELD;
   Eina_Bool taskbar E_BITFIELD;

   Eina_Bool resize E_BITFIELD;
   Eina_Bool force E_BITFIELD;
} E_Smart_Data;

typedef struct Mirror
{
   EINA_INLIST;
   E_Smart_Data *sd;
   E_Client *ec;
   Evas_Object *comp_object;
   Evas_Object *mirror;
   int x, y, w, h;
   int ref;
   Eina_Bool added E_BITFIELD;
} Mirror;

typedef struct Mirror_Border
{
   Mirror *m;
   Evas_Object *mirror;
   Evas_Object *frame;
   Evas_Object *obj;
} Mirror_Border;

static Evas_Smart *_e_deskmirror_smart = NULL;
static Evas_Smart *_mirror_client_smart = NULL;

static void _e_deskmirror_mirror_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _e_deskmirror_mirror_color_set_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);

static void _e_deskmirror_mirror_setup(Mirror *m);
static void _comp_object_dirty(void *data, Evas_Object *obj, void *event_info EINA_UNUSED);
static void _comp_object_hide(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _comp_object_show(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _comp_object_stack(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED);
static void _comp_object_configure(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);

static Eina_Bool
_e_deskmirror_visible_get(E_Smart_Data *sd, Mirror *m)
{
   Eina_Bool visible = evas_object_visible_get(m->comp_object);
   if (m->ec && (!e_object_is_del(E_OBJECT(m->ec))))
     {
        visible = m->ec->visible;
        /* all iconic visibility changes occur with iconic flag set:
         * visibility here is determined by frame visibility
         */
        if (m->sd->handlers && m->ec->iconic)
          visible = evas_object_visible_get(m->ec->frame);
        if (m->ec->iconic) visible = EINA_FALSE;
        if (visible)
          {
             visible = (sd->desk == m->ec->desk) || (m->ec->sticky && (!m->ec->hidden));
             if (sd->pager)
               visible = !m->ec->netwm.state.skip_pager;
             if (visible && sd->taskbar)
               visible = !m->ec->netwm.state.skip_taskbar;
          }
     }
   if ((m->w < 1) || (m->h < 1)) visible = EINA_FALSE;
   return visible;
}

static void
_mirror_visible_apply(Mirror *m)
{
   if (_e_deskmirror_visible_get(m->sd, m))
     {
        evas_object_show(m->mirror);
     }
   else
     {
        evas_object_hide(m->mirror);
     }
}

static void
_mirror_scale_set(Mirror *m, float sc)
{
   Edje_Message_Float msg;
   Mirror_Border *mb;

   if (!m->mirror) return;
   mb = evas_object_smart_data_get(m->mirror);
   if (!mb) return;
   msg.val = sc;
   edje_object_message_send(mb->frame, EDJE_MESSAGE_FLOAT, 0, &msg);
}

static void
_mirror_ref(Mirror *m)
{
   m->ref++;
}

static void
_mirror_unref(Mirror *m)
{
   m->ref--;
   if (m->ref > 0) return;
   free(m);
}

static void
_e_deskmirror_smart_reconfigure(E_Smart_Data *sd)
{
   e_layout_freeze(sd->layout);
   evas_object_move(sd->clip, sd->x, sd->y);
   evas_object_move(sd->bgpreview, sd->x, sd->y);
   evas_object_move(sd->layout, sd->x, sd->y);
   if (sd->events)
     evas_object_move(sd->events, sd->x, sd->y);

   if (sd->resize)
     {
        Mirror *m;

        evas_object_resize(sd->clip, sd->w, sd->h);
        evas_object_resize(sd->bgpreview, sd->w, sd->h);
        evas_object_resize(sd->layout, sd->w, sd->h);
        if (sd->events)
          evas_object_resize(sd->events, sd->w, sd->h);
        EINA_INLIST_FOREACH(sd->mirrors, m)
          _mirror_scale_set(m, (float)sd->h / (float)sd->desk->zone->h);
     }
   e_layout_thaw(sd->layout);
   sd->resize = 0;
}

///////////////////////////////////////////////

static void
_e_deskmirror_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = E_NEW(E_Smart_Data, 1);
   if (!sd) return;
   sd->obj = obj;
   sd->e = evas_object_evas_get(obj);
   sd->x = sd->y = sd->w = sd->h = 0;
   sd->clip = evas_object_rectangle_add(sd->e);
   evas_object_smart_member_add(sd->clip, sd->obj);
   evas_object_smart_data_set(obj, sd);
}

static void
_e_deskmirror_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   e_object_unref(E_OBJECT(sd->desk));
   if (sd->desk_delfn)
     {
        e_object_delfn_del(E_OBJECT(sd->desk), sd->desk_delfn);
        sd->desk_delfn = NULL;
        sd->desk = NULL;
     }
   E_FREE_LIST(sd->handlers, ecore_event_handler_del);
   eina_hash_free(sd->mirror_hash);
   evas_object_del(sd->clip);
   evas_object_del(sd->bgpreview);
   evas_object_del(sd->layout);
   evas_object_del(sd->events);
   free(sd);
}

static void
_e_deskmirror_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_deskmirror_smart_reconfigure(sd);
}

static void
_e_deskmirror_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   sd->resize = 1;
   _e_deskmirror_smart_reconfigure(sd);
}

static void
_e_deskmirror_smart_show(Evas_Object *obj)
{
   Mirror *m;

   INTERNAL_ENTRY;
   EINA_INLIST_FOREACH(sd->mirrors, m)
     {
        Mirror_Border *mb;

        if ((!m->ec) || (!m->mirror)) continue;
        mb = evas_object_smart_data_get(m->mirror);
        if (!mb) continue;
        edje_object_thaw(mb->frame);
        edje_object_play_set(mb->frame, 1);
        evas_object_show(mb->mirror);
     }
   evas_object_show(sd->clip);
}

static void
_e_deskmirror_smart_hide(Evas_Object *obj)
{
   Mirror *m;

   INTERNAL_ENTRY;
   EINA_INLIST_FOREACH(sd->mirrors, m)
     {
        Mirror_Border *mb;

        if ((!m->ec) || (!m->mirror)) continue;
        mb = evas_object_smart_data_get(m->mirror);
        if (!mb) continue;
        edje_object_freeze(mb->frame);
        edje_object_play_set(mb->frame, 0);
        evas_object_hide(mb->mirror);
     }
   evas_object_hide(sd->clip);
}

static void
_e_deskmirror_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->clip, r, g, b, a);
}

static void
_e_deskmirror_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->clip, clip);
}

static void
_e_deskmirror_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->clip);
}

////////////////////////////////////////////////////////

static void
_mirror_client_theme_setup(Mirror_Border *mb, Evas_Object *o)
{
   char buf[4096];

   if ((!mb) || (!mb->m) || (!mb->m->ec)) return;
   if (e_comp_object_frame_exists(mb->m->ec->frame))
     snprintf(buf, sizeof(buf), "e/deskmirror/frame/%s", mb->m->ec->border.name);
   else
     snprintf(buf, sizeof(buf), "e/deskmirror/frame/borderless");
   e_theme_edje_object_set(o, "base/theme/borders", buf);
   if (e_client_util_shadow_state_get(mb->m->ec))
     edje_object_signal_emit(o, "e,state,shadow,on", "e");
   else
     edje_object_signal_emit(o, "e,state,shadow,off", "e");
   if (mb->m->ec->focused)
     edje_object_signal_emit(o, "e,state,focused", "e");
   if (mb->m->ec->shaded)
     edje_object_signal_emit(o, "e,state,shaded", "e");
   if (mb->m->ec->maximized)
     edje_object_signal_emit(o, "e,action,maximize", "e");
   if (mb->m->ec->sticky)
     edje_object_signal_emit(o, "e,state,sticky", "e");
   if (mb->m->ec->iconic)
     edje_object_signal_emit(o, "e,action,iconify", "e");
}

static void
_e_deskmirror_mirror_frame_recalc_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Mirror *m = data;
   Mirror_Border *mb;

   if (!m->mirror) return;
   mb = evas_object_smart_data_get(m->mirror);
   if (mb->m->ec && (!e_object_is_del(E_OBJECT(mb->m->ec))))
     {
        _mirror_client_theme_setup(mb, mb->frame);
        _mirror_scale_set(mb->m, (float)mb->m->sd->h / (float)mb->m->sd->desk->zone->h);
     }
   else
     {
        m = mb->m;
        if (m->comp_object)
          {
             evas_object_smart_callback_del_full(m->comp_object, "dirty", _comp_object_dirty, m);
             evas_object_smart_callback_del_full(m->comp_object, "frame_recalc_done", _e_deskmirror_mirror_frame_recalc_cb, m);
             evas_object_smart_callback_del_full(m->comp_object, "color_set", _e_deskmirror_mirror_color_set_cb, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_DEL, _e_deskmirror_mirror_del_cb, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_SHOW, (Evas_Object_Event_Cb)_comp_object_show, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_HIDE, (Evas_Object_Event_Cb)_comp_object_hide, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_RESTACK, (Evas_Object_Event_Cb)_comp_object_stack, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_comp_object_configure, m);
             evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_comp_object_configure, m);
             m->comp_object = NULL;
          }
     }
}

static void
_e_deskmirror_mirror_color_set_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Mirror *m = data;
   int r, g, b, a;

   if (!m->mirror) return;
   evas_object_color_get(obj, &r, &g, &b, &a);
   evas_object_color_set(m->mirror, r, g, b, a);
}

static void
_mirror_client_smart_add(Evas_Object *obj)
{
   Mirror_Border *mb;

   mb = E_NEW(Mirror_Border, 1);
   mb->obj = obj;
   evas_object_smart_data_set(obj, mb);
}

static void
_mirror_client_signal_cb(void *data, Evas_Object *obj EINA_UNUSED, const char *emission, const char *src)
{
   if (!strncmp(emission, "e,state,shadow", sizeof("e,state,shadow") - 1)) return;
   edje_object_signal_emit(data, emission, src);
   edje_object_message_signal_process(data);
   edje_object_calc_force(data);
}

static void
_mirror_client_shadow_change(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Client *ec = event_info;

   if (e_client_util_shadow_state_get(ec))
     edje_object_signal_emit(data, "e,state,shadow,on", "e");
   else
     edje_object_signal_emit(data, "e,state,shadow,off", "e");
}

static void
_mirror_client_smart_del(Evas_Object *obj)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   if (mb->m->comp_object && mb->m->ec)
     {
        e_comp_object_signal_callback_del_full(mb->m->ec->frame, "*", "*", _mirror_client_signal_cb, mb->frame);
        evas_object_smart_callback_del_full(mb->m->ec->frame, "shadow_change", _mirror_client_shadow_change, mb->frame);
     }
   evas_object_del(mb->frame);
   mb->frame = NULL;
   evas_object_del(mb->mirror);
   mb->mirror = NULL;
   _mirror_unref(mb->m);
   free(mb);
}

static void
_mirror_client_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);

   evas_object_move(mb->frame, x, y);
}

static void
_mirror_client_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_resize(mb->frame, w, h);
}

static void
_mirror_client_smart_show(Evas_Object *obj)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_show(mb->frame);
}

static void
_mirror_client_smart_hide(Evas_Object *obj)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_hide(mb->frame);
}

static void
_mirror_client_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_color_set(mb->frame, r, g, b, a);
}

static void
_mirror_client_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_clip_set(mb->frame, clip);
}

static void
_mirror_client_smart_clip_unset(Evas_Object *obj)
{
   Mirror_Border *mb = evas_object_smart_data_get(obj);
   evas_object_clip_unset(mb->frame);
}

static void
_mirror_client_smart_init(void)
{
   static const Evas_Smart_Class sc =
     {
        "mirror_border", EVAS_SMART_CLASS_VERSION,
        _mirror_client_smart_add, _mirror_client_smart_del, _mirror_client_smart_move, _mirror_client_smart_resize,
        _mirror_client_smart_show, _mirror_client_smart_hide, _mirror_client_smart_color_set, _mirror_client_smart_clip_set,
        _mirror_client_smart_clip_unset, NULL, NULL, NULL, NULL, NULL, NULL, NULL
     };
   if (_mirror_client_smart) return;
   _mirror_client_smart = evas_smart_class_new(&sc);
}

static void
_e_deskmirror_smart_init(void)
{
   static const Evas_Smart_Class sc =
     {
        SMART_NAME, EVAS_SMART_CLASS_VERSION,
        _e_deskmirror_smart_add, _e_deskmirror_smart_del, _e_deskmirror_smart_move, _e_deskmirror_smart_resize,
        _e_deskmirror_smart_show, _e_deskmirror_smart_hide, _e_deskmirror_smart_color_set, _e_deskmirror_smart_clip_set,
        _e_deskmirror_smart_clip_unset, NULL, NULL, NULL, NULL, NULL, NULL, NULL
     };
   if (_e_deskmirror_smart) return;
   _e_deskmirror_smart = evas_smart_class_new(&sc);
}

static void
_e_deskmirror_delfn(E_Smart_Data *sd, void *desk EINA_UNUSED)
{
   sd->desk_delfn = NULL;
   sd->desk = NULL;
   evas_object_del(sd->obj);
}

static void
_e_deskmirror_mirror_del(Mirror *m)
{
   void *val = m->comp_object;

   m->comp_object = NULL;
   eina_hash_del_by_key(m->sd->mirror_hash, &val);
}

static void
_e_deskmirror_mirror_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_deskmirror_mirror_del(data);
}

static void
_e_deskmirror_mirror_geometry_get(Mirror *m)
{
   E_Zone *zone;

   evas_object_geometry_get(m->comp_object, &m->x, &m->y, &m->w, &m->h);
   zone = e_comp_object_util_zone_get(m->comp_object);
   if (zone)
     m->x -= zone->x, m->y -= zone->y;
   /* double check here if we get zeroes */
   if ((!m->w) || (!m->h))
     {
        ERR("ACK!");
     }
}

static void
_e_deskmirror_mirror_reconfigure(Mirror *m)
{
   _e_deskmirror_mirror_geometry_get(m);
   if (m->mirror)
     {
        e_layout_child_move(m->mirror, m->x, m->y);
        e_layout_child_resize(m->mirror, m->w, m->h);
        /* assume that anything happening here is the result of a drag */
        if (((!m->sd->force) && (!e_drag_current_get())) || (m->sd->force))
          _mirror_visible_apply(m);
     }
}

static void
_e_deskmirror_mirror_del_hash(Mirror *m)
{
   m->sd->mirrors = eina_inlist_remove(m->sd->mirrors, EINA_INLIST_GET(m));
   if (m->mirror)
     {
        evas_object_del(m->mirror);
        m->mirror = NULL;
     }
   if (m->comp_object)
     {
        evas_object_smart_callback_del_full(m->comp_object, "dirty", _comp_object_dirty, m);
        evas_object_smart_callback_del_full(m->comp_object, "frame_recalc_done", _e_deskmirror_mirror_frame_recalc_cb, m);
        evas_object_smart_callback_del_full(m->comp_object, "color_set", _e_deskmirror_mirror_color_set_cb, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_DEL, _e_deskmirror_mirror_del_cb, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_SHOW, (Evas_Object_Event_Cb)_comp_object_show, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_HIDE, (Evas_Object_Event_Cb)_comp_object_hide, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_RESTACK, (Evas_Object_Event_Cb)_comp_object_stack, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_comp_object_configure, m);
        evas_object_event_callback_del_full(m->comp_object, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_comp_object_configure, m);
        m->comp_object = NULL;
     }
   _mirror_unref(m);
}

static Evas_Object *
_mirror_client_new(Mirror *m)
{
   Evas_Object *o;
   Mirror_Border *mb;

   _mirror_client_smart_init();
   o = evas_object_smart_add(m->sd->e, _mirror_client_smart);
   mb = evas_object_smart_data_get(o);
   mb->m = m;
   _mirror_ref(m);
   mb->frame = edje_object_add(m->sd->e);
   evas_object_name_set(mb->frame, "mirror_border");
   _mirror_client_theme_setup(mb, mb->frame);
   if (m->comp_object)
     {
        e_comp_object_signal_callback_add(m->comp_object, "*", "*", _mirror_client_signal_cb, mb->frame);
        evas_object_smart_callback_add(m->comp_object, "shadow_change", _mirror_client_shadow_change, mb->frame);
     }

   mb->mirror = m->mirror;
   evas_object_smart_member_add(mb->frame, o);
   evas_object_name_set(mb->mirror, "mirror");
   edje_object_part_swallow(mb->frame, "e.swallow.client", m->mirror);
   edje_object_part_text_set(mb->frame, "e.text.title", e_client_util_name_get(m->ec));
   return o;
}

static void
_e_deskmirror_mirror_setup(Mirror *m)
{
   if (!m->mirror) return;
   if (m->ec)
     {
        m->mirror = _mirror_client_new(m);
        _mirror_scale_set(m, (double)m->sd->h / (double)m->sd->desk->zone->h);
     }
   else
     {
        evas_object_pass_events_set(m->mirror, !m->ec);
     }
   if (m->ec) evas_object_data_set(m->mirror, "E_Client", m->ec);
   evas_object_precise_is_inside_set(m->mirror, m->ec && (m->ec->shaped));
   e_layout_pack(m->sd->layout, m->mirror);
   _e_deskmirror_mirror_reconfigure(m);
   if (m->sd->handlers) // no handlers = we're setting up = there's no possible listeners
     {
        _comp_object_stack(m, NULL, m->comp_object, NULL);
        if (!m->added)
          evas_object_smart_callback_call(m->sd->obj, "mirror_add", m->mirror);
     }
   else
     e_layout_child_raise(m->mirror);
   m->added = 1;
}

static Eina_Bool
_comp_object_check(Mirror *m)
{
   int w, h;

   evas_object_geometry_get(m->comp_object, NULL, NULL, &w, &h);
   if ((w < 2) || (h < 2)) return EINA_FALSE;
   if (!m->mirror)
     {
        m->mirror = e_comp_object_util_mirror_add(m->comp_object);
        if (!m->mirror) return EINA_FALSE;
     }
   evas_object_smart_callback_del(m->comp_object, "dirty", _comp_object_dirty);
   if (m->added) return EINA_TRUE;
   evas_object_name_set(m->mirror, "m->mirror");
   _e_deskmirror_mirror_setup(m);
   return EINA_TRUE;
}

static void
_comp_object_hide(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _mirror_visible_apply(m);
}

static void
_comp_object_show(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (!m->mirror)
     {
        if (!_comp_object_check(m)) return;
     }
   _mirror_visible_apply(m);
}

static void
_comp_object_stack(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Evas_Object *comp_object;

   if (!m->mirror)
     {
        if (!_comp_object_check(m)) return;
     }
   comp_object = evas_object_below_get(obj);
   while (comp_object)
     {
        Mirror *m2;

        m2 = eina_hash_find(m->sd->mirror_hash, &comp_object);
        if (m2 && m2->mirror)
          {
             e_layout_child_raise_above(m->mirror, m2->mirror);
             return;
          }
        comp_object = evas_object_below_get(comp_object);
     }
   e_layout_child_lower(m->mirror);
}

static void
_comp_object_configure(Mirror *m, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (!m->mirror)
     {
        if (!_comp_object_check(m)) return;
     }
   _e_deskmirror_mirror_reconfigure(m);
}

static void
_comp_object_dirty(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _comp_object_check(data);
}

static Mirror *
_e_deskmirror_mirror_add(E_Smart_Data *sd, Evas_Object *obj)
{
   Mirror *m;
   Evas_Object *o = NULL;
   E_Client *ec;
   int w, h;

   if (sd->desk->zone != e_comp_object_util_zone_get(obj)) return NULL;

   ec = e_comp_object_client_get(obj);
   if (ec)
     {
        if ((ec->desk != sd->desk) && (!ec->sticky))
          return NULL;
        if (ec->input_only || ec->ignored)
          return NULL;
     }
   else
     {
        E_Shelf *es;

        if (sd->pager || sd->taskbar) return NULL;
        es = evas_object_data_get(obj, "E_Shelf");
        if (es)
          {
             if (!e_shelf_desk_visible(es, sd->desk)) return NULL;
          }
        else
          {
             if (sd->desk != e_desk_current_get(sd->desk->zone)) return NULL;
          }
     }
   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   if ((w > 1) && (h > 1))
     {
        o = e_comp_object_util_mirror_add(obj);
        evas_object_name_set(o, "m->mirror");
     }
   m = calloc(1, sizeof(Mirror));
   m->comp_object = obj;
   m->ec = ec;
   m->sd = sd;
   m->mirror = o;
   m->ref = 1;
   evas_object_event_callback_add(m->comp_object, EVAS_CALLBACK_DEL, _e_deskmirror_mirror_del_cb, m);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, (Evas_Object_Event_Cb)_comp_object_show, m);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, (Evas_Object_Event_Cb)_comp_object_hide, m);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESTACK, (Evas_Object_Event_Cb)_comp_object_stack, m);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_comp_object_configure, m);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_comp_object_configure, m);
   evas_object_smart_callback_add(obj, "frame_recalc_done", _e_deskmirror_mirror_frame_recalc_cb, m);
   evas_object_smart_callback_add(obj, "color_set", _e_deskmirror_mirror_color_set_cb, m);
   if (ec && (!ec->redirected) && (!ec->new_client) && e_pixmap_usable_get(ec->pixmap))
     evas_object_smart_callback_add(obj, "dirty", _comp_object_dirty, m);
   sd->mirrors = eina_inlist_append(sd->mirrors, EINA_INLIST_GET(m));
   eina_hash_add(sd->mirror_hash, &obj, m);
   _e_deskmirror_mirror_setup(m);
   return m;
}

static Eina_Bool
_comp_object_add(E_Smart_Data *sd, int type EINA_UNUSED, E_Event_Comp_Object *ev)
{
   if (eina_hash_find(sd->mirror_hash, &ev->comp_object)) return ECORE_CALLBACK_RENEW;
   _e_deskmirror_mirror_add(sd, ev->comp_object);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_client_del(E_Smart_Data *sd, int type EINA_UNUSED, E_Event_Client *ev)
{
   Mirror *m = eina_hash_find(sd->mirror_hash, &ev->ec->frame);

   if (m) m->ec = NULL;

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_client_add(E_Smart_Data *sd, int type EINA_UNUSED, E_Event_Client *ev)
{
   if (!eina_hash_find(sd->mirror_hash, &ev->ec->frame))
     _e_deskmirror_mirror_add(sd, ev->ec->frame);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_client_desk_set(E_Smart_Data *sd, int type EINA_UNUSED, E_Event_Client_Desk_Set *ev)
{
   Mirror *m;

   m = eina_hash_find(sd->mirror_hash, &ev->ec->frame);
   if (m)
     {
        /* ev->desk is previous desk */
        _mirror_visible_apply(m);
        if (!e_client_util_desk_visible(ev->ec, sd->desk))
          eina_hash_del_by_key(sd->mirror_hash, &ev->ec->frame);
     }
   if ((!m) && (sd->desk == ev->ec->desk))
     _e_deskmirror_mirror_add(sd, ev->ec->frame);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_client_property(E_Smart_Data *sd, int type EINA_UNUSED, E_Event_Client_Property *ev)
{
   Mirror *m;

   if ((!(ev->property & E_CLIENT_PROPERTY_NETWM_STATE)) &&
       (!(ev->property & E_CLIENT_PROPERTY_STICKY)))
     return ECORE_CALLBACK_RENEW;

   m = eina_hash_find(sd->mirror_hash, &ev->ec->frame);
   if (m)
     {
        _mirror_visible_apply(m);
        if (ev->property & E_CLIENT_PROPERTY_STICKY)
          {
             if ((!ev->ec->sticky) && (ev->ec->desk != sd->desk))
               eina_hash_del_by_key(sd->mirror_hash, &ev->ec->frame);
          }
     }
   else if (ev->ec->sticky)
     _e_deskmirror_mirror_add(sd, ev->ec->frame);
   return ECORE_CALLBACK_RENEW;
}

/* externally accessible functions */
E_API Evas_Object *
e_deskmirror_add(E_Desk *desk, Eina_Bool pager, Eina_Bool taskbar)
{
   E_Smart_Data *sd;
   Evas_Object *o, *l;
   Evas *e;

   e = e_comp->evas;
   _e_deskmirror_smart_init();
   o = evas_object_smart_add(e, _e_deskmirror_smart);
   e_object_ref(E_OBJECT(desk));
   sd = evas_object_smart_data_get(o);
   sd->pager = !!pager;
   sd->taskbar = !!taskbar;
   sd->desk = desk;
   sd->mirror_hash = eina_hash_pointer_new((Eina_Free_Cb)_e_deskmirror_mirror_del_hash);
   sd->desk_delfn = e_object_delfn_add(E_OBJECT(desk), (Ecore_End_Cb)_e_deskmirror_delfn, sd);
   if (pager || taskbar)
     {
        sd->events = evas_object_rectangle_add(e);
        evas_object_color_set(sd->events, 0, 0, 0, 0);
        evas_object_clip_set(sd->events, sd->clip);
        evas_object_smart_member_add(sd->events, o);
        evas_object_show(sd->events);
     }
   sd->bgpreview = e_widget_bgpreview_desk_add(e, desk->zone, desk->x, desk->y);
   evas_object_pass_events_set(sd->bgpreview, 1);
   evas_object_clip_set(sd->bgpreview, sd->clip);
   evas_object_smart_member_add(sd->bgpreview, o);
   evas_object_show(sd->bgpreview);
   sd->layout = e_layout_add(e);
   evas_object_clip_set(sd->layout, sd->clip);
   e_layout_virtual_size_set(sd->layout, desk->zone->w, desk->zone->h);
   evas_object_smart_member_add(sd->layout, o);
   evas_object_show(sd->layout);

   e_comp_object_util_center_on(o, sd->desk->zone->bg_clip_object);

   e_layout_freeze(sd->layout);

   l = evas_object_bottom_get(e_comp->evas);
   do
     {
        if (evas_object_data_get(l, "comp_object"))
          _e_deskmirror_mirror_add(sd, l);
        l = evas_object_above_get(l);
     } while (l);

   e_layout_thaw(sd->layout);

   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_COMP_OBJECT_ADD, (Ecore_Event_Handler_Cb)_comp_object_add, sd);
   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_CLIENT_ADD, (Ecore_Event_Handler_Cb)_client_add, sd);
   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_CLIENT_REMOVE, (Ecore_Event_Handler_Cb)_client_del, sd);
   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_CLIENT_PROPERTY, (Ecore_Event_Handler_Cb)_client_property, sd);
   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_CLIENT_DESK_SET, (Ecore_Event_Handler_Cb)_client_desk_set, sd);
   return o;
}

E_API Evas_Object *
e_deskmirror_mirror_find(Evas_Object *deskmirror, Evas_Object *comp_object)
{
   Mirror *m;
   EINA_SAFETY_ON_NULL_RETURN_VAL(deskmirror, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(comp_object, NULL);
   API_ENTRY(deskmirror) NULL;

   m = eina_hash_find(sd->mirror_hash, &comp_object);
   return m ? m->mirror : NULL;
}

E_API Eina_List *
e_deskmirror_mirror_list(Evas_Object *deskmirror)
{
   Eina_List *l = NULL;
   Mirror *m;

   EINA_SAFETY_ON_NULL_RETURN_VAL(deskmirror, NULL);
   API_ENTRY(deskmirror) NULL;

   EINA_INLIST_FOREACH(sd->mirrors, m)
     {
        if (m->mirror)
          l = eina_list_append(l, m->mirror);
     }
   return l;
}

static void
_mirror_copy_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_comp_object_signal_callback_del_full(data, "*", "*", _mirror_client_signal_cb, obj);
   evas_object_smart_callback_del_full(data, "shadow_change", _mirror_client_shadow_change, obj);
}

E_API Evas_Object *
e_deskmirror_mirror_copy(Evas_Object *obj)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);

   if (!e_util_strcmp(evas_object_type_get(obj), "mirror_border"))
     {
        Evas_Object *o, *oo;
        Edje_Message_Float msg;
        Mirror_Border *mb;

        mb = evas_object_smart_data_get(obj);
        o = edje_object_add(evas_object_evas_get(obj));
        _mirror_client_theme_setup(mb, o);
        if (mb->m->comp_object)
          {
             e_comp_object_signal_callback_add(mb->m->comp_object, "*", "*", _mirror_client_signal_cb, o);
             evas_object_smart_callback_add(mb->m->comp_object, "shadow_change", _mirror_client_shadow_change, o);
             evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, _mirror_copy_del, mb->m->comp_object);
          }
        msg.val = mb->m->sd->h / mb->m->sd->desk->zone->h;
        edje_object_message_send(o, EDJE_MESSAGE_FLOAT, 0, &msg);

        oo = e_comp_object_util_mirror_add(mb->m->comp_object);
        edje_object_part_swallow(o, "e.swallow.client", oo);
        if (e_comp_object_frame_exists(mb->m->ec->frame))
          edje_object_part_text_set(o, "e.text.title", e_client_util_name_get(mb->m->ec));
        e_comp_object_util_del_list_append(o, oo);
        return o;
     }
   else if (!e_util_strcmp(evas_object_type_get(obj), "image"))
     return e_comp_object_util_mirror_add(obj);
   CRI("NOT A DESKMIRROR CLIENT");
   return NULL;
}

E_API void
e_deskmirror_coord_canvas_to_virtual(Evas_Object *obj, Evas_Coord cx, Evas_Coord cy, Evas_Coord *vx, Evas_Coord *vy)
{
   API_ENTRY(obj);

   e_layout_coord_canvas_to_virtual(sd->layout, cx, cy, vx, vy);
}

E_API void
e_deskmirror_coord_virtual_to_canvas(Evas_Object *obj, Evas_Coord vx, Evas_Coord vy, Evas_Coord *cx, Evas_Coord *cy)
{
   API_ENTRY(obj);

   e_layout_coord_virtual_to_canvas(sd->layout, vx, vy, cx, cy);
}

E_API E_Desk *
e_deskmirror_desk_get(Evas_Object *obj)
{
   API_ENTRY(obj) NULL;
   return sd->desk;
}

E_API void
e_deskmirror_util_wins_print(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Mirror *m;

   EINA_SAFETY_ON_NULL_RETURN(obj);
   sd = evas_object_smart_data_get(obj);
   EINA_INLIST_FOREACH(sd->mirrors, m)
     {
        if (m->ec)
          {
             if (m->ec->override)
               fprintf(stderr, "MIRROR OVERRIDE: %p - %p%s\n", m->comp_object, m->ec, m->ec->input_only ? " INPUT" : "");
             else
               fprintf(stderr, "MIRROR EC:  %p - %p '%s:%s'\n", m->comp_object, m->ec, m->ec->icccm.name, m->ec->icccm.class);
          }
        else
          fprintf(stderr, "MIRROR OBJ: %p\n", m->comp_object);
     }
}

E_API void
e_deskmirror_update_force(Evas_Object *obj)
{
   Mirror *m;

   API_ENTRY(obj);

   e_layout_freeze(sd->layout);
   sd->force = EINA_TRUE;
   _e_deskmirror_smart_hide(obj);
   EINA_INLIST_FOREACH(sd->mirrors, m)
     _e_deskmirror_mirror_reconfigure(m);
   _e_deskmirror_smart_reconfigure(sd);
   _e_deskmirror_smart_show(obj);
   sd->force = EINA_FALSE;
   e_layout_thaw(sd->layout);
}
#include "e.h"

/* TODO: Check cache update */

struct _E_Config_Dialog_Data
{
   Efreet_Desktop *desktop;
   int             type; /* desktop type */

   char           *name; /* app name (e.g. Firefox) */
   char           *generic_name; /* generic app name (e.g. Web Browser) */
   char           *comment; /* a longer description */
   char           *exec; /* command to execute */
   char           *try_exec; /* executable to test for an apps existence */
   char           *url; /* url to open */

   char           *startup_wm_class; /* window class */
   char           *categories; /* list of category names that app is in */
   char           *mimes; /* list of mimes this app can handle */
   char           *icon; /* absolute path to file or icon name */

   int             startup_notify;
   int             terminal;
   int             show_in_menus;

   E_Desktop_Edit *editor;

   char           *orig_path; /* informational only */
   Evas_Object    *orig_path_entry; /* to set when info changes */
   Evas_Object    *icon_entry; /* to set when icon changes */

   /* speed up check_changed tests */
   Eina_Bool       changed_categories;
   Eina_Bool       changed_mimes;
   Eina_Bool       edited_categories;
   Eina_Bool       edited_mimes;
};

/* local subsystem functions */

static int          _e_desktop_edit_view_create(E_Desktop_Edit *editor);
static void         _e_desktop_edit_free(E_Desktop_Edit *editor);
static void        *_e_desktop_edit_create_data(E_Config_Dialog *cfd);
static void         _e_desktop_edit_free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *data);
static int          _e_desktop_edit_basic_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *data);
static int          _e_desktop_edit_basic_check_changed(E_Config_Dialog *cfd, E_Config_Dialog_Data *data);
static Evas_Object *_e_desktop_edit_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *data);
static void         _e_desktop_editor_cb_icon_select(void *data1, void *data2);
static void         _e_desktop_edit_cb_icon_select_destroy(void *obj);
static void         _e_desktop_edit_cb_icon_select_ok(void *data, E_Dialog *dia);
static void         _e_desktop_edit_cb_icon_select_cancel(void *data, E_Dialog *dia);
static void         _e_desktop_editor_icon_update(E_Config_Dialog_Data *cfdata);
static void         _e_desktop_editor_cb_exec_select(void *data1, void *data2);
static void         _e_desktop_edit_cb_exec_select_destroy(void *obj);
static void         _e_desktop_edit_cb_exec_select_ok(void *data, E_Dialog *dia);
static void         _e_desktop_edit_cb_exec_select_cancel(void *data, E_Dialog *dia);
static void         _e_desktop_editor_exec_update(E_Config_Dialog_Data *cfdata);
static void         _e_desktop_edit_select_cb(void *data, Evas_Object *obj);
static void         _e_desktop_editor_icon_entry_changed(void *data, Evas_Object *obj);

#define IFDUP(src, dst) if (src) dst = strdup(src); else \
    dst = NULL


static int
_e_util_icon_save(Ecore_X_Icon *icon, const char *filename)
{
   Ecore_Evas *ee;
   Evas *evas;
   Evas_Object *im;
   int ret;

   ee = ecore_evas_buffer_new(icon->width, icon->height);
   if (!ee) return 0;
   evas = ecore_evas_get(ee);
   evas_image_cache_set(evas, 0);
   evas_font_cache_set(evas, 0);

   im = evas_object_image_add(evas);
   if (!im)
     {
        ecore_evas_free(ee);
        return 0;
     }
   evas_object_move(im, 0, 0);
   evas_object_resize(im, icon->width, icon->height);
   evas_object_image_size_set(im, icon->width, icon->height);
   evas_object_image_data_copy_set(im, icon->data);
   evas_object_image_alpha_set(im, 1);
   evas_object_show(im);
   ret = evas_object_image_save(im, filename, NULL, NULL);
   evas_object_del(im);
   ecore_evas_free(ee);
   return ret;
}
/* externally accessible functions */

E_API Efreet_Desktop *
e_desktop_client_create(E_Client *ec)
{
   Efreet_Desktop *desktop = NULL;
   const char *desktop_dir, *icon_dir;
   const char *bname, *bclass, *btitle;
   char path[PATH_MAX + PATH_MAX + 2];

   bname = ec->icccm.name;
   if ((bname) && (bname[0] == 0)) bname = NULL;
   bclass = ec->icccm.class;
   if ((bclass) && (bclass[0] == 0)) bclass = NULL;
   btitle = e_client_util_name_get(ec);

   desktop_dir = e_user_desktop_dir_get();

   if ((!desktop_dir) || (!e_util_dir_check(desktop_dir))) return NULL;

   icon_dir = e_user_icon_dir_get();
   if ((!icon_dir) || (!e_util_dir_check(icon_dir))) return NULL;

   if (bname)
     {
        snprintf(path, sizeof(path), "%s/%s.desktop", desktop_dir, bname);
        desktop = efreet_desktop_empty_new(path);
     }
   else
     {
        int i;

        for (i = 1; i < 65536; i++)
          {
             snprintf(path, sizeof(path), "%s/_new_app-%i.desktop",
                      desktop_dir, i);
             if (!ecore_file_exists(path))
               {
                  desktop = efreet_desktop_empty_new(path);
                  break;
               }
          }
        if (!desktop)
          {
             snprintf(path, sizeof(path), "%s/_rename_me-%i.desktop",
                      desktop_dir, (int)ecore_time_get());
             desktop = efreet_desktop_empty_new(NULL);
          }
     }

   if (!desktop)
     {
        //XXX out of memory?
        return NULL;
     }
   if (bclass) desktop->name = strdup(bclass);
   else if (bname)
     desktop->name = strdup(bname);
   else if (btitle)
     desktop->name = strdup(btitle);

   if (btitle) desktop->comment = strdup(btitle);

   if (bclass) desktop->startup_wm_class = strdup(bclass);
   if (ec->icccm.command.argc > 0)
     // FIXME this should concat the entire argv array together
     desktop->exec = strdup(ec->icccm.command.argv[0]);
   else if (bname)
     desktop->exec = strdup(bname);

// disable this
//   if (ec->netwm.startup_id > 0) desktop->startup_notify = 1;
#ifndef HAVE_WAYLAND_ONLY
   if (ec->netwm.icons)
     {
        /* FIXME
         * - Find the icon with the best size
         * - Should use mkstemp
         */
        char file[PATH_MAX];

        snprintf(file, sizeof(file), "%s-%.6f.png", bname ?: "", ecore_time_get());
        snprintf(path, sizeof(path), "%s/%s", icon_dir, file);
        if (_e_util_icon_save(&(ec->netwm.icons[0]), path))
          desktop->icon = strdup(file);
        else
          fprintf(stderr, "Could not save file from ARGB: %s\n", path);
     }
#endif
   return desktop;
}

E_API E_Desktop_Edit *
e_desktop_client_edit(E_Client *ec)
{
   E_Desktop_Edit *editor;
   int new_desktop = 0;

   editor = E_OBJECT_ALLOC(E_Desktop_Edit, E_DESKTOP_EDIT_TYPE, _e_desktop_edit_free);
   if (!editor) return NULL;
   if (ec->desktop)
     editor->desktop = ec->desktop;

   /* the client does not yet have a desktop entry. add one and pre-populate
      it with values from the client */
   if (!editor->desktop)
     {
        editor->desktop = e_desktop_client_create(ec);
        if ((editor->desktop) && (editor->desktop->icon))
          editor->tmp_image_path = strdup(editor->desktop->icon);
        new_desktop = 1;
     }

#if 0
   if ((!bname) && (!bclass))
     {
        e_util_dialog_show(_("Incomplete Window Properties"),
                           _("The window you are creating an icon for<ps/>"
                             "does not contain window name and class<ps/>"
                             "properties. Without these, you will have to<ps/>"
                             "use the window title instead. This will only<ps/>"
                             "work if the window title is the same at<ps/>"
                             "the time the window starts up, and does not<ps/>"
                             "change."));
     }
#endif
   if (!_e_desktop_edit_view_create(editor))
     {
        e_object_del(E_OBJECT(editor));
        editor = NULL;
     }
   else
     e_config_dialog_changed_set(editor->cfd, new_desktop);

   return editor;
}

E_API E_Desktop_Edit *
e_desktop_edit(Efreet_Desktop *desktop)
{
   E_Desktop_Edit *editor;

   editor = E_OBJECT_ALLOC(E_Desktop_Edit, E_DESKTOP_EDIT_TYPE, _e_desktop_edit_free);
   if (!editor) return NULL;
   if (desktop) editor->desktop = desktop;
   if (!_e_desktop_edit_view_create(editor))
     {
        e_object_del(E_OBJECT(editor));
        editor = NULL;
     }
   return editor;
}

static int
_e_desktop_edit_view_create(E_Desktop_Edit *editor)
{
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);
   if (!v) return 0;

   /* view methods */
   v->create_cfdata = _e_desktop_edit_create_data;
   v->free_cfdata = _e_desktop_edit_free_data;
   v->basic.apply_cfdata = _e_desktop_edit_basic_apply_data;
   v->basic.create_widgets = _e_desktop_edit_basic_create_widgets;
   v->basic.check_changed = _e_desktop_edit_basic_check_changed;

   editor->cfd =
     e_config_dialog_new(NULL, _("Desktop Entry Editor"), "E",
                         "applications/new_application",
                         "preferences-applications", 0, v, editor);

   if (!editor->cfd)
     {
        E_FREE(v);
        return 0;
     }

   return 1;
}

/* local subsystem functions */
static void
_e_desktop_edit_free(E_Desktop_Edit *editor)
{
   if (!editor) return;
   E_OBJECT_CHECK(editor);
   E_OBJECT_TYPE_CHECK(editor, E_DESKTOP_EDIT_TYPE);

   E_FREE(editor->tmp_image_path);
   E_FREE(editor);
}

/**
 * Populates the config dialog's data from the .desktop file passed in
 */
static void *
_e_desktop_edit_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;
   Efreet_Desktop *desktop = NULL;
   char path[PATH_MAX + PATH_MAX + 2];

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   if (!cfdata) return NULL;
   e_win_no_reopen_set(cfd->dia->win, 1);
   cfdata->editor = cfd->data;

   /*
    * If we can't write to the file on it's current location,
    * we always save to the user's applications dir.
    * If the file is already there, then edit it directly. Otherwise, create
    * a new empty desktop entry there.
    *
    * cfdata->editor->desktop is the the desktop passed in
    * cfdata->desktop is the desktop to save
    * desktop is the desktop to load
    */
   path[0] = '\0';
   if (cfdata->editor->desktop)
     {
        char dir[PATH_MAX];
        const char *file;

        if (ecore_file_can_write(cfdata->editor->desktop->orig_path))
          cfdata->desktop = efreet_desktop_uncached_new(cfdata->editor->desktop->orig_path);
        else
          {
             snprintf(dir, sizeof(dir), "%s/applications", efreet_data_home_get());
             if (!strncmp(dir, cfdata->editor->desktop->orig_path, strlen(dir)))
               cfdata->desktop = efreet_desktop_uncached_new(cfdata->editor->desktop->orig_path);
             else
               {
                  /* file not in user's dir, so create new desktop that points there */
                  if (!ecore_file_exists(dir)) ecore_file_mkdir(dir);
                  file = ecore_file_file_get(cfdata->editor->desktop->orig_path);
                  snprintf(path, sizeof(path), "%s/%s", dir, file);
                  /*
                   * if a file already exists in the user dir with this name, we
                   * fetch the pointer to it, so the caches stay consistent (this
                   * probably will never return non null, since the ui shouldn't
                   * provide a means to edit a file in a system dir when one
                   * exists in the user's
                   */
                  cfdata->desktop = efreet_desktop_uncached_new(path);
               }
          }
        desktop = cfdata->editor->desktop;
     }

   if (!cfdata->desktop)
     cfdata->desktop = efreet_desktop_empty_new(path);

   if (!desktop) desktop = cfdata->desktop;

   IFDUP(desktop->name, cfdata->name);
   IFDUP(desktop->generic_name, cfdata->generic_name);
   IFDUP(desktop->comment, cfdata->comment);
   IFDUP(desktop->exec, cfdata->exec);
   IFDUP(desktop->try_exec, cfdata->try_exec);
   IFDUP(desktop->url, cfdata->url);
   IFDUP(desktop->startup_wm_class, cfdata->startup_wm_class);
   IFDUP(desktop->orig_path, cfdata->orig_path);

   if (desktop->categories)
     cfdata->categories = efreet_desktop_string_list_join(desktop->categories);
   if (desktop->mime_types)
     cfdata->mimes = efreet_desktop_string_list_join(desktop->mime_types);

   IFDUP(desktop->icon, cfdata->icon);

   cfdata->startup_notify = desktop->startup_notify;
   cfdata->terminal = desktop->terminal;
   cfdata->show_in_menus = !desktop->no_display;

   if (cfdata->exec && *cfdata->exec)
     cfdata->type = 0;
   else if (cfdata->url && *cfdata->url)
     cfdata->type = 1;

   return cfdata;
}

/**
 * Frees the config dialog data
 */
static void
_e_desktop_edit_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   if (cfdata->editor->tmp_image_path)
     {
        if ((!cfdata->desktop) || (!cfdata->editor->saved) ||
            (!cfdata->desktop->icon) ||
            (strcmp(cfdata->editor->tmp_image_path, cfdata->desktop->icon)))
          {
             ecore_file_unlink(cfdata->editor->tmp_image_path);
          }
     }
   if (cfdata->desktop) efreet_desktop_free(cfdata->desktop);

   E_FREE(cfdata->name);
   E_FREE(cfdata->generic_name);
   E_FREE(cfdata->comment);
   E_FREE(cfdata->exec);
   E_FREE(cfdata->try_exec);
   E_FREE(cfdata->url);
   E_FREE(cfdata->startup_wm_class);
   E_FREE(cfdata->categories);
   E_FREE(cfdata->icon);
   E_FREE(cfdata->mimes);
   E_FREE(cfdata->orig_path);

   if (cfdata->editor->icon_fsel_dia)
     e_object_del(E_OBJECT(cfdata->editor->icon_fsel_dia));

   e_object_del(E_OBJECT(cfdata->editor));
   free(cfdata);
}

static void
_e_desktop_edit_user_local_desktop_filename_generate(E_Config_Dialog_Data *cfdata, char *path)
{
   char buf[PATH_MAX];
   const char *name;
   unsigned int i;
   int prefix;

   if ((cfdata->name) && (cfdata->name[0]))
     name = cfdata->name;
   else if ((cfdata->desktop) && (cfdata->desktop->name) &&
            (cfdata->desktop->name[0]))
     name = cfdata->name;
   else
     name = NULL;

   if (name)
     {
        const char *s = name;
        for (i = 0; i < sizeof(buf) - 1 && s[i]; i++)
          {
             if (isalnum(s[i]))
               buf[i] = s[i];
             else
               buf[i] = '_';
          }
        buf[i] = '\0';
     }
   else
     eina_strlcpy(buf, "unnamed_desktop", sizeof(buf));

   i = snprintf(path, PATH_MAX, "%s/applications/%s.desktop",
                efreet_data_home_get(), buf);
   if (i >= PATH_MAX)
     {
        path[0] = '\0';
        return;
     }
   prefix = i - (sizeof(".desktop") - 1);

   for (i = 0; ecore_file_exists(path); i++)
     snprintf(path + prefix, PATH_MAX - prefix, "-%u.desktop", i);
}

static void
_e_desktop_edit_update_orig_path(E_Config_Dialog_Data *cfdata)
{
   const char *orig_path;
   char path[PATH_MAX];

   if ((cfdata->desktop->orig_path) && (cfdata->desktop->orig_path[0]))
     orig_path = cfdata->desktop->orig_path;
   else
     {
        _e_desktop_edit_user_local_desktop_filename_generate(cfdata, path);
        orig_path = cfdata->orig_path = strdup(path);
     }

   if (cfdata->orig_path_entry)
     e_widget_entry_text_set(cfdata->orig_path_entry, orig_path);
}

/**
 * Apply the basic config dialog
 */
static int
_e_desktop_edit_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   E_FREE(cfdata->desktop->name);
   IFDUP(cfdata->name, cfdata->desktop->name);
   E_FREE(cfdata->desktop->comment);
   IFDUP(cfdata->comment, cfdata->desktop->comment);

   switch (cfdata->type)
     {
      case 1:
        E_FREE(cfdata->desktop->url);
        IFDUP(cfdata->url, cfdata->desktop->url);
        break;

      default:
        E_FREE(cfdata->desktop->exec);
        IFDUP(cfdata->exec, cfdata->desktop->exec);
        E_FREE(cfdata->desktop->try_exec);
        IFDUP(cfdata->try_exec, cfdata->desktop->try_exec);
        break;
     }

   E_FREE(cfdata->desktop->generic_name);
   IFDUP(cfdata->generic_name, cfdata->desktop->generic_name);
   E_FREE(cfdata->desktop->startup_wm_class);
   IFDUP(cfdata->startup_wm_class, cfdata->desktop->startup_wm_class);


   E_FREE_LIST(cfdata->desktop->categories, eina_stringshare_del);
   if (cfdata->categories)
     cfdata->desktop->categories = efreet_desktop_string_list_parse(cfdata->categories);

   E_FREE_LIST(cfdata->desktop->mime_types, eina_stringshare_del);
   if (cfdata->mimes)
     cfdata->desktop->mime_types = efreet_desktop_string_list_parse(cfdata->mimes);

   E_FREE(cfdata->desktop->icon);
   IFDUP(cfdata->icon, cfdata->desktop->icon);

   cfdata->desktop->startup_notify = cfdata->startup_notify;
   cfdata->desktop->terminal = cfdata->terminal;
   cfdata->desktop->no_display = !cfdata->show_in_menus;

   if (cfdata->desktop->orig_path && cfdata->desktop->orig_path[0])
     cfdata->editor->saved = efreet_desktop_save(cfdata->desktop);
   else
     {
        _e_desktop_edit_update_orig_path(cfdata);
        cfdata->editor->saved = efreet_desktop_save_as
            (cfdata->desktop, cfdata->orig_path);
     }
   e_int_menus_cache_clear();
   return 1;
}

static int
_e_desktop_edit_basic_check_changed(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   int ret;

   if ((cfdata->startup_notify != cfdata->desktop->startup_notify) ||
       (cfdata->terminal != cfdata->desktop->terminal) ||
       (cfdata->show_in_menus != !cfdata->desktop->no_display))
     return 1;

#define CHECK(k)                                               \
  do                                                           \
    {                                                          \
       const char *__k;                                        \
       if (cfdata->desktop->k)                                 \
         __k = cfdata->desktop->k;                             \
       else if ((cfdata->editor) && (cfdata->editor->desktop)) \
         __k = cfdata->editor->desktop->k;                     \
       else                                                    \
         __k = NULL;                                           \
       if ((cfdata->k && !__k) || (!cfdata->k && __k) ||       \
           (cfdata->k && __k && strcmp(cfdata->k, __k) != 0))  \
         return 1;                                             \
    }                                                          \
  while (0)

   CHECK(name);
   CHECK(exec);
   CHECK(try_exec);
   CHECK(url);
   CHECK(comment);
   CHECK(generic_name);
   CHECK(startup_wm_class);
   CHECK(icon);
#undef CHECK

   ret = 0;

   if (cfdata->edited_categories)
     {
        const char *str;
        Eina_List *lst = NULL, *old_lst;

        cfdata->edited_categories = EINA_FALSE;
        cfdata->changed_categories = EINA_FALSE;

        if (cfdata->desktop->categories)
          old_lst = cfdata->desktop->categories;
        else if ((cfdata->editor) && (cfdata->editor->desktop))
          old_lst = cfdata->editor->desktop->categories;
        else
          old_lst = NULL;

        if (cfdata->categories)
          lst = efreet_desktop_string_list_parse(cfdata->categories);
        if (eina_list_count(lst) != eina_list_count(old_lst))
          cfdata->changed_categories = EINA_TRUE;

        EINA_LIST_FREE(lst, str)
          {
             if (!cfdata->changed_categories)
               {
                  Eina_List *n;
                  const char *old;
                  Eina_Bool found = EINA_FALSE;
                  EINA_LIST_FOREACH(old_lst, n, old)
                    {
                       if (strcmp(str, old) == 0)
                         {
                            found = EINA_TRUE;
                            break;
                         }
                    }
                  if (!found)
                    cfdata->changed_categories = EINA_TRUE;
               }
             eina_stringshare_del(str);
          }
     }
   ret |= cfdata->changed_categories;

   if (!ret)
     {
        if (cfdata->edited_mimes)
          {
             const char *str;
             Eina_List *lst = NULL, *old_lst;

             cfdata->edited_mimes = EINA_FALSE;
             cfdata->changed_mimes = EINA_FALSE;

             if (cfdata->desktop->mime_types)
               old_lst = cfdata->desktop->mime_types;
             else if ((cfdata->editor) && (cfdata->editor->desktop))
               old_lst = cfdata->editor->desktop->mime_types;
             else
               old_lst = NULL;

             if (cfdata->mimes)
               lst = efreet_desktop_string_list_parse(cfdata->mimes);
             if (eina_list_count(lst) != eina_list_count(old_lst))
               cfdata->changed_mimes = EINA_TRUE;

             EINA_LIST_FREE(lst, str)
               {
                  if (!cfdata->changed_mimes)
                    {
                       Eina_List *n;
                       const char *old;
                       Eina_Bool found = EINA_FALSE;
                       EINA_LIST_FOREACH(old_lst, n, old)
                         {
                            if (strcmp(str, old) == 0)
                              {
                                 found = EINA_TRUE;
                                 break;
                              }
                         }
                       if (!found)
                         cfdata->changed_mimes = EINA_TRUE;
                    }
                  eina_stringshare_del(str);
               }
          }
        ret |= cfdata->changed_mimes;
     }

   return ret;
}

static void
_e_desktop_editor_name_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   _e_desktop_edit_update_orig_path(cfdata);
}

static void
_e_desktop_editor_categories_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   cfdata->edited_categories = EINA_TRUE;
}

static void
_e_desktop_editor_icon_entry_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   _e_desktop_editor_icon_update(cfdata);
}

static void
_e_desktop_editor_mimes_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   cfdata->edited_mimes = EINA_TRUE;
}

static Evas_Object *
_e_desktop_edit_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   E_Desktop_Edit *editor = cfdata->editor;
   E_Radio_Group *rg;
   Evas_Object *otb, *ol, *o, *ot, *win;
   Evas_Coord mw, mh;

   editor->evas = evas;
   win = e_win_evas_win_get(evas);

   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   o = evas_object_rectangle_add(evas);
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_pass_events_set(o, EINA_TRUE);
   mw = 200 * e_scale; mh = 80 * e_scale;
   evas_object_size_hint_min_set(o, mw, mh);
   e_widget_table_object_append(ot, o, 1, 4, 1, 1, 1, 1, 1, 1);

   o = e_widget_label_add(evas, _("Name"));
   e_widget_table_object_append(ot, o, 0, 0, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->name), NULL, NULL, NULL);
   e_widget_size_min_get(o, &mw, &mh);
   if (mw < 220) mw = 220;
   e_widget_size_min_set(o, mw, mh);
   if ((!cfdata->desktop->orig_path) || (!cfdata->desktop->orig_path[0]))
     e_widget_on_change_hook_set(o, _e_desktop_editor_name_changed, cfdata);
   e_widget_table_object_append(ot, o, 1, 0, 1, 1, 1, 1, 1, 0);

   o = e_widget_label_add(evas, _("Comment"));
   e_widget_table_object_append(ot, o, 0, 1, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->comment), NULL, NULL, NULL);
   e_widget_table_object_append(ot, o, 1, 1, 1, 1, 1, 1, 1, 0);

   rg = e_widget_radio_group_new(&(cfdata->type));

   // desktop type: application

   o = e_widget_radio_add(evas, _("Application"), 0, rg);
   e_widget_table_object_append(ot, o, 0, 2, 1, 1, 1, 1, 0, 0);

   editor->entry_widget_exec = e_widget_entry_add
       (win, &(cfdata->exec), NULL, NULL, NULL);
   e_widget_table_object_append(ot, editor->entry_widget_exec, 1, 2, 1, 1, 1, 1, 1, 0);

   o = e_widget_button_add
       (evas, "...", NULL, _e_desktop_editor_cb_exec_select, cfdata, editor);
   e_widget_table_object_append(ot, o, 2, 2, 1, 1, 0, 0, 0, 0);

   // desktop type: url
   o = e_widget_radio_add(evas, _("URL"), 1, rg);
   e_widget_table_object_append(ot, o, 0, 3, 1, 1, 1, 1, 0, 0);

   editor->entry_widget_url = e_widget_entry_add
       (win, &(cfdata->url), NULL, NULL, NULL);
   e_widget_table_object_append(ot, editor->entry_widget_url, 1, 3, 1, 1, 1, 1, 1, 0);
   // FIXME: add url selection dialog (file:/etc/..)

   e_widget_toolbook_page_append
     (otb, NULL, _("Basic"), ot, 1, 0, 1, 0, 0.5, 0.0);

   /* e_widget_size_min_get(ol, &mw, &mh); */

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   o = evas_object_rectangle_add(evas);
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_pass_events_set(o, EINA_TRUE);
   mw = 160 * e_scale; mh = 160 * e_scale;
   evas_object_size_hint_min_set(o, mw, mh);
   e_widget_table_object_append(ot, o, 0, 0, 2, 1, 1, 1, 1, 1);

   editor->img_widget = e_widget_button_add
       (evas, "", NULL, _e_desktop_editor_cb_icon_select, cfdata, editor);
   _e_desktop_editor_icon_update(cfdata);

   e_widget_table_object_append(ot, editor->img_widget, 0, 0, 2, 1, 1, 1, 1, 1);

   o = e_widget_label_add(evas, _("Icon"));
   e_widget_table_object_append(ot, o, 0, 1, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->icon), NULL, NULL, NULL);
   cfdata->icon_entry = o;
   e_widget_on_change_hook_set(o, _e_desktop_editor_icon_entry_changed, cfdata);
   e_widget_table_object_append(ot, o, 1, 1, 1, 1, 1, 1, 1, 0);

   e_widget_toolbook_page_append
     (otb, NULL, _("Icon"), ot, 0, 0, 1, 1, 0.5, 0.5);

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   o = e_widget_label_add(evas, _("Generic Name"));
   e_widget_table_object_append(ot, o, 0, 0, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->generic_name), NULL, NULL, NULL);
   e_widget_table_object_append(ot, o, 1, 0, 1, 1, 1, 1, 1, 0);

   o = e_widget_label_add(evas, _("Window Class"));
   e_widget_table_object_append(ot, o, 0, 1, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->startup_wm_class), NULL, NULL, NULL);
   e_widget_table_object_append(ot, o, 1, 1, 1, 1, 1, 1, 1, 0);

   o = e_widget_label_add(evas, _("Categories"));
   e_widget_table_object_append(ot, o, 0, 2, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->categories), NULL, NULL, NULL);
   e_widget_on_change_hook_set(o, _e_desktop_editor_categories_changed, cfdata);
   e_widget_table_object_append(ot, o, 1, 2, 1, 1, 1, 1, 1, 0);

   o = e_widget_label_add(evas, _("Mime Types"));
   e_widget_table_object_append(ot, o, 0, 3, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, &(cfdata->mimes), NULL, NULL, NULL);
   e_widget_on_change_hook_set(o, _e_desktop_editor_mimes_changed, cfdata);
   e_widget_table_object_append(ot, o, 1, 3, 1, 1, 1, 1, 1, 0);

   o = e_widget_label_add(evas, _("Desktop file"));
   e_widget_table_object_append(ot, o, 0, 4, 1, 1, 1, 1, 0, 0);

   o = e_widget_entry_add(win, NULL, NULL, NULL, NULL);
   e_widget_table_object_append(ot, o, 1, 4, 1, 1, 1, 1, 1, 0);
   e_widget_entry_readonly_set(o, 1);
   cfdata->orig_path_entry = o;
   _e_desktop_edit_update_orig_path(cfdata);

   e_widget_toolbook_page_append
     (otb, NULL, _("General"), ot, 1, 0, 1, 0, 0.5, 0.0);

   ol = e_widget_list_add(evas, 0, 0);
   o = e_widget_check_add(evas, _("Startup Notify"), &(cfdata->startup_notify));
   e_widget_list_object_append(ol, o, 1, 0, 0.0);
   o = e_widget_check_add(evas, _("Run in Terminal"), &(cfdata->terminal));
   e_widget_list_object_append(ol, o, 1, 0, 0.0);
   o = e_widget_check_add(evas, _("Show in Menus"), &(cfdata->show_in_menus));
   e_widget_list_object_append(ol, o, 1, 0, 0.0);
   e_widget_toolbook_page_append
     (otb, NULL, _("Options"), ol, 1, 0, 1, 0, 0.5, 0.0);

   e_widget_toolbook_page_show(otb, 0);

   e_util_win_auto_resize_fill(cfd->dia->win);
   elm_win_center(cfd->dia->win, 1, 1);

   return otb;
}

static void
_e_desktop_editor_cb_icon_select(void *data1, void *data2)
{
   E_Config_Dialog_Data *cfdata;
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord mw, mh;
   E_Desktop_Edit *editor;
   char *path = NULL;
   const char *icon_path = NULL;
   char buf[PATH_MAX + 32];

   editor = data2;
   cfdata = data1;

   if (editor->icon_fsel_dia) return;

   dia = e_dialog_new(NULL, "E", "_eap_icon_select_dialog");
   if (!dia) return;
   e_dialog_resizable_set(dia, 1);
   e_object_del_attach_func_set(E_OBJECT(dia),
                                _e_desktop_edit_cb_icon_select_destroy);
   snprintf(buf, sizeof(buf), _("Select an Icon for '%s'"), ecore_file_file_get(cfdata->orig_path));
   e_dialog_title_set(dia, buf);
   dia->data = cfdata;

   /* absolute path to icon */
   /* XXX change this to a generic icon selector (that can do either
    * files from a dir, or icons in the current theme */
   if (cfdata->icon)
     {
        if (ecore_file_exists(cfdata->icon))
          icon_path = cfdata->icon;
        else
          icon_path = efreet_icon_path_find(e_config->icon_theme, cfdata->icon, 64);

        if (icon_path)
          path = ecore_file_dir_get(icon_path);
     }

   if (path)
     {
        o = e_widget_fsel_add(evas_object_evas_get(dia->win), "/", path, NULL, NULL,
                              _e_desktop_edit_select_cb, cfdata,
                              NULL, cfdata, 1);
        free(path);
     }
   else
     {
        o = e_widget_fsel_add(evas_object_evas_get(dia->win), "~/", "/", NULL, NULL,
                              _e_desktop_edit_select_cb, cfdata,
                              NULL, cfdata, 1);
     }

   evas_object_show(o);
   editor->icon_fsel = o;
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(dia, o, mw, mh);

   /* buttons at the bottom */
   e_dialog_button_add(dia, _("OK"), NULL,
                       _e_desktop_edit_cb_icon_select_ok, cfdata);
   e_dialog_button_add(dia, _("Cancel"), NULL,
                       _e_desktop_edit_cb_icon_select_cancel, cfdata);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   editor->icon_fsel_dia = dia;
}

static void
_e_desktop_editor_cb_exec_select(void *data1, void *data2)
{
   E_Config_Dialog_Data *cfdata;
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord mw, mh;
   E_Desktop_Edit *editor;
   char *path = NULL;

   editor = data2;
   cfdata = data1;

   if (editor->exec_fsel_dia) return;

   dia = e_dialog_new(NULL, "E", "_eap_exec_select_dialog");
   if (!dia) return;
   e_object_del_attach_func_set(E_OBJECT(dia),
                                _e_desktop_edit_cb_exec_select_destroy);
   e_dialog_title_set(dia, _("Select an Executable"));
   dia->data = cfdata;

   /* absolute path to exe */
   if (cfdata->exec)
     {
        path = ecore_file_dir_get(cfdata->exec);
        if (path && !ecore_file_exists(path))
          {
             free(path);
             path = NULL;
          }
     }

   if (path)
     {
        o = e_widget_fsel_add(evas_object_evas_get(dia->win), "/", path, NULL, NULL,
                              _e_desktop_edit_select_cb, cfdata,
                              NULL, cfdata, 1);
        free(path);
        path = NULL;
     }
   else
     {
        o = e_widget_fsel_add(evas_object_evas_get(dia->win), "~/", "/", NULL, NULL,
                              _e_desktop_edit_select_cb, cfdata,
                              NULL, cfdata, 1);
     }

   evas_object_show(o);
   editor->exec_fsel = o;
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(dia, o, mw, mh);
   e_dialog_resizable_set(dia, 1);

   /* buttons at the bottom */
   e_dialog_button_add(dia, _("OK"), NULL,
                       _e_desktop_edit_cb_exec_select_ok, cfdata);
   e_dialog_button_add(dia, _("Cancel"), NULL,
                       _e_desktop_edit_cb_exec_select_cancel, cfdata);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   editor->exec_fsel_dia = dia;
}

static void
_e_desktop_edit_select_cb(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED)
{
}

static void
_e_desktop_edit_cb_icon_select_destroy(void *obj)
{
   E_Dialog *dia = obj;
   E_Config_Dialog_Data *cfdata = dia->data;

/* extra unref isn't needed - there is no extra ref() anywhere i saw */
/*   e_object_unref(E_OBJECT(dia));*/
   _e_desktop_edit_cb_icon_select_cancel(cfdata, NULL);
}

static void
_e_desktop_edit_cb_icon_select_ok(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;
   const char *file;
   char *dir;
   const char *icon_dir;

   cfdata = data;
   file = e_widget_fsel_selection_path_get(cfdata->editor->icon_fsel);
   dir = ecore_file_dir_get(file);

   E_FREE(cfdata->icon);

   /* TODO: Check for theme icon */
   icon_dir = e_user_icon_dir_get();
   if ((icon_dir) && (e_util_dir_check(icon_dir)) && (!e_util_strcmp(dir, icon_dir)))
     {
        cfdata->icon = strdup(ecore_file_file_get(file));
     }
   else
     {
        Eina_List *xdg_dirs, *l;
        char buf[PATH_MAX];

        xdg_dirs = efreet_data_dirs_get();

        EINA_LIST_FOREACH(xdg_dirs, l, icon_dir)
          {
             snprintf(buf, sizeof(buf), "%s/icons", icon_dir);
             if (!ecore_file_is_dir(buf)) continue;
             if (!e_util_strcmp(dir, buf))
               {
                  cfdata->icon = strdup(ecore_file_file_get(file));
                  break;
               }
          }
     }
   if (!cfdata->icon)
     {
        IFDUP(file, cfdata->icon);
     }
   else
     {
        /* strip ext */
        char *p;
        p = strrchr(cfdata->icon, '.');
        if (p)
          {
             /* TODO: Check if known extension */
             *p = '\0';
          }
     }
   E_FREE(dir);
   e_widget_entry_text_set(cfdata->icon_entry, cfdata->icon);

   _e_desktop_edit_cb_icon_select_cancel(data, dia);
}

static void
_e_desktop_edit_cb_icon_select_cancel(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   if (dia) e_util_defer_object_del(E_OBJECT(dia));
   cfdata->editor->icon_fsel_dia = NULL;
   _e_desktop_editor_icon_update(cfdata);
}

static void
_e_desktop_editor_icon_update(E_Config_Dialog_Data *cfdata)
{
   Evas_Object *o;

   if (!cfdata->editor->img_widget) return;
   o = e_util_icon_theme_icon_add(cfdata->icon, 128, cfdata->editor->evas);

   /* NB: this takes care of freeing any previous icon object */
   e_widget_button_icon_set(cfdata->editor->img_widget, o);
   e_widget_change(cfdata->editor->img_widget);
}

static void
_e_desktop_edit_cb_exec_select_destroy(void *obj)
{
   E_Dialog *dia = obj;
   E_Config_Dialog_Data *cfdata = dia->data;

/* guess it should work like _e_desktop_edit_cb_icon_select_destroy */
/*    e_object_unref(E_OBJECT(dia)); */
   _e_desktop_edit_cb_exec_select_cancel(cfdata, NULL);
}

static void
_e_desktop_edit_cb_exec_select_ok(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;
   const char *file;

   cfdata = data;
   file = e_widget_fsel_selection_path_get(cfdata->editor->exec_fsel);

   E_FREE(cfdata->exec);
   cfdata->exec = eina_str_escape(file);
   E_FREE(cfdata->try_exec);
   IFDUP(cfdata->exec, cfdata->try_exec);

   _e_desktop_edit_cb_exec_select_cancel(data, dia);
}

static void
_e_desktop_edit_cb_exec_select_cancel(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   if (dia) e_util_defer_object_del(E_OBJECT(dia));
   cfdata->editor->exec_fsel_dia = NULL;
   _e_desktop_editor_exec_update(cfdata);
}

static void
_e_desktop_editor_exec_update(E_Config_Dialog_Data *cfdata)
{
   if (!cfdata->editor->entry_widget_exec) return;
   e_widget_entry_text_set(cfdata->editor->entry_widget_exec, cfdata->exec);
}

#include "e.h"

/* local subsystem functions */
static void _e_dialog_free(E_Dialog *dia);
static void _e_dialog_del_func_cb(void *data, E_Dialog *dia);
static void _e_dialog_cb_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _e_dialog_cb_key_down(void *data, Evas *e, Evas_Object *obj, void *event);
static void _e_dialog_cb_wid_on_focus(void *data, Evas_Object *obj);

/* local subsystem globals */

/* externally accessible functions */

static E_Dialog *
_e_dialog_internal_new(Evas_Object *parent, const char *name, const char *class, int dialog)
{
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Modifier_Mask mask;
   Eina_Bool kg;
   Evas *e;

   dia = E_OBJECT_ALLOC(E_Dialog, E_DIALOG_TYPE, _e_dialog_free);
   if (!dia) return NULL;
   if (dialog)
     dia->win = elm_win_add(parent, name, ELM_WIN_DIALOG_BASIC);
   else
     dia->win = elm_win_add(parent, name, ELM_WIN_BASIC);
   if (!dia->win)
     {
        free(dia);
        return NULL;
     }
   elm_win_modal_set(dia->win, !!parent);
   e = evas_object_evas_get(dia->win);
   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL, _e_dialog_cb_delete, dia);
   ecore_evas_name_class_set(ecore_evas_ecore_evas_get(e), name, class);
   dia->bg_object = o = elm_layout_add(dia->win);
   E_EXPAND(o);
   E_FILL(o);
   elm_win_resize_object_add(dia->win, o);
   e_theme_edje_object_set(o, "base/theme/dialog",
                           "e/widgets/dialog/main");
   elm_layout_signal_emit(dia->bg_object, "e,state,buttons,off", "e");
   evas_object_show(o);

   o = e_widget_list_add(evas_object_evas_get(dia->win), 1, 1);
   e_widget_on_focus_hook_set(o, _e_dialog_cb_wid_on_focus, dia);
   dia->box_object = o;
   elm_object_part_content_set(dia->bg_object, "e.swallow.buttons", o);
   elm_layout_signal_emit(dia->bg_object, "e,state,no_resizable", "e");

   o = evas_object_rectangle_add(e);
   dia->event_object = o;
   mask = 0;
   kg = evas_object_key_grab(o, "Tab", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"Tab\" key events to object %p.\n", o);
   mask = evas_key_modifier_mask_get(evas_object_evas_get(dia->win), "Shift");
   kg = evas_object_key_grab(o, "Tab", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"Tab\" key events to object %p.\n", o);
   mask = 0;
   kg = evas_object_key_grab(o, "Return", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"Return\" key events to object %p.\n", o);
   mask = 0;
   kg = evas_object_key_grab(o, "Escape", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"Escape\" key events to object %p.\n", o);
   mask = 0;
   kg = evas_object_key_grab(o, "KP_Enter", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"KP_Enter\" key events to object %p.\n", o);

   evas_object_event_callback_add(o, EVAS_CALLBACK_KEY_DOWN, _e_dialog_cb_key_down, dia);

   return dia;
}

E_API E_Dialog *
e_dialog_new(Evas_Object *parent, const char *name, const char *class)
{
   return _e_dialog_internal_new(parent, name, class, 1);
}

E_API E_Dialog *
e_dialog_normal_win_new(Evas_Object *parent, const char *name, const char *class)
{
   return _e_dialog_internal_new(parent, name, class, 0);
}

E_API void
e_dialog_button_add(E_Dialog *dia, const char *label, const char *icon, E_Dialog_Cb func, void *data)
{
   Evas_Object *o;

   if (!func) func = _e_dialog_del_func_cb;
   o = e_widget_button_add(evas_object_evas_get(dia->win), label, icon, (void (*)(void *, void *))func, data, dia);
   e_widget_list_object_append(dia->box_object, o, 1, 0, 0.5);
   if (!dia->buttons)
     elm_layout_signal_emit(dia->bg_object, "e,state,buttons,on", "e");
   dia->buttons = eina_list_append(dia->buttons, o);
}

E_API int
e_dialog_button_focus_num(E_Dialog *dia, int button)
{
   Evas_Object *o;

   o = eina_list_nth(dia->buttons, button);
   if (o) e_widget_focus_steal(o);
   return 1;
}

E_API int
e_dialog_button_disable_num_set(E_Dialog *dia, int button, int disabled)
{
   Evas_Object *o;

   o = eina_list_nth(dia->buttons, button);
   if (o) e_widget_disabled_set(o, disabled);
   return 1;
}

E_API int
e_dialog_button_disable_num_get(E_Dialog *dia, int button)
{
   Evas_Object *o;
   int ret = 0;

   o = eina_list_nth(dia->buttons, button);
   if (o) ret = e_widget_disabled_get(o);
   return ret;
}

E_API void
e_dialog_title_set(E_Dialog *dia, const char *title)
{
   elm_win_title_set(dia->win, title);
}

E_API void
e_dialog_text_set(E_Dialog *dia, const char *text)
{
   if (!dia->text_object)
     {
        Evas_Object *o;

        o = elm_layout_add(dia->win);
        dia->text_object = o;
        e_theme_edje_object_set(o, "base/theme/dialog",
                                "e/widgets/dialog/text");
        elm_object_part_content_set(dia->bg_object, "e.swallow.content", o);
        evas_object_show(o);
     }
   elm_object_part_text_set(dia->text_object, "e.textblock.message", text);
}

E_API void
e_dialog_icon_set(E_Dialog *dia, const char *icon, Evas_Coord size)
{
   if (!icon)
     {
        if (dia->icon_object)
          {
             evas_object_del(dia->icon_object);
             dia->icon_object = NULL;
          }
        elm_layout_signal_emit(dia->bg_object, "e,state,icon", "e");
        elm_layout_signal_emit(dia->bg_object, "e,icon,disabled", "e");
     }
   else
     {
        if (!dia->icon_object)
          {
             dia->icon_object = e_icon_add(evas_object_evas_get(dia->win));
             elm_object_part_content_set(dia->bg_object, "e.swallow.icon", dia->icon_object);
             evas_object_show(dia->icon_object);
          }
        if (!e_util_icon_theme_set(dia->icon_object, icon))
          e_icon_file_edje_set(dia->icon_object, icon, "icon");
        evas_object_size_hint_min_set(dia->icon_object, size * e_scale, size * e_scale);
        elm_layout_signal_emit(dia->bg_object, "e,state,icon", "e");
        elm_layout_signal_emit(dia->bg_object, "e,icon,enabled", "e");
     }
   edje_object_message_signal_process(elm_layout_edje_get(dia->bg_object));
}

E_API void
e_dialog_border_icon_set(E_Dialog *dia, const char *icon)
{
   E_Client *ec;

   ec = e_win_client_get(dia->win);
   if (ec)
     eina_stringshare_replace(&ec->internal_icon, icon);
}

E_API void
e_dialog_content_set(E_Dialog *dia, Evas_Object *obj, Evas_Coord minw, Evas_Coord minh)
{
   dia->content_object = obj;
   e_widget_on_focus_hook_set(obj, _e_dialog_cb_wid_on_focus, dia);
   evas_object_size_hint_min_set(obj, minw, minh);
   elm_object_part_content_set(dia->bg_object, "e.swallow.content", obj);
   evas_object_show(obj);
}

E_API void
e_dialog_resizable_set(E_Dialog *dia, int resizable)
{
   dia->resizable = resizable;
   if (dia->win)
     {
        if (resizable)
          {
             E_EXPAND(dia->bg_object);
             e_util_win_auto_resize_fill(dia->win);
             elm_layout_signal_emit(dia->bg_object, "e,state,resizable", "e");
          }
        else
          {
             evas_object_resize(dia->win, dia->min_w, dia->min_h);
             evas_object_size_hint_weight_set(dia->bg_object, 0, 0);
             elm_layout_signal_emit(dia->bg_object, "e,state,no_resizable", "e");
          }
     }
}

E_API void
e_dialog_show(E_Dialog *dia)
{
   Evas_Coord mw, mh;
   Evas_Object *o;

   o = dia->text_object;
   if (o)
     elm_object_part_content_set(dia->bg_object, "e.swallow.content", o);

   edje_object_message_signal_process(elm_layout_edje_get(dia->bg_object));
   elm_layout_sizing_eval(dia->bg_object);
   evas_object_smart_calculate(dia->bg_object);
   evas_object_size_hint_min_get(dia->bg_object, &mw, &mh);
   dia->min_w = mw;
   dia->min_h = mh;

   evas_object_resize(dia->win, mw, mh);
   if (!dia->resizable)
     {
        evas_object_size_hint_weight_set(dia->bg_object, 0, 0);
        e_util_win_auto_resize_fill(dia->win);
     }
   else
     {
        E_EXPAND(dia->bg_object);
        e_util_win_auto_resize_fill(dia->win);
     }
   evas_object_show(dia->win);

   if (!e_widget_focus_get(dia->box_object))
     e_widget_focus_set(dia->box_object, 1);
   {
      E_Client *ec = e_win_client_get(dia->win);
      if (ec->focused && (e_config->pointer_slide || (!e_client_focus_policy_click(ec))))
        e_client_pointer_warp_to_center_now(ec);
   }
}

/* local subsystem functions */
static void
_e_dialog_free(E_Dialog *dia)
{
   if (dia->buttons) eina_list_free(dia->buttons);
   if (dia->text_object) evas_object_del(dia->text_object);
   if (dia->icon_object) evas_object_del(dia->icon_object);
   if (dia->box_object) evas_object_del(dia->box_object);
   if (dia->bg_object) evas_object_del(dia->bg_object);
   if (dia->content_object) evas_object_del(dia->content_object);
   if (dia->event_object) evas_object_del(dia->event_object);
   evas_object_del(dia->win);
   free(dia);
}

static void
_e_dialog_del_func_cb(void *data EINA_UNUSED, E_Dialog *dia)
{
   e_util_defer_object_del(E_OBJECT(dia));
}

static void
_e_dialog_cb_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Evas_Event_Key_Down *ev;
   E_Dialog *dia;

   ev = event;
   dia = data;
   if (!strcmp(ev->key, "Tab"))
     {
        if (evas_key_modifier_is_set(evas_key_modifier_get(evas_object_evas_get(dia->win)), "Shift"))
          {
             if (e_widget_focus_get(dia->box_object))
               {
                  if (!e_widget_focus_jump(dia->box_object, 0))
                    {
                       if (dia->text_object)
                         e_widget_focus_set(dia->box_object, 0);
                       else
                         {
                            e_widget_focus_set(dia->content_object, 0);
                            if (!e_widget_focus_get(dia->content_object))
                              e_widget_focus_set(dia->box_object, 0);
                         }
                    }
               }
             else
               {
                  if (!e_widget_focus_jump(dia->content_object, 0))
                    e_widget_focus_set(dia->box_object, 0);
               }
          }
        else
          {
             if (e_widget_focus_get(dia->box_object))
               {
                  if (!e_widget_focus_jump(dia->box_object, 1))
                    {
                       if (dia->text_object)
                         e_widget_focus_set(dia->box_object, 1);
                       else
                         {
                            e_widget_focus_set(dia->content_object, 1);
                            if (!e_widget_focus_get(dia->content_object))
                              e_widget_focus_set(dia->box_object, 1);
                         }
                    }
               }
             else
               {
                  if (!e_widget_focus_jump(dia->content_object, 1))
                    e_widget_focus_set(dia->box_object, 1);
               }
          }
     }
   else if (((!strcmp(ev->key, "Return")) ||
             (!strcmp(ev->key, "KP_Enter"))))
     {
        Evas_Object *o = NULL;

        if ((dia->content_object) && (e_widget_focus_get(dia->content_object)))
          o = e_widget_focused_object_get(dia->content_object);
        else
          o = e_widget_focused_object_get(dia->box_object);
        if (o) e_widget_activate(o);
     }
   else if (!strcmp(ev->key, "Escape"))
     e_object_del(E_OBJECT(dia));
}

static void
_e_dialog_cb_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_del(E_OBJECT(data));
}

static void
_e_dialog_cb_wid_on_focus(void *data, Evas_Object *obj)
{
   E_Dialog *dia;

   dia = data;
   if (obj == dia->content_object)
     e_widget_focused_object_clear(dia->box_object);
   else if (dia->content_object)
     e_widget_focused_object_clear(dia->content_object);
}

#include "e.h"

/* local subsystem functions */

static void           _e_drag_move(E_Drag *drag, int x, int y);
static void           _e_drag_coords_update(const E_Drop_Handler *h, int *dx, int *dy);
static Ecore_Window _e_drag_win_get(const E_Drop_Handler *h, int xdnd);
static int            _e_drag_win_matches(E_Drop_Handler *h, Ecore_Window win, int xdnd);
static void           _e_drag_win_show(E_Drop_Handler *h);
static void           _e_drag_win_hide(E_Drop_Handler *h);
static int            _e_drag_update(Ecore_Window root, int x, int y, Ecore_X_Atom action);
static void           _e_drag_end(int x, int y);
static void           _e_drag_xdnd_end(Ecore_Window root, int x, int y);
static void           _e_drag_free(E_Drag *drag);

static Eina_Bool      _e_dnd_cb_key_down(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_key_up(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_mouse_up(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_mouse_move(void *data, int type, void *event);
#ifndef HAVE_WAYLAND_ONLY
static Eina_Bool      _e_dnd_cb_event_dnd_enter(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_dnd_leave(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_dnd_position(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_dnd_finished(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_dnd_drop(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_dnd_selection(void *data, int type, void *event);
static Eina_Bool      _e_dnd_cb_event_hide(void *data, int type, Ecore_X_Event_Window_Hide *ev);
#endif

/* local subsystem globals */

typedef struct _XDnd XDnd;

struct _XDnd
{
   int         x, y;
   const char *type;
   void       *data;
};

static Eina_List *_event_handlers = NULL;
static Eina_List *_drop_handlers = NULL;
static Eina_List *_active_handlers = NULL;
static Eina_Hash *_drop_win_hash = NULL;

static Ecore_Window _drag_win = 0;
static Ecore_Window _drag_win_root = 0;

static Eina_List *_drag_list = NULL;
static E_Drag *_drag_current = NULL;
static Ecore_Window drop_win;

static XDnd *_xdnd = NULL;
static Ecore_X_Atom _text_atom = 0;

static Eina_Stringshare *_type_text_uri_list = NULL;
static Eina_Stringshare *_type_xds = NULL;
static Eina_Stringshare *_type_text_x_moz_url = NULL;
static Eina_Stringshare *_type_enlightenment_x_file = NULL;

static Eina_Stringshare **_e_dnd_types[] =
{
   &_type_text_uri_list,
   &_type_xds,
   &_type_text_x_moz_url,
   //&_type_enlightenment_x_file,
   NULL
};

static Eina_Hash *_drop_handlers_responsives;
static Ecore_X_Atom _action;

static void
_e_drop_handler_active_check(E_Drop_Handler *h, const E_Drag *drag, Eina_Stringshare *type)
{
   unsigned int i, j;

   if (h->hidden) return;
   for (i = 0; i < h->num_types; i++)
     {
        if (drag)
          {
             for (j = 0; j < drag->num_types; j++)
               {
                  if (h->types[i] != drag->types[j]) continue;
                  h->active = 1;
                  h->active_type = eina_stringshare_ref(h->types[i]);
                  return;
               }
          }
        else
          {
             if (h->types[i] != type) continue;
             h->active = 1;
             h->active_type = eina_stringshare_ref(h->types[i]);
             return;
          }
     }
}

static int
_e_drag_finalize(E_Drag *drag, E_Drag_Type type, int x, int y)
{
   const Eina_List *l;
   E_Drop_Handler *h;

   if (_drag_win) return 0;
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _drag_win = ecore_x_window_input_new(e_comp->win,
                                             0, 0,
                                             e_comp->w, e_comp->h);
        ecore_event_window_register(_drag_win, e_comp->ee, e_comp->evas,
                                      NULL, NULL, NULL, NULL);
        ecore_x_window_show(_drag_win);
        _drag_win_root = e_comp->root;
        if (!e_grabinput_get(_drag_win, 0, _drag_win))
          {
             ecore_x_window_free(_drag_win);
             _drag_win = _drag_win_root = 0;
             return 0;
          }
     }
   else
#endif
     {
        _drag_win = _drag_win_root = e_comp->ee_win;
        if (!e_comp_grab_input(1, 1))
          {
             _drag_win = _drag_win_root = 0;
             return 0;
          }
     }

   if (!drag->object)
     {
        e_drag_object_set(drag, evas_object_rectangle_add(drag->evas));
        evas_object_color_set(drag->object, 0, 0, 0, 0);
        //evas_object_color_set(drag->object, 255, 0, 0, 255);
     }
   evas_object_move(drag->comp_object, drag->x, drag->y);
   evas_object_resize(drag->comp_object, drag->w, drag->h);
   drag->visible = 1;
   evas_object_show(drag->comp_object);
   drag->type = type;

   drag->dx = x - drag->x;
   drag->dy = y - drag->y;

   _active_handlers = eina_list_free(_active_handlers);
   EINA_LIST_FOREACH(_drop_handlers, l, h)
     {
        Eina_Bool active = h->active;

        h->active = 0;
        eina_stringshare_replace(&h->active_type, NULL);
        _e_drop_handler_active_check(h, drag, NULL);
        if (h->active != active)
          {
             if (h->active)
               _active_handlers = eina_list_append(_active_handlers, h);
             else
               _active_handlers = eina_list_remove(_active_handlers, h);
          }
        h->entered = 0;
     }

   if (type == E_DRAG_XDND)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          {
             Ecore_X_Atom actions[] = {
                ECORE_X_DND_ACTION_MOVE, ECORE_X_DND_ACTION_PRIVATE,
                ECORE_X_DND_ACTION_COPY, ECORE_X_DND_ACTION_ASK,
                ECORE_X_DND_ACTION_LINK
             };

             ecore_x_dnd_aware_set(_drag_win, 1);
             ecore_x_dnd_types_set(_drag_win, drag->types, drag->num_types);
             ecore_x_dnd_actions_set(_drag_win, actions, 5);
             ecore_x_dnd_begin(_drag_win, drag->data, drag->data_size);
          }
#endif
#ifdef HAVE_WAYLAND
        if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
          {
          }
#endif
     }
   e_bindings_disabled_set(1);
   _drag_current = drag;
   return 1;
}

/* externally accessible functions */

EINTERN int
e_dnd_init(void)
{
   if (!_event_handlers)
     {
        _type_text_uri_list = eina_stringshare_add("text/uri-list");
        _type_xds = eina_stringshare_add("XdndDirectSave0");
        _type_text_x_moz_url = eina_stringshare_add("text/x-moz-url");
        _type_enlightenment_x_file = eina_stringshare_add("enlightenment/x-file");

        _drop_win_hash = eina_hash_int32_new(NULL);
        _drop_handlers_responsives = eina_hash_int32_new(NULL);

        E_LIST_HANDLER_APPEND(_event_handlers, ECORE_EVENT_MOUSE_BUTTON_UP, _e_dnd_cb_mouse_up, NULL);
        E_LIST_HANDLER_APPEND(_event_handlers, ECORE_EVENT_MOUSE_MOVE, _e_dnd_cb_mouse_move, NULL);
        E_LIST_HANDLER_APPEND(_event_handlers, ECORE_EVENT_KEY_DOWN, _e_dnd_cb_key_down, NULL);
        E_LIST_HANDLER_APPEND(_event_handlers, ECORE_EVENT_KEY_UP, _e_dnd_cb_key_up, NULL);
     }
   if (!e_comp_util_has_x()) return 1;
#ifndef HAVE_WAYLAND_ONLY
   if (_text_atom) return 1;
   _text_atom = ecore_x_atom_get("text/plain");
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_XDND_ENTER, _e_dnd_cb_event_dnd_enter, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_XDND_LEAVE, _e_dnd_cb_event_dnd_leave, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_XDND_POSITION, _e_dnd_cb_event_dnd_position, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_XDND_FINISHED, _e_dnd_cb_event_dnd_finished, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_XDND_DROP, _e_dnd_cb_event_dnd_drop, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_SELECTION_NOTIFY, _e_dnd_cb_event_dnd_selection, NULL);
   E_LIST_HANDLER_APPEND(_event_handlers, ECORE_X_EVENT_WINDOW_HIDE, _e_dnd_cb_event_hide, NULL);

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_drop_xdnd_register_set(e_comp->ee_win, 1);
   else
     e_drop_xdnd_register_set(e_comp->cm_selection, 1);

   _action = ECORE_X_ATOM_XDND_ACTION_PRIVATE;
#endif
   return 1;
}

EINTERN int
e_dnd_shutdown(void)
{
   E_FREE_LIST(_drag_list, e_object_del);

   _active_handlers = eina_list_free(_active_handlers);
   E_FREE_LIST(_drop_handlers, e_drop_handler_del);

   E_FREE_LIST(_event_handlers, ecore_event_handler_del);

   eina_hash_free(_drop_win_hash);

   eina_hash_free(_drop_handlers_responsives);

   eina_stringshare_del(_type_text_uri_list);
   eina_stringshare_del(_type_xds);
   eina_stringshare_del(_type_text_x_moz_url);
   eina_stringshare_del(_type_enlightenment_x_file);
   _type_text_uri_list = NULL;
   _type_xds = NULL;
   _type_text_x_moz_url = NULL;
   _type_enlightenment_x_file = NULL;
   _text_atom = 0;

   return 1;
}

E_API E_Drag *
e_drag_current_get(void)
{
   return _drag_current;
}

E_API E_Drag *
e_drag_new(int x, int y,
           const char **types, unsigned int num_types,
           void *data, int size,
           void *(*convert_cb)(E_Drag * drag, const char *type),
           void (*finished_cb)(E_Drag *drag, int dropped))
{
   E_Drag *drag;
   unsigned int i;

   drag = e_object_alloc(sizeof(E_Drag) + num_types * sizeof(char *),
                         E_DRAG_TYPE, E_OBJECT_CLEANUP_FUNC(_e_drag_free));
   if (!drag) return NULL;

   drag->x = x;
   drag->y = y;
   drag->w = 24;
   drag->h = 24;
   drag->layer = E_LAYER_CLIENT_DRAG;

   drag->evas = e_comp->evas;

   drag->type = E_DRAG_NONE;

   for (i = 0; i < num_types; i++)
     drag->types[i] = eina_stringshare_add(types[i]);
   drag->num_types = num_types;
   drag->data = data;
   drag->data_size = size;
   drag->cb.convert = convert_cb;
   drag->cb.finished = finished_cb;

   _drag_list = eina_list_append(_drag_list, drag);

#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     ecore_x_window_shadow_tree_flush();
#endif

   _drag_win_root = e_comp->root;

   drag->cb.key_down = NULL;
   drag->cb.key_up = NULL;

   return drag;
}

E_API Evas *
e_drag_evas_get(const E_Drag *drag)
{
   return drag->evas;
}

E_API void
e_drag_object_set(E_Drag *drag, Evas_Object *object)
{
   EINA_SAFETY_ON_NULL_RETURN(object);
   EINA_SAFETY_ON_TRUE_RETURN(!!drag->object);
   if (drag->visible)
     evas_object_show(object);
   else
     evas_object_hide(object);
   drag->object = object;
   drag->comp_object = e_comp_object_util_add(object, E_COMP_OBJECT_TYPE_NONE);
   evas_object_layer_set(drag->comp_object, drag->layer);
   evas_object_name_set(drag->comp_object, "E Drag");
   evas_object_pass_events_set(drag->comp_object, 1);
}

E_API void
e_drag_move(E_Drag *drag, int x, int y)
{
   if ((drag->x == x) && (drag->y == y)) return;
   drag->x = x;
   drag->y = y;
   if (_drag_current == drag)
     evas_object_move(drag->comp_object, x, y);
}

E_API void
e_drag_resize(E_Drag *drag, int w, int h)
{
   if ((drag->w == w) && (drag->h == h)) return;
   drag->h = h;
   drag->w = w;
   if (_drag_current == drag)
     evas_object_resize(drag->comp_object, w, h);
}

E_API int
e_dnd_active(void)
{
   return _drag_win != 0;
}

E_API int
e_drag_start(E_Drag *drag, int x, int y)
{
   return _e_drag_finalize(drag, E_DRAG_INTERNAL, x, y);
}

E_API int
e_drag_xdnd_start(E_Drag *drag, int x, int y)
{
   return _e_drag_finalize(drag, E_DRAG_XDND, x, y);
}

E_API void
e_drop_handler_xds_set(E_Drop_Handler *handler, Eina_Bool (*cb)(void *data, const char *type))
{
   handler->cb.xds = cb;
}

/* should only be used for windows */
E_API void
e_drop_xds_update(Eina_Bool enable, const char *value)
{
#ifndef HAVE_WAYLAND_ONLY
   Ecore_Window xwin;
   char buf[PATH_MAX + 8];
   char *file;
   int size;
   size_t len;

   if (!e_comp_util_has_x()) return;
   enable = !!enable;

   xwin = ecore_x_selection_owner_get(ECORE_X_ATOM_SELECTION_XDND);
   if (enable)
     {
        if (!ecore_x_window_prop_property_get(xwin, ECORE_X_ATOM_XDND_DIRECTSAVE0, _text_atom, 8, (unsigned char **)&file, &size))
          return;
        len = strlen(value);
        if (size + len + 8 + 1 > sizeof(buf))
          {
             free(file);
             return;
          }
        snprintf(buf, sizeof(buf), "file://%s/", value);
        strncat(buf, file, size);
        free(file);
        ecore_x_window_prop_property_set(xwin, ECORE_X_ATOM_XDND_DIRECTSAVE0, _text_atom, 8, (void *)buf, size + len + 8);
     }
   else
     ecore_x_window_prop_property_del(xwin, ECORE_X_ATOM_XDND_DIRECTSAVE0);
#else
   (void)enable;
   (void)value;
#endif
}

E_API E_Drop_Handler *
e_drop_handler_add(E_Object *obj, Evas_Object *win,
                   void *data,
                   void (*enter_cb)(void *data, const char *type, void *event),
                   void (*move_cb)(void *data, const char *type, void *event),
                   void (*leave_cb)(void *data, const char *type, void *event),
                   void (*drop_cb)(void *data, const char *type, void *event),
                   const char **types, unsigned int num_types, int x, int y, int w, int h)
{
   E_Drop_Handler *handler;
   unsigned int i;

   handler = calloc(1, sizeof(E_Drop_Handler) + num_types * sizeof(char *));
   if (!handler) return NULL;

   handler->cb.data = data;
   handler->cb.enter = enter_cb;
   handler->cb.move = move_cb;
   handler->cb.leave = leave_cb;
   handler->cb.drop = drop_cb;
   handler->num_types = num_types;
   for (i = 0; i < num_types; i++)
     handler->types[i] = eina_stringshare_add(types[i]);
   handler->x = x;
   handler->y = y;
   handler->w = w;
   handler->h = h;

   handler->obj = obj;
   handler->win = win;
   handler->entered = 0;

   _drop_handlers = eina_list_append(_drop_handlers, handler);

   return handler;
}

E_API void
e_drop_handler_geometry_set(E_Drop_Handler *handler, int x, int y, int w, int h)
{
   handler->x = x;
   handler->y = y;
   handler->w = w;
   handler->h = h;
}

E_API int
e_drop_inside(const E_Drop_Handler *handler, int x, int y)
{
   int dx, dy;

   _e_drag_coords_update(handler, &dx, &dy);
   x -= dx;
   y -= dy;
   return E_INSIDE(x, y, handler->x, handler->y, handler->w, handler->h);
}

E_API void
e_drop_handler_del(E_Drop_Handler *handler)
{
   unsigned int i;
   Eina_List *l;
   Ecore_Window hwin;

   if (!handler)
     return;

   hwin = _e_drag_win_get(handler, 1);
   if (hwin)
     {
        l = eina_hash_find(_drop_handlers_responsives, &hwin);
        if (l)
          eina_hash_set(_drop_handlers_responsives, &hwin, eina_list_remove(l, handler));
     }
   _drop_handlers = eina_list_remove(_drop_handlers, handler);
   if (handler->active)
     _active_handlers = eina_list_remove(_active_handlers, handler);
   for (i = 0; i < handler->num_types; i++)
     eina_stringshare_del(handler->types[i]);
   eina_stringshare_del(handler->active_type);
   free(handler);
}

E_API int
e_drop_xdnd_register_set(Ecore_Window win, int reg)
{
   if (!e_comp_util_has_x()) return 0;
   if (reg)
     {
        if (!eina_hash_find(_drop_win_hash, &win))
          {
#ifndef HAVE_WAYLAND_ONLY
             ecore_x_dnd_aware_set(win, 1);
#endif
             eina_hash_add(_drop_win_hash, &win, (void *)1);
          }
     }
   else
     {
#ifndef HAVE_WAYLAND_ONLY
        ecore_x_dnd_aware_set(win, 0);
#endif
        eina_hash_del(_drop_win_hash, &win, (void *)1);
     }
   return 1;
}

E_API void
e_drop_handler_responsive_set(E_Drop_Handler *handler)
{
   Ecore_Window hwin = _e_drag_win_get(handler, 1);
   Eina_List *l;

   l = eina_hash_find(_drop_handlers_responsives, &hwin);
   eina_hash_set(_drop_handlers_responsives, &hwin, eina_list_append(l, handler));
}

E_API int
e_drop_handler_responsive_get(const E_Drop_Handler *handler)
{
   Ecore_Window hwin = _e_drag_win_get(handler, 1);
   Eina_List *l;

   l = eina_hash_find(_drop_handlers_responsives, &hwin);
   return l && eina_list_data_find(l, handler);
}

E_API void
e_drop_handler_action_set(unsigned int action)
{
   _action = action;
}

E_API unsigned int
e_drop_handler_action_get(void)
{
   return _action;
}

E_API void
e_drag_key_down_cb_set(E_Drag *drag, void (*func)(E_Drag *drag, Ecore_Event_Key *e))
{
   drag->cb.key_down = func;
}

E_API void
e_drag_key_up_cb_set(E_Drag *drag, void (*func)(E_Drag *drag, Ecore_Event_Key *e))
{
   drag->cb.key_up = func;
}

/* from ecore_x_selection.c */
E_API Eina_List *
e_dnd_util_text_uri_list_convert(char *data, int size)
{
   char *tmp;
   int i, is;
   Eina_List *ret = NULL;

   if ((!data) || (!size)) return NULL;
   tmp = malloc(size);
   is = i = 0;
   while ((is < size) && (data[is]))
     {
        if ((i == 0) && (data[is] == '#'))
          for (; ((data[is]) && (data[is] != '\n')); is++) ;
        else
          {
             if ((data[is] != '\r') &&
                 (data[is] != '\n'))
               tmp[i++] = data[is++];
             else
               {
                  while ((data[is] == '\r') || (data[is] == '\n'))
                    is++;
                  tmp[i] = 0;
                  ret = eina_list_append(ret, strdup(tmp));
                  tmp[0] = 0;
                  i = 0;
               }
          }
     }
   if (i > 0)
     {
        tmp[i] = 0;
        ret = eina_list_append(ret, strdup(tmp));
     }

   free(tmp);

   return ret;
}

/* local subsystem functions */

static Eina_Stringshare *
_e_dnd_type_implemented(const char *type)
{
   const char ***t;

   for (t = _e_dnd_types; *t; t++)
     {
        if (!strcmp(type, **t))
          return **t;
     }
   return NULL;
}

static void
_e_drag_move(E_Drag *drag, int x, int y)
{
   E_Zone *zone;

   if (((drag->x + drag->dx) == x) && ((drag->y + drag->dy) == y)) return;

   zone = e_comp_zone_xy_get(x, y);
   if (zone) e_zone_flip_coords_handle(zone, x, y);

   drag->x = x - drag->dx;
   drag->y = y - drag->dy;
   evas_object_move(drag->comp_object, drag->x, drag->y);
}

static void
_e_drag_coords_update(const E_Drop_Handler *h, int *dx, int *dy)
{
   int px = 0, py = 0;

   *dx = 0;
   *dy = 0;
   if (h->win)
     {
        E_Client *ec;

        ec = e_win_client_get(h->win);
        px = ec->x;
        py = ec->y;
     }
   else if (h->obj)
     {
        switch (h->obj->type)
          {
             E_Gadcon *gc;

           case E_GADCON_TYPE:
             gc = (E_Gadcon *)h->obj;
             if (!gc->toolbar) return;
             evas_object_geometry_get(gc->toolbar->fwin, &px, &py, NULL, NULL);
             break;

           case E_GADCON_CLIENT_TYPE:
             gc = ((E_Gadcon_Client *)(void *)(h->obj))->gadcon;
             e_gadcon_canvas_zone_geometry_get(gc, &px, &py, NULL, NULL);
             if (!gc->toolbar) break;
             {
                int x, y;

                evas_object_geometry_get(gc->toolbar->fwin, &x, &y, NULL, NULL);
                px += x, py += y;
             }
             break;

           case E_ZONE_TYPE:
// zone based drag targets are in a comp thus their coords should be
// screen-relative as containers just cover the screen
//	     px = ((E_Zone *)(h->obj))->x;
//	     py = ((E_Zone *)(h->obj))->y;
             break;

           case E_CLIENT_TYPE:
             px = ((E_Client *)(void *)(h->obj))->x;
             py = ((E_Client *)(void *)(h->obj))->y;
             break;

           /* FIXME: add more types as needed */
           default:
             break;
          }
     }
   *dx += px;
   *dy += py;
}

static Ecore_Window
_e_drag_win_get(const E_Drop_Handler *h, int xdnd)
{
   Ecore_Window hwin = 0;

   if (h->win)
     return elm_win_window_id_get(h->win);
   if (h->obj)
     {
        E_Gadcon *gc = NULL;

        switch (h->obj->type)
          {
           case E_GADCON_CLIENT_TYPE:
             gc = ((E_Gadcon_Client *)(void *)(h->obj))->gadcon;
             if (!gc) return 0;
             EINA_FALLTHROUGH;
             /* no break */

           case E_GADCON_TYPE:
             if (!gc) gc = (E_Gadcon *)h->obj;

             if (gc->toolbar) hwin = e_client_util_pwin_get(e_win_client_get(gc->toolbar->fwin)); //double check for xdnd...
             else
               {
                  if (xdnd) hwin = e_gadcon_xdnd_window_get(gc);
                  else hwin = e_gadcon_dnd_window_get(gc);
               }
             break;

           case E_CLIENT_TYPE:
           case E_ZONE_TYPE:
           default:
             /* protect against crashes during shutdown */
             if (e_comp)
               hwin = e_comp->ee_win;
             break;
          }
     }

   return hwin;
}

static int
_e_drag_win_matches(E_Drop_Handler *h, Ecore_Window win, int xdnd)
{
   Ecore_Window hwin = _e_drag_win_get(h, xdnd);

   if (win == hwin) return 1;
   return 0;
}

static void
_e_drag_win_show(E_Drop_Handler *h)
{
   E_Shelf *shelf;

   if (h->win) return;
   if (h->obj)
     {
        switch (h->obj->type)
          {
           case E_GADCON_TYPE:
             shelf = e_gadcon_shelf_get((E_Gadcon *)(h->obj));
             if (shelf) e_shelf_toggle(shelf, 1);
             break;

           case E_GADCON_CLIENT_TYPE:
             shelf = e_gadcon_shelf_get(((E_Gadcon_Client *)(void *)(h->obj))->gadcon);
             if (shelf) e_shelf_toggle(shelf, 1);
             break;

           /* FIXME: add more types as needed */
           default:
             break;
          }
     }
}

static void
_e_drag_win_hide(E_Drop_Handler *h)
{
   E_Shelf *shelf;

   if (h->win) return;
   if (h->obj)
     {
        switch (h->obj->type)
          {
           case E_GADCON_TYPE:
             shelf = e_gadcon_shelf_get((E_Gadcon *)(h->obj));
             if (shelf) e_shelf_toggle(shelf, 0);
             break;

           case E_GADCON_CLIENT_TYPE:
             shelf = e_gadcon_shelf_get(((E_Gadcon_Client *)(void *)(h->obj))->gadcon);
             if (shelf) e_shelf_toggle(shelf, 0);
             break;

           /* FIXME: add more types as needed */
           default:
             break;
          }
     }
}

static unsigned int
_e_dnd_object_layer_get(E_Drop_Handler *h)
{
   unsigned int adjust = 0;
   E_Object *obj = h->obj;

   if (h->base) return evas_object_layer_get(h->base);
   if (!obj) return 0;
   if (h->win)
     obj = (E_Object*)e_win_client_get(h->win);
   switch (obj->type)
     {
      case E_GADCON_CLIENT_TYPE:
        /* add 1 to ensure we're above a potential receiving gadcon */
        adjust = 1;
        EINA_FALLTHROUGH;
        /* no break */

      default:
        adjust += e_comp_e_object_layer_get(obj);
     }
   return adjust;
}

static Ecore_Window
_dnd_top_window_at_xy_get(Evas_Coord x, Evas_Coord y)
{
   E_Client *ec;
   Eina_List *objs, *l;
   Evas_Object *o;

   if (_drag_current->type == E_DRAG_INTERNAL)
     return e_comp_top_window_at_xy_get(x, y);
   objs = evas_objects_at_xy_get(e_comp->evas, x, y, 0, 0);
   if (!objs) return e_comp->ee_win;
   EINA_LIST_FOREACH(objs, l, o)
     {
        ec = evas_object_data_get(o, "E_Client");
        if (ec)
          {
             eina_list_free(objs);
             return e_client_util_pwin_get(ec);
          }
     }
   eina_list_free(objs);
   return e_comp->ee_win;
}

static int
_e_drag_update(Ecore_Window root, int x, int y, unsigned int action)
{
   const Eina_List *l;
   Eina_List *entered = NULL;
   E_Event_Dnd_Enter enter_ev;
   E_Event_Dnd_Move move_ev;
   E_Event_Dnd_Leave leave_ev;
   E_Drop_Handler *h, *top = NULL;
   unsigned int top_layer = 0;
   int dx, dy;
   Ecore_Window win;
   int responsive = 0;

//   double t1 = ecore_time_get(); ////
   if (_drag_current && !_xdnd)
     win = _dnd_top_window_at_xy_get(x, y);
   else
     win = root;

   if (_drag_current)
     {
        if (_drag_current->ended) return 0;
        if (_drag_current->visible) evas_object_show(_drag_current->comp_object);
        else evas_object_hide(_drag_current->comp_object);
        _e_drag_move(_drag_current, x, y);
     }
   EINA_LIST_FOREACH(_active_handlers, l, h)
     {
        _e_drag_coords_update(h, &dx, &dy);
        enter_ev.x = x - dx;
        enter_ev.y = y - dy;
        enter_ev.data = NULL;
        enter_ev.action = action;
        move_ev.x = x - dx;
        move_ev.y = y - dy;
        move_ev.action = action;
        leave_ev.x = x - dx;
        leave_ev.y = y - dy;

        if (E_INSIDE(enter_ev.x, enter_ev.y, h->x, h->y, h->w, h->h) &&
            ((!_drag_current) || _e_drag_win_matches(h, win, 0)))
          entered = eina_list_append(entered, h);
        else
          {
             if (h->entered)
               {
                  if (h->cb.leave)
                    h->cb.leave(h->cb.data, h->active_type, &leave_ev);
                  if (_drag_current)
                    _e_drag_win_hide(h);
                  h->entered = 0;
               }
          }
     }
   if (!entered) return 0;

   EINA_LIST_FREE(entered, h)
     {
        unsigned int layer;
        E_Drop_Handler *h2;

        _e_drag_coords_update(h, &dx, &dy);
        leave_ev.x = x - dx;
        leave_ev.y = y - dy;

        layer = _e_dnd_object_layer_get(h);
        if (!top)
          {
             top = h;
             top_layer = layer;
             enter_ev.x = x - dx;
             enter_ev.y = y - dy;
             enter_ev.data = NULL;
             enter_ev.action = action;
             move_ev.x = x - dx;
             move_ev.y = y - dy;
             move_ev.action = action;
             continue;
          }
        if (layer > top_layer)
          {
             h2 = top, top = h, h = h2;
             enter_ev.x = x - dx;
             enter_ev.y = y - dy;
             enter_ev.data = NULL;
             enter_ev.action = action;
             move_ev.x = x - dx;
             move_ev.y = y - dy;
             move_ev.action = action;
          }
        if (h == top) continue;
        if (h->entered)
          {
             if (h->cb.leave)
               h->cb.leave(h->cb.data, h->active_type, &leave_ev);
             if (_drag_current)
               _e_drag_win_hide(h);
             h->entered = 0;
          }
     }
   responsive = !!e_drop_handler_responsive_get(top);
   if (!top->entered)
     {
        _e_drag_win_show(top);
        if (top->cb.enter)
          {
             if (_drag_current)
               {
                  if (_drag_current->cb.convert)
                    {
                       enter_ev.data = _drag_current->cb.convert(_drag_current,
                                                                 top->active_type);
                    }
                  else
                    enter_ev.data = _drag_current->data;
               }
             top->cb.enter(top->cb.data, top->active_type, &enter_ev);
          }
        top->entered = 1;
     }
   if (top->cb.move)
     top->cb.move(top->cb.data, top->active_type, &move_ev);
   return responsive;
//   double t2 = ecore_time_get() - t1; ////
//   printf("DND UPDATE %3.7f\n", t2); ////
}

static Eina_Bool
_drag_timeout(void *data)
{
   E_Drag *drag = data;

   drag->timeout = NULL;
   ERR("Drop finished response timeout...");
   e_object_del(E_OBJECT(drag));
   return EINA_FALSE;
}

static void
_e_drag_end(int x, int y)
{
   E_Zone *zone;
   const Eina_List *l;
   E_Event_Dnd_Drop ev;
   int dx, dy;
   Ecore_Window win;
   E_Drop_Handler *h;
   int dropped = 0;

   if (!_drag_current) return;
   win = _dnd_top_window_at_xy_get(x, y);
   zone = e_comp_zone_xy_get(x, y);
   /* Pass -1, -1, so that it is possible to drop at the edge. */
   if (zone) e_zone_flip_coords_handle(zone, -1, -1);

   evas_object_hide(_drag_current->comp_object);

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_grabinput_release(_drag_win, _drag_win);

   while (_drag_current->type == E_DRAG_XDND)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          {
             if (!(dropped = ecore_x_dnd_drop()))
               break;
             else
               drop_win = win;
          }
        else
#endif
          if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
            break;
        if (_drag_current->cb.finished)
          _drag_current->cb.finished(_drag_current, dropped);
        _drag_current->cb.finished = NULL;
        _drag_current->ended = 1;
        _drag_current->timeout = ecore_timer_add(2.0, _drag_timeout, _drag_current);
        return;
     }

   dropped = 0;
   if (!_drag_current->data)
     {
        /* Just leave */
        E_Event_Dnd_Leave leave_ev;

        leave_ev.x = x;
        leave_ev.y = y;

        EINA_LIST_FOREACH(_active_handlers, l, h)
          {
             if (h->entered)
               {
                  if (h->cb.leave)
                    h->cb.leave(h->cb.data, h->active_type, &leave_ev);
                  h->entered = 0;
               }
          }
     }

   EINA_LIST_FOREACH(_active_handlers, l, h)
     {
        if (!h->entered) continue;
        _e_drag_coords_update(h, &dx, &dy);
        ev.x = x - dx;
        ev.y = y - dy;
        if ((_e_drag_win_matches(h, win, 0)) &&
            ((h->cb.drop) && (E_INSIDE(ev.x, ev.y, h->x, h->y, h->w, h->h))))
          {
             Eina_Bool need_free = EINA_FALSE;
             Eina_List *list;

             if (_drag_current->cb.convert)
               {
                  ev.data = _drag_current->cb.convert(_drag_current,
                                                      h->active_type);
               }
             else
               {
                  unsigned int i;

                  for (i = 0; i < _drag_current->num_types; i++)
                    if (_drag_current->types[i] == _type_text_uri_list)
                      {
                         char *data = _drag_current->data;
                         int size = _drag_current->data_size;

                         if (data && data[size - 1])
                           {
                              /* Isn't nul terminated */
                              size++;
                              data = realloc(data, size);
                              data[size - 1] = 0;
                           }
                         _drag_current->data = data;
                         _drag_current->data_size = size;
                         ev.data = e_dnd_util_text_uri_list_convert(_drag_current->data, _drag_current->data_size);
                         need_free = EINA_TRUE;
                         break;
                      }
                  if (!need_free)
                    ev.data = _drag_current->data;
               }
             h->cb.drop(h->cb.data, h->active_type, &ev);
             list = ev.data;
             if (need_free) E_FREE_LIST(list, free);
             dropped = 1;
          }
        h->entered = 0;
        if (dropped) break;
     }
   if (_drag_current->cb.finished)
     _drag_current->cb.finished(_drag_current, dropped);
   _drag_current->cb.finished = NULL;

   e_object_del(E_OBJECT(_drag_current));
}

static void
_e_drag_xdnd_end(Ecore_Window win, int x, int y)
{
   const Eina_List *l;
   E_Event_Dnd_Drop ev;
   int dx, dy;

   if (!_xdnd) return;

   ev.data = _xdnd->data;

   if (ev.data)
     {
        E_Drop_Handler *h;

        EINA_LIST_FOREACH(_active_handlers, l, h)
          {
             _e_drag_coords_update(h, &dx, &dy);
             ev.x = x - dx;
             ev.y = y - dy;
             if (_e_drag_win_matches(h, win, 1) && h->cb.drop
                 && E_INSIDE(ev.x, ev.y, h->x, h->y, h->w, h->h))
               {
                  h->cb.drop(h->cb.data, h->active_type, &ev);
               }
          }
     }
   if (_drag_current) e_object_del(E_OBJECT(_drag_current));
}

static void
_e_drag_free(E_Drag *drag)
{
   unsigned int i;

   if (drag->timeout)
     {
        ecore_timer_del(drag->timeout);
        drag->timeout = NULL;
     }
   if (drag == _drag_current)
     {
        E_Event_Dnd_Leave leave_ev;
        E_Drop_Handler *h;

        e_grabinput_release(_drag_win, _drag_win);
        _drag_win_root = 0;

        leave_ev.x = 0;
        leave_ev.y = 0;
        EINA_LIST_FREE(_active_handlers, h)
          {
             if (h->entered)
               {
                  if (h->cb.leave)
                    h->cb.leave(h->cb.data, h->active_type, &leave_ev);
                  _e_drag_win_hide(h);
               }
             h->active = 0;
          }
        if (drag->cb.finished)
          drag->cb.finished(drag, 0);
        drag->cb.finished = NULL;
        drop_win = 0;
     }

   _drag_current = NULL;

   _drag_list = eina_list_remove(_drag_list, drag);

   evas_object_hide(drag->comp_object);
   E_FREE_FUNC(drag->comp_object, evas_object_del);
   for (i = 0; i < drag->num_types; i++)
     eina_stringshare_del(drag->types[i]);
   free(drag);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        ecore_event_window_unregister(_drag_win);
        if (_drag_win != e_comp->ee_win)
          ecore_x_window_free(_drag_win);
        ecore_x_window_shadow_tree_flush();
     }
   else
#endif
     {
        e_comp_ungrab_input(1, 1);
     }
   e_bindings_disabled_set(0);
   _drag_win = 0;
}

static Eina_Bool
_e_dnd_cb_key_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Key *ev = event;

   if (ev->window != _drag_win) return ECORE_CALLBACK_PASS_ON;

   if (!_drag_current) return ECORE_CALLBACK_PASS_ON;

   if (_drag_current->cb.key_down)
     _drag_current->cb.key_down(_drag_current, ev);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_key_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Key *ev = event;

   if (ev->window != _drag_win) return ECORE_CALLBACK_PASS_ON;

   if (!_drag_current) return ECORE_CALLBACK_PASS_ON;

   if (_drag_current->cb.key_up)
     _drag_current->cb.key_up(_drag_current, ev);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Button *ev = event;

   if (ev->window != _drag_win) return ECORE_CALLBACK_PASS_ON;

   if (_drag_current && _drag_current->button_mask)
     {
        _drag_current->button_mask &= ~(1 << (ev->buttons - 1));
        if (_drag_current->button_mask) return ECORE_CALLBACK_RENEW;
     }
   _e_drag_end(ev->x, ev->y);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_mouse_move(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Move *ev = event;

   if (ev->window != _drag_win) return ECORE_CALLBACK_PASS_ON;

#ifndef HAVE_WAYLAND_ONLY
   if (!_xdnd)
     _e_drag_update(_drag_win_root, ev->x, ev->y,
                    _action ?: ECORE_X_ATOM_XDND_ACTION_PRIVATE);
# ifdef HAVE_WAYLAND
   if (e_comp_util_has_xwayland())
     {
        if (e_comp_wl->drag != _drag_current) return ECORE_CALLBACK_RENEW;
        if (!e_comp_wl->ptr.ec) return ECORE_CALLBACK_RENEW;
        if (!e_client_has_xwindow(e_comp_wl->ptr.ec)) return ECORE_CALLBACK_RENEW;
        if (e_client_has_xwindow(e_comp_wl->drag_client)) return ECORE_CALLBACK_RENEW;
        ecore_x_client_message32_send(e_client_util_win_get(e_comp_wl->ptr.ec),
          ECORE_X_ATOM_XDND_POSITION, ECORE_X_EVENT_MASK_NONE,
          e_comp->cm_selection, 0, ((ev->x << 16) & 0xffff0000) | (ev->y & 0xffff),
          ev->timestamp, ECORE_X_ATOM_XDND_ACTION_COPY);
     }
# endif
#endif

   return ECORE_CALLBACK_PASS_ON;
}

#ifndef HAVE_WAYLAND_ONLY
static Eina_Bool
_e_dnd_cb_event_dnd_enter(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Xdnd_Enter *ev = event;
   E_Drop_Handler *h;
   const Eina_List *l;
   int i;

   if (!eina_hash_find(_drop_win_hash, &ev->win)) return ECORE_CALLBACK_PASS_ON;

   EINA_LIST_FREE(_active_handlers, h)
     {
        h->active = 0;
        eina_stringshare_replace(&h->active_type, NULL);
        h->entered = 0;
     }
   for (i = 0; i < ev->num_types; i++)
     {
        Eina_Stringshare *t;

        t = eina_stringshare_ref(_e_dnd_type_implemented(ev->types[i]));
        if (!t) continue;
        _xdnd = E_NEW(XDnd, 1);
        _xdnd->type = t;
        EINA_LIST_FOREACH(_drop_handlers, l, h)
          {
             _e_drop_handler_active_check(h, NULL, _xdnd->type);
             if (h->active)
               _active_handlers = eina_list_append(_active_handlers, h);
             h->entered = 0;
          }
        break;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_event_dnd_leave(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Xdnd_Leave *ev = event;
   E_Event_Dnd_Leave leave_ev;
   const Eina_List *l;

   if (!eina_hash_find(_drop_win_hash, &ev->win)) return ECORE_CALLBACK_PASS_ON;

   leave_ev.x = 0;
   leave_ev.y = 0;

   if (_xdnd)
     {
        E_Drop_Handler *h;

        EINA_LIST_FOREACH(_active_handlers, l, h)
          {
             if (h->entered)
               {
                  if (h->cb.leave)
                    h->cb.leave(h->cb.data, h->active_type, &leave_ev);
                  h->entered = 0;
               }
          }

        eina_stringshare_del(_xdnd->type);
        E_FREE(_xdnd);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_event_hide(void *data EINA_UNUSED, int type EINA_UNUSED, Ecore_X_Event_Window_Hide *ev)
{
   E_Event_Dnd_Leave leave_ev;
   const char *id;
   const Eina_List *l;

   id = e_util_winid_str_get(ev->win);
   if (!eina_hash_find(_drop_win_hash, id))
     {
        if (_drag_current && _drag_current->ended && (drop_win == ev->win))
          e_object_del(E_OBJECT(_drag_current));
        return ECORE_CALLBACK_RENEW;
     }
   leave_ev.x = 0;
   leave_ev.y = 0;

   if (_xdnd)
     {
        unsigned int entered = 0;
        E_Drop_Handler *h;

        EINA_LIST_FOREACH(_active_handlers, l, h)
          {
             if (h->entered && (_e_drag_win_get(h, 1) == ev->win))
               {
                  if (h->cb.leave)
                    h->cb.leave(h->cb.data, h->active_type, &leave_ev);
                  h->entered = 0;
               }
             entered += h->entered;
          }

        if (!entered)
          {
             eina_stringshare_del(_xdnd->type);
             E_FREE(_xdnd);
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_dnd_cb_event_dnd_position(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Xdnd_Position *ev = event;
   Ecore_X_Rectangle rect;
   int responsive;

//   double t1 = ecore_time_get(); ////
   if (!eina_hash_find(_drop_win_hash, &ev->win))
     {
//	double t2 = ecore_time_get() - t1; ////
//	printf("DND POS EV 1 %3.7f\n", t2); ////
        return ECORE_CALLBACK_PASS_ON;
     }

   rect.x = 0;
   rect.y = 0;
   rect.width = 0;
   rect.height = 0;

   if (!_active_handlers)
     ecore_x_dnd_send_status(0, 0, rect, ECORE_X_DND_ACTION_PRIVATE);
   else
     {
        responsive = _e_drag_update(ev->win, ev->position.x, ev->position.y, ev->action);
        if (responsive)
          ecore_x_dnd_send_status(1, 0, rect, _action);
        else
          ecore_x_dnd_send_status(1, 0, rect, ECORE_X_ATOM_XDND_ACTION_PRIVATE);
     }
//   double t2 = ecore_time_get() - t1; ////
//   printf("DND POS EV 2 %3.7f\n", t2); ////
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_event_dnd_finished(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
/*
 * this is broken since the completed flag doesn't tell us anything with current
 * ecore-x and results in never-ending dnd operation which breaks the window
 * 18 September 2012
 * BORKER CERTIFICATION: BRONZE
 * -discomfitor
   Ecore_X_Event_Xdnd_Finished *ev;

   ev = event;

   if (!ev->completed) return ECORE_CALLBACK_PASS_ON;
 */

   if (_drag_current && (!_xdnd))
     e_object_del(E_OBJECT(_drag_current));
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_event_dnd_drop(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Xdnd_Drop *ev = event;

   if (!eina_hash_find(_drop_win_hash, &ev->win)) return ECORE_CALLBACK_PASS_ON;

   if (_xdnd)
     {
        E_Drop_Handler *h;
        Eina_Bool req = EINA_TRUE;
        Eina_List *l;

        EINA_LIST_FOREACH(_active_handlers, l, h)
          {
             if (_e_drag_win_matches(h, ev->win, 1) && h->entered && h->cb.xds)
               {
                  req = h->cb.xds(h->cb.data, _xdnd->type);
               }
          }
        if (req) ecore_x_selection_xdnd_request(ev->win, _xdnd->type);

        _xdnd->x = ev->position.x;
        _xdnd->y = ev->position.y;
        if (!req)
          {
             _e_drag_xdnd_end(ev->win, _xdnd->x, _xdnd->y);
             ecore_x_dnd_send_finished();
             eina_stringshare_del(_xdnd->type);
             E_FREE(_xdnd);
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dnd_cb_event_dnd_selection(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Selection_Notify *ev = event;
   int i;

   if (!eina_hash_find(_drop_win_hash, &ev->win)) return ECORE_CALLBACK_PASS_ON;
   if (ev->selection != ECORE_X_SELECTION_XDND) return ECORE_CALLBACK_PASS_ON;
   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return ECORE_CALLBACK_RENEW;

   if (!_xdnd)
     {
        /* something crazy happened */
        ecore_x_dnd_send_finished();
        return ECORE_CALLBACK_RENEW;
     }

   if (_type_text_uri_list == _xdnd->type)
     {
        Ecore_X_Selection_Data_Files *files;
        Eina_List *l = NULL;

        files = ev->data;
        for (i = 0; i < files->num_files; i++)
          {
             /* TODO: Check if hostname is in file:// uri */
             /* if (!strncmp(files->files[i], "file://", 7)) */
             /*   l = eina_list_append(l, files->files[i]); */
             /* TODO: download files
                else if (!strncmp(files->files[i], "http://", 7))
                else if (!strncmp(files->files[i], "ftp://", 6))
              */
             /* else */
             l = eina_list_append(l, files->files[i]);
          }
        _xdnd->data = l;
        _e_drag_xdnd_end(ev->win, _xdnd->x, _xdnd->y);
        eina_list_free(l);
     }
   else if (_type_text_x_moz_url == _xdnd->type)
     {
        Ecore_X_Selection_Data_X_Moz_Url *sel;
        E_Dnd_X_Moz_Url moz;

        sel = ev->data;
        moz.links = sel->links;
        moz.link_names = sel->link_names;
        _xdnd->data = &moz;
        _e_drag_xdnd_end(ev->win, _xdnd->x, _xdnd->y);
     }
   else
     _e_drag_xdnd_end(ev->win, _xdnd->x, _xdnd->y);
   /* FIXME: When to execute this? It could be executed in ecore_x after getting
    * the drop property... */
   ecore_x_dnd_send_finished();
   eina_stringshare_del(_xdnd->type);
   E_FREE(_xdnd);
   return ECORE_CALLBACK_PASS_ON;
}
#endif

#include "e.h"

static Ecore_Event_Handler *_e_dpms_handler_config_mode = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_fullscreen = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_unfullscreen = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_remove = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_iconify = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_uniconify = NULL;
static Ecore_Event_Handler *_e_dpms_handler_border_desk_set = NULL;
static Ecore_Event_Handler *_e_dpms_handler_desk_show = NULL;

static unsigned int _e_dpms_timeout_standby = 0;
static unsigned int _e_dpms_timeout_suspend = 0;
static unsigned int _e_dpms_timeout_off = 0;
static int _e_dpms_enabled = EINA_FALSE;

#ifdef HAVE_WAYLAND
static Eina_List *handlers;
static Ecore_Timer *standby_timer;
static Ecore_Timer *suspend_timer;
static Ecore_Timer *off_timer;
#endif

#define STANDBY 10
#define SUSPEND 11
#define OFF 12

E_API void
e_dpms_update(void)
{
   unsigned int standby = 0, suspend = 0, off = 0;
   int enabled;
   Eina_Bool changed = EINA_FALSE;

   enabled = ((e_config->screensaver_enable) &&
              (!((e_util_fullscreen_any()) &&
                 (e_config->no_dpms_on_fullscreen))));
   if (_e_dpms_enabled != enabled)
     {
        _e_dpms_enabled = enabled;
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          {
             if (!e_config->screensaver_dpms_off)
               ecore_x_dpms_enabled_set(enabled);
             else
               ecore_x_dpms_enabled_set(0);
          }
#endif
     }
   if (!enabled) return;

   if (e_config->screensaver_enable)
     {
        off = suspend = standby = e_screensaver_timeout_get(EINA_FALSE);
        standby += STANDBY;
        suspend += SUSPEND;
        off += OFF;
     }
   if (_e_dpms_timeout_standby != standby)
     {
        _e_dpms_timeout_standby = standby;
        changed = EINA_TRUE;
     }
   if (_e_dpms_timeout_suspend != suspend)
     {
        _e_dpms_timeout_suspend = suspend;
        changed = EINA_TRUE;
     }
   if (_e_dpms_timeout_off != off)
     {
        _e_dpms_timeout_off = off;
        changed = EINA_TRUE;
     }
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        if (changed) ecore_x_dpms_timeouts_set(standby, suspend, off);
     }
#endif
}

E_API void
e_dpms_force_update(void)
{
   unsigned int standby = 0, suspend = 0, off = 0;
   int enabled;

   enabled = (e_config->screensaver_enable);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        if (!e_config->screensaver_dpms_off)
          ecore_x_dpms_enabled_set(enabled);
        else
          ecore_x_dpms_enabled_set(0);
     }
#endif
   if (!enabled) return;

   if (e_config->screensaver_enable)
     {
        off = suspend = standby = e_screensaver_timeout_get(EINA_FALSE);
        standby += STANDBY;
        suspend += SUSPEND;
        off += OFF;
     }
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type != E_PIXMAP_TYPE_X) return;
   ecore_x_dpms_timeouts_set(standby + 10, suspend + 10, off + 10);
   ecore_x_dpms_timeouts_set(standby, suspend, off);
#endif
}

static Eina_Bool
_e_dpms_handler_config_mode_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_dpms_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dpms_handler_border_fullscreen_check_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (e_config->no_dpms_on_fullscreen) e_dpms_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dpms_handler_border_desk_set_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (e_config->no_dpms_on_fullscreen) e_dpms_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_dpms_handler_desk_show_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (e_config->no_dpms_on_fullscreen) e_dpms_update();
   return ECORE_CALLBACK_PASS_ON;
}

#ifdef HAVE_WAYLAND
static Eina_Bool
_e_dpms_standby(void *d EINA_UNUSED)
{
   if (e_comp->screen && e_comp->screen->dpms)
     e_comp->screen->dpms(1);
   standby_timer = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_dpms_suspend(void *d EINA_UNUSED)
{
   if (e_comp->screen && e_comp->screen->dpms)
     e_comp->screen->dpms(2);
   suspend_timer = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_dpms_off(void *d EINA_UNUSED)
{
   if (e_comp->screen && e_comp->screen->dpms)
     e_comp->screen->dpms(3);
   off_timer = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_dpms_screensaver_on()
{
   standby_timer = ecore_timer_loop_add(STANDBY, _e_dpms_standby, NULL);
   suspend_timer = ecore_timer_loop_add(SUSPEND, _e_dpms_suspend, NULL);
   off_timer = ecore_timer_loop_add(OFF, _e_dpms_off, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_dpms_screensaver_off()
{
   E_FREE_FUNC(standby_timer, ecore_timer_del);
   E_FREE_FUNC(suspend_timer, ecore_timer_del);
   E_FREE_FUNC(off_timer, ecore_timer_del);
   if (e_comp->screen && e_comp->screen->dpms)
     e_comp->screen->dpms(0);
   return ECORE_CALLBACK_RENEW;
}
#endif

EINTERN int
e_dpms_init(void)
{
   _e_dpms_handler_config_mode = ecore_event_handler_add
       (E_EVENT_CONFIG_MODE_CHANGED, _e_dpms_handler_config_mode_cb, NULL);

   _e_dpms_handler_border_fullscreen = ecore_event_handler_add
       (E_EVENT_CLIENT_FULLSCREEN, _e_dpms_handler_border_fullscreen_check_cb, NULL);

   _e_dpms_handler_border_unfullscreen = ecore_event_handler_add
       (E_EVENT_CLIENT_UNFULLSCREEN, _e_dpms_handler_border_fullscreen_check_cb, NULL);

   _e_dpms_handler_border_remove = ecore_event_handler_add
       (E_EVENT_CLIENT_REMOVE, _e_dpms_handler_border_fullscreen_check_cb, NULL);

   _e_dpms_handler_border_iconify = ecore_event_handler_add
       (E_EVENT_CLIENT_ICONIFY, _e_dpms_handler_border_fullscreen_check_cb, NULL);

   _e_dpms_handler_border_uniconify = ecore_event_handler_add
       (E_EVENT_CLIENT_UNICONIFY, _e_dpms_handler_border_fullscreen_check_cb, NULL);

   _e_dpms_handler_border_desk_set = ecore_event_handler_add
       (E_EVENT_CLIENT_DESK_SET, _e_dpms_handler_border_desk_set_cb, NULL);

   _e_dpms_handler_desk_show = ecore_event_handler_add
       (E_EVENT_DESK_SHOW, _e_dpms_handler_desk_show_cb, NULL);

#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _e_dpms_enabled = ecore_x_dpms_enabled_get();
        ecore_x_dpms_timeouts_get
          (&_e_dpms_timeout_standby, &_e_dpms_timeout_suspend, &_e_dpms_timeout_off);
        e_dpms_force_update();
     }
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type != E_PIXMAP_TYPE_X)
     {
        E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_ON,
                              _e_dpms_screensaver_on, NULL);
        E_LIST_HANDLER_APPEND(handlers, E_EVENT_SCREENSAVER_OFF_PRE,
                              _e_dpms_screensaver_off, NULL);
     }
#endif

   return 1;
}

EINTERN int
e_dpms_shutdown(void)
{
   if (_e_dpms_handler_config_mode)
     {
        ecore_event_handler_del(_e_dpms_handler_config_mode);
        _e_dpms_handler_config_mode = NULL;
     }

   if (_e_dpms_handler_border_fullscreen)
     {
        ecore_event_handler_del(_e_dpms_handler_border_fullscreen);
        _e_dpms_handler_border_fullscreen = NULL;
     }

   if (_e_dpms_handler_border_unfullscreen)
     {
        ecore_event_handler_del(_e_dpms_handler_border_unfullscreen);
        _e_dpms_handler_border_unfullscreen = NULL;
     }

   if (_e_dpms_handler_border_remove)
     {
        ecore_event_handler_del(_e_dpms_handler_border_remove);
        _e_dpms_handler_border_remove = NULL;
     }

   if (_e_dpms_handler_border_iconify)
     {
        ecore_event_handler_del(_e_dpms_handler_border_iconify);
        _e_dpms_handler_border_iconify = NULL;
     }

   if (_e_dpms_handler_border_uniconify)
     {
        ecore_event_handler_del(_e_dpms_handler_border_uniconify);
        _e_dpms_handler_border_uniconify = NULL;
     }

   if (_e_dpms_handler_border_desk_set)
     {
        ecore_event_handler_del(_e_dpms_handler_border_desk_set);
        _e_dpms_handler_border_desk_set = NULL;
     }

   if (_e_dpms_handler_desk_show)
     {
        ecore_event_handler_del(_e_dpms_handler_desk_show);
        _e_dpms_handler_desk_show = NULL;
     }

   return 1;
}
#include "config.h"
#include <Elementary.h>

EAPI_MAIN int
elm_main(int argc, char **argv)
{
   const char *src_profile;
   const char *derive_profile;
   const char *derive_options;

   if (argc < 4)
     {
        printf("This is an internal tool for enlightenment\n"
               "  enlightenment_elm_cfgtool set SRCPROFILE DERIVEDPROFILE DERIVEOPTIONS\n"
               "  enlightenment_elm_cfgtool del SRCPROFILE DERIVEDPROFILE\n"
               "\n"
               "e.g.\n"
               "  enlightenment_elm_cfgtool set standard .scale-0150-standard 'scale-mul 150'\n"
               "\n");
        return 0;
     }
   if (!strcmp(argv[1], "set"))
     {
        src_profile    = argv[2];
        derive_profile = argv[3];
        derive_options = argv[4];
        elm_config_profile_set(src_profile);
        elm_config_profile_derived_del(derive_profile);
        elm_config_profile_derived_add(derive_profile, derive_options);
     }
   else if (!strcmp(argv[1], "del"))
     {
        src_profile    = argv[2];
        derive_profile = argv[3];
        elm_config_profile_set(src_profile);
        elm_config_profile_derived_del(derive_profile);
     }
   else
     {
        printf("Unknown command '%s'\n", argv[1]);
     }
   return 0;
}
ELM_MAIN()
#include "e.h"

static void
_e_entry_dialog_free(E_Entry_Dialog *ed)
{
   e_object_del(E_OBJECT(ed->dia));
   E_FREE(ed->text);
   free(ed);
}

static void
_e_entry_dialog_delete(E_Dialog *dia, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_del(E_OBJECT(dia->data));
}

static void
_e_entry_dialog_del(void *data)
{
   E_Dialog *dia;

   dia = data;
   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL,
                                  (Evas_Object_Event_Cb)_e_entry_dialog_delete,
                                  dia);
   e_object_del(dia->data);
}

static void
_e_entry_dialog_ok(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Entry_Dialog *ed;

   ed = data;
   e_object_ref(E_OBJECT(ed));
   if (ed->ok.func) ed->ok.func(ed->ok.data, ed->text);
   e_object_del(E_OBJECT(ed));
   e_object_unref(E_OBJECT(ed));
}
static void
_e_entry_dialog_cancel(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Entry_Dialog *ed;

   ed = data;
   e_object_ref(E_OBJECT(ed));
   if (ed->cancel.func) ed->cancel.func(ed->cancel.data);
   e_object_del(E_OBJECT(ed));
   e_object_unref(E_OBJECT(ed));
}

static void
_e_entry_cb_key_down(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Key_Down *ev;
   E_Entry_Dialog *ed;

   ev = event_info;
   if (!(ed = data)) return;
   if (!strcmp(ev->key, "Return"))
     _e_entry_dialog_ok(data, ed->dia);
   else if (!strcmp(ev->key, "Escape"))
     _e_entry_dialog_cancel(data, ed->dia);
}

E_API E_Entry_Dialog *
e_entry_dialog_show(Evas_Object *parent, const char *title, const char *icon, const char *text, const char *initial_text, const char *button_text, const char *button2_text, void (*ok_func) (void *data, char *text), void (*cancel_func) (void *data), void *data)
{
   E_Entry_Dialog *ed;
   E_Dialog *dia;
   Evas_Object *o, *obj;
   Evas_Modifier_Mask mask;
   Evas *e;
   int w, h;

   ed = E_OBJECT_ALLOC(E_Entry_Dialog, E_ENTRY_DIALOG_TYPE,
                       _e_entry_dialog_free);

   ed->ok.func = ok_func;
   ed->ok.data = data;
   ed->cancel.func = cancel_func;
   ed->cancel.data = data;

   if (initial_text) ed->text = strdup(initial_text);

   dia = e_dialog_new(parent, "E", "_entry_dialog");
   if (!dia)
     {
        e_object_del(E_OBJECT(ed));
        return NULL;
     }

   dia->data = ed;
   ed->dia = dia;

   mask = 0;
   evas_object_key_ungrab(dia->event_object, "space", mask, ~mask);
   e_object_del_attach_func_set(E_OBJECT(dia), _e_entry_dialog_del);
   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL,
                                  (Evas_Object_Event_Cb)_e_entry_dialog_delete,
                                  dia);

   if (title) e_dialog_title_set(dia, title);
   if (icon) e_dialog_icon_set(dia, icon, 64);

   e = evas_object_evas_get(dia->win);

   /* create main packing table */
   o = e_widget_table_add(e, 0);

   if (text)
     {
        obj = e_widget_label_add(e, text);
        e_widget_table_object_append(o, obj, 0, 0, 1, 1, 1, 1, 0, 0);
     }

   ed->entry = e_widget_entry_add(dia->win, &(ed->text), NULL, NULL, NULL);
   evas_object_smart_callback_add(ed->entry, "key_down",
                                  _e_entry_cb_key_down, ed);
   evas_object_size_hint_weight_set(ed->entry, EVAS_HINT_EXPAND, 0.5);

   e_widget_table_object_append(o, ed->entry, 1, 0, 1, 1, 1, 1, 1, 0);
   e_widget_size_min_get(o, &w, &h);
   e_dialog_content_set(dia, o, w, h);

   e_dialog_button_add(dia, !button_text ? _("OK") : button_text, NULL,
                       _e_entry_dialog_ok, ed);
   e_dialog_button_add(dia, !button2_text ? _("Cancel") : button2_text, NULL,
                       _e_entry_dialog_cancel, ed);

   elm_win_center(dia->win, 1, 1);
   e_dialog_resizable_set(dia, 1);
   e_dialog_show(dia);
   e_widget_focus_set(ed->entry, 1);
   e_widget_entry_select_all(ed->entry);

   return ed;
}
#include "e.h"

/* local subsystem functions */

/* externally accessible functions */
EINTERN int
e_env_init(void)
{
   Eina_List *l;
   E_Config_Env_Var *evr;

   EINA_LIST_FOREACH(e_config->env_vars, l, evr)
     {
        if (evr->unset)
          e_env_unset(evr->var);
        else
          e_env_set(evr->var, evr->val);
     }
   return 1;
}

EINTERN int
e_env_shutdown(void)
{
   Eina_List *l;
   E_Config_Env_Var *evr;

   EINA_LIST_FOREACH(e_config->env_vars, l, evr)
     {
        e_env_unset(evr->var);
     }
   return 1;
}

E_API void
e_env_set(const char *var, const char *val)
{
   e_util_env_set(var, val);
}

E_API void
e_env_unset(const char *var)
{
   e_util_env_set(var, NULL);
}

/* local subsystem functions */
#include "e.h"

/* local subsystem functions */

/* local subsystem globals */

/* externally accessible functions */
E_API void
e_error_message_show_internal(char *txt)
{
   /* FIXME: maybe log these to a file and display them at some point */
   printf("<<<< Enlightenment Error >>>>\n%s\n", txt);
}

/* local subsystem functions */
#include "e.h"

#define MAX_OUTPUT_CHARACTERS 5000

/* TODO:
 * - Clear e_exec_instances on shutdown
 * - Clear e_exec_start_pending on shutdown
 * - Create border add handler
 * - Launch .desktop in terminal if .desktop requires it
 */

typedef struct _E_Exec_Launch E_Exec_Launch;
typedef struct _E_Exec_Search E_Exec_Search;
typedef struct _E_Exec_Watch  E_Exec_Watch;
typedef struct _E_Exec_Recent E_Exec_Recent;

struct _E_Exec_Launch
{
   E_Zone     *zone;
   const char *launch_method;
};

struct _E_Exec_Search
{
   E_Exec_Instance *inst;
   Efreet_Desktop  *desktop;
   int              startup_id;
   pid_t            pid;
};

struct _E_Exec_Watch
{
   void        (*func)(void *data, E_Exec_Instance *inst, E_Exec_Watch_Type type);
   const void *data;
   Eina_Bool   delete_me E_BITFIELD;
};

struct _E_Config_Dialog_Data
{
   Efreet_Desktop       *desktop;
   char                 *exec;

   Ecore_Exe_Event_Del   event;
   Ecore_Exe_Event_Data *error;
   Ecore_Exe_Event_Data *read;

   char                 *label, *exit, *signal;
};

struct _E_Exec_Recent
{
   Eina_List *files;
};

/* local subsystem functions */
static E_Exec_Instance *_e_exec_cb_exec(void *data, Efreet_Desktop *desktop, char *exec, int remaining);
static Eina_Bool        _e_exec_cb_expire_timer(void *data);
static Eina_Bool        _e_exec_cb_exit(void *data, int type, void *event);
static Eina_Bool        _e_exec_cb_desktop_update(void *data, int type, void *event);
static void        _e_exec_cb_exec_new_free(void *data, void *event);
static void _e_exec_cb_exec_new_client_free(void *data, void *ev);
static void        _e_exec_cb_exec_del_free(void *data, void *event);
static void _e_exe_instance_watchers_call(E_Exec_Instance *inst, E_Exec_Watch_Type type);
static Eina_Bool        _e_exec_startup_id_pid_find(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *value, void *data);

static void             _e_exec_error_dialog(Efreet_Desktop *desktop, const char *exec, Ecore_Exe_Event_Del *event, Ecore_Exe_Event_Data *error, Ecore_Exe_Event_Data *read);
static void             _fill_data(E_Config_Dialog_Data *cfdata);
static void            *_create_data(E_Config_Dialog *cfd);
static void             _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object     *_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Evas_Object     *_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Evas_Object     *_dialog_scrolltext_create(Evas *evas, char *title, Ecore_Exe_Event_Data_Line *lines);
static void             _dialog_save_cb(void *data, void *data2);
static Eina_Bool        _e_exec_instance_free(E_Exec_Instance *inst);

/* local subsystem globals */
static Eina_List *e_exec_start_pending = NULL;
static Eina_Hash *e_exec_instances = NULL;
static int startup_id = 0;

static Ecore_Event_Handler *_e_exec_exit_handler = NULL;
static Ecore_Event_Handler *_e_exec_desktop_update_handler = NULL;

static E_Exec_Instance *(*_e_exec_executor_func)(void *data, E_Zone * zone, Efreet_Desktop * desktop, const char *exec, Eina_List *files, const char *launch_method) = NULL;
static void *_e_exec_executor_data = NULL;

E_API int E_EVENT_EXEC_NEW = -1;
E_API int E_EVENT_EXEC_NEW_CLIENT = -1;
E_API int E_EVENT_EXEC_DEL = -1;

static E_Exec_Recent * _e_exec_recent = NULL;
static Ecore_Idler *_e_exec_recent_idler = NULL;

static void
_e_exec_recent_exists_filter(void)
{
   Eina_List *l, *ll;
   E_Exec_Recent_File *fl;

   if ((!_e_exec_recent) || (!_e_exec_recent->files)) return;
   EINA_LIST_FOREACH_SAFE(_e_exec_recent->files, l, ll, fl)
     {
        if (!ecore_file_exists(fl->file))
          {
             _e_exec_recent->files =
               eina_list_remove_list(_e_exec_recent->files, l);
             eina_stringshare_del(fl->file);
             free(fl);
          }
     }
}

static Eina_Bool
_e_exec_cb_recent_idler(void *data EINA_UNUSED)
{
   char buf[4096];
   FILE *f;
   Eina_List *l;
   E_Exec_Recent_File *fl;

   _e_exec_recent_exists_filter();
   if ((_e_exec_recent) && (_e_exec_recent->files))
     {
        e_user_dir_snprintf(buf, sizeof(buf), "recent-files.txt");
        f = fopen(buf, "w");
        if (f)
          {
             EINA_LIST_FOREACH(_e_exec_recent->files, l, fl)
               {
                  fprintf(f, "%1.0f %s\n", fl->timestamp * 100.0, fl->file);
               }
             fclose(f);
          }
     }
   _e_exec_recent_idler = NULL;
   return EINA_FALSE;
}

static void
_e_exec_recent_load(void)
{
   FILE *f;
   char buf[4096];
   long long timi;

   if (_e_exec_recent) return;

   e_user_dir_snprintf(buf, sizeof(buf), "recent-files.txt");
   f = fopen(buf, "r");
   if (!f) return;
   while (fscanf(f, "%lli %4095[^\n]\n", &timi, buf) == 2)
     {
        E_Exec_Recent_File *fl = calloc(1, sizeof(E_Exec_Recent_File));

        if (!fl) free(fl);
        if (!_e_exec_recent) _e_exec_recent = calloc(1, sizeof(E_Exec_Recent));
        if (!_e_exec_recent)
          {
             free(fl);
             break;
          }
        fl->file = eina_stringshare_add(buf);
        fl->timestamp = (double)timi / 100.0;
        _e_exec_recent->files = eina_list_append(_e_exec_recent->files, fl);
     }
   fclose(f);
}

static void
_e_exec_recent_file_append(const char *file, double tim)
{
   Eina_List *l;
   E_Exec_Recent_File *fl = calloc(1, sizeof(E_Exec_Recent_File));
   E_Exec_Recent_File *fl2;

   if (!fl) return;
   _e_exec_recent_load();
   if (!_e_exec_recent)
     _e_exec_recent = calloc(1, sizeof(E_Exec_Recent));
   if (!_e_exec_recent)
     {
        free(fl);
        return;
     }
   fl->file = eina_stringshare_add(file);
   fl->timestamp = tim;
   EINA_LIST_FOREACH(_e_exec_recent->files, l, fl2)
     {
        if (!strcmp(fl2->file, fl->file))
          {
             _e_exec_recent->files = eina_list_remove_list(_e_exec_recent->files, l);
             eina_stringshare_del(fl2->file);
             free(fl2);
             break;
          }
     }
   _e_exec_recent->files = eina_list_prepend(_e_exec_recent->files, fl);
   if (eina_list_count(_e_exec_recent->files) > 30)
     {
        l = eina_list_last(_e_exec_recent->files);
        if (l)
          {
             fl = l->data;
             _e_exec_recent->files = eina_list_remove_list(_e_exec_recent->files, l);
             eina_stringshare_del(fl->file);
             free(fl);
          }
     }
   if (!_e_exec_recent_idler)
     _e_exec_recent_idler = ecore_idler_add(_e_exec_cb_recent_idler, NULL);
}

static void
_e_exec_recent_clean(void)
{
   E_Exec_Recent_File *fl;

   if (!_e_exec_recent) return;
   EINA_LIST_FREE(_e_exec_recent->files, fl)
     {
        eina_stringshare_del(fl->file);
        free(fl);
     }
   free(_e_exec_recent);
   _e_exec_recent = NULL;
   if (_e_exec_recent_idler) ecore_idler_del(_e_exec_recent_idler);
   _e_exec_recent_idler = NULL;
}

/* externally accessible functions */
EINTERN int
e_exec_init(void)
{
   e_exec_instances = eina_hash_string_superfast_new(NULL);

   _e_exec_exit_handler =
     ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _e_exec_cb_exit, NULL);
   _e_exec_desktop_update_handler =
     ecore_event_handler_add(EFREET_EVENT_DESKTOP_CACHE_UPDATE, _e_exec_cb_desktop_update, NULL);

   E_EVENT_EXEC_NEW = ecore_event_type_new();
   E_EVENT_EXEC_NEW_CLIENT = ecore_event_type_new();
   E_EVENT_EXEC_DEL = ecore_event_type_new();
   return 1;
}

EINTERN int
e_exec_shutdown(void)
{
   _e_exec_recent_clean();
   if (_e_exec_exit_handler) ecore_event_handler_del(_e_exec_exit_handler);
   if (_e_exec_desktop_update_handler)
     ecore_event_handler_del(_e_exec_desktop_update_handler);
   eina_hash_free(e_exec_instances);
   eina_list_free(e_exec_start_pending);
   return 1;
}

E_API void
e_exec_executor_set(E_Exec_Instance *(*func)(void *data, E_Zone * zone, Efreet_Desktop * desktop, const char *exec, Eina_List *files, const char *launch_method), const void *data)
{
   _e_exec_executor_func = func;
   _e_exec_executor_data = (void *)data;
}

E_API const Eina_List *
e_exec_recent_files_get(void)
{
   _e_exec_recent_load();
   if (!_e_exec_recent) return NULL;
   _e_exec_recent_exists_filter();
   return _e_exec_recent->files;
}

E_API E_Exec_Instance *
e_exec(E_Zone *zone, Efreet_Desktop *desktop, const char *exec,
       Eina_List *files, const char *launch_method)
{
   E_Exec_Launch *launch;
   E_Exec_Instance *inst = NULL;

   if ((!desktop) && (!exec)) return NULL;

   if (files)
     {
        const char *s;
        Eina_List *l;
        char buf[4096], buf2[8192+128];
        double tim;

        if (getcwd(buf, sizeof(buf)))
          {
             tim = ecore_time_unix_get();
             EINA_LIST_FOREACH(files, l, s)
               {
                  if (s[0] == '/')
                    _e_exec_recent_file_append(s, tim);
                  else
                    {
                       snprintf(buf2, sizeof(buf2), "%s/%s", buf, s);
                       _e_exec_recent_file_append(buf2, tim);
                    }
               }
          }
     }
   if (_e_exec_executor_func)
     return _e_exec_executor_func(_e_exec_executor_data, zone,
                                  desktop, exec, files, launch_method);

   if (desktop)
     {
        const char *single;

        single = eina_hash_find(desktop->x, "X-Enlightenment-Single-Instance");
        if ((single) ||
            (e_config->exe_always_single_instance))
          {
             Eina_Bool dosingle = EINA_FALSE;

             // first take system config for always single instance if set
             if (e_config->exe_always_single_instance) dosingle = EINA_TRUE;

             // and now let desktop file override it
             if (single)
               {
                  if ((!strcasecmp(single, "true")) ||
                      (!strcasecmp(single, "yes")) ||
                      (!strcasecmp(single, "1")))
                    dosingle = EINA_TRUE;
                  else if ((!strcasecmp(single, "false")) ||
                           (!strcasecmp(single, "no")) ||
                           (!strcasecmp(single, "0")))
                    dosingle = EINA_FALSE;
               }

             if (dosingle)
               {
                  const Eina_List *l;
                  E_Client *ec;

                  EINA_LIST_FOREACH(e_comp->clients, l, ec)
                    {
                       if (ec && (ec->desktop == desktop))
                         {
                            if (!ec->focused)
                              e_client_activate(ec, EINA_TRUE);
                            else
                              evas_object_raise(ec->frame);
                            return NULL;
                         }
                    }
               }
          }
     }

   launch = E_NEW(E_Exec_Launch, 1);
   if (!launch) return NULL;
   if (zone)
     {
        launch->zone = zone;
        e_object_ref(E_OBJECT(launch->zone));
     }
   if (launch_method)
     launch->launch_method = eina_stringshare_add(launch_method);

   if (desktop)
     {
        if (exec)
          inst = _e_exec_cb_exec(launch, NULL, strdup(exec), 0);
        else
          {
             if (desktop->exec)
               inst = efreet_desktop_command_get(desktop, files,
                 (Efreet_Desktop_Command_Cb)_e_exec_cb_exec, launch);
             else
               inst = _e_exec_cb_exec(launch, desktop, NULL, 0);
          }
     }
   else
     inst = _e_exec_cb_exec(launch, NULL, strdup(exec), 0);
   if ((zone) && (inst))
     {
        inst->screen = zone->num;
        inst->desk_x = zone->desk_x_current;
        inst->desk_y = zone->desk_y_current;
     }
   return inst;
}

E_API Eina_Bool
e_exec_phony_del(E_Exec_Instance *inst)
{
   if (!inst) return EINA_TRUE;
   EINA_SAFETY_ON_TRUE_RETURN_VAL(!inst->phony, EINA_FALSE);
   inst->ref--;
   _e_exe_instance_watchers_call(inst, E_EXEC_WATCH_STOPPED);
   return _e_exec_instance_free(inst);
}

E_API E_Exec_Instance *
e_exec_phony(E_Client *ec)
{
   E_Exec_Instance *inst;
   Eina_List *l, *lnew;

   if (ec->desktop)
     {
        /* try grouping with previous phony exec */
        l = eina_hash_find(e_exec_instances, ec->desktop->orig_path ?: ec->desktop->name);
        EINA_LIST_FOREACH(l, lnew, inst)
          if (inst && inst->phony)
            {
               e_exec_instance_client_add(inst, ec);
               return inst;
            }
     }

   inst = E_NEW(E_Exec_Instance, 1);
   inst->ref = 1;
   inst->phony = 1;
   inst->desktop = ec->desktop;
   inst->startup_id = ec->netwm.startup_id;
   if (ec->desktop)
     {
        efreet_desktop_ref(ec->desktop);
        inst->key = eina_stringshare_add(ec->desktop->orig_path ?: ec->desktop->name);
     }
   else if (ec->icccm.command.argc)
     {
        Eina_Strbuf *buf;
        int x;

        buf = eina_strbuf_new();
        for (x = 0; x < ec->icccm.command.argc; x++)
          {
             eina_strbuf_append(buf, ec->icccm.command.argv[x]);
             if (x + 1 < ec->icccm.command.argc)
               eina_strbuf_append_char(buf, ' ');
          }
        inst->key = eina_stringshare_add(eina_strbuf_string_get(buf));
        eina_strbuf_free(buf);
     }
   else
     {
        free(inst);
        return NULL;
     }
   inst->used = 1;
   if (ec->zone) inst->screen = ec->zone->num;
   if (ec->desk)
     {
        inst->desk_x = ec->desk->x;
        inst->desk_y = ec->desk->y;
     }
   l = eina_hash_find(e_exec_instances, inst->key);
   lnew = eina_list_append(l, inst);
   if (l) eina_hash_modify(e_exec_instances, inst->key, lnew);
   else eina_hash_add(e_exec_instances, inst->key, lnew);
   inst->ref++;
   ecore_event_add(E_EVENT_EXEC_NEW, inst, _e_exec_cb_exec_new_free, inst);
   e_exec_instance_client_add(inst, ec);
   return inst;
}

E_API E_Exec_Instance *
e_exec_startup_id_pid_instance_find(int id, pid_t pid)
{
   E_Exec_Search search;

   search.inst = NULL;
   search.desktop = NULL;
   search.startup_id = id;
   search.pid = pid;
   eina_hash_foreach(e_exec_instances, _e_exec_startup_id_pid_find, &search);
   return search.inst;
}

E_API Efreet_Desktop *
e_exec_startup_id_pid_find(int id, pid_t pid)
{
   E_Exec_Instance *inst;

   inst = e_exec_startup_id_pid_instance_find(id, pid);
   if (!inst) return NULL;
   return inst->desktop;
}

E_API E_Exec_Instance *
e_exec_startup_desktop_instance_find(Efreet_Desktop *desktop)
{
   E_Exec_Search search;

   search.inst = NULL;
   search.desktop = desktop;
   search.startup_id = 0;
   search.pid = 0;
   eina_hash_foreach(e_exec_instances, _e_exec_startup_id_pid_find, &search);
   return search.inst;
}

static void
_e_exe_instance_watchers_call(E_Exec_Instance *inst, E_Exec_Watch_Type type)
{
   E_Exec_Watch *iw;
   Eina_List *l, *ln;

   inst->ref++;
   EINA_LIST_FOREACH(inst->watchers, l, iw)
     {
        if (iw->func && (!iw->delete_me)) iw->func((void *)(iw->data), inst, type);
     }
   inst->ref--;
   if (inst->ref == 0)
     {
        EINA_LIST_FOREACH_SAFE(inst->watchers, l, ln, iw)
          {
             if (iw->delete_me)
               {
                  inst->watchers = eina_list_remove_list(inst->watchers, l);
                  free(iw);
               }
          }
     }
}

E_API void
e_exec_instance_found(E_Exec_Instance *inst)
{
   E_FREE_FUNC(inst->expire_timer, ecore_timer_del);
   _e_exe_instance_watchers_call(inst, E_EXEC_WATCH_STARTED);
}

E_API void
e_exec_instance_client_add(E_Exec_Instance *inst, E_Client *ec)
{
   inst->clients = eina_list_append(inst->clients, ec);
   REFD(ec, 2);
   e_object_ref(E_OBJECT(ec));
   ec->exe_inst = inst;
   inst->ref++;
   ecore_event_add(E_EVENT_EXEC_NEW_CLIENT, inst, _e_exec_cb_exec_new_client_free, ec);
}

E_API void
e_exec_instance_watcher_add(E_Exec_Instance *inst, void (*func)(void *data, E_Exec_Instance *inst, E_Exec_Watch_Type type), const void *data)
{
   E_Exec_Watch *iw;

   iw = E_NEW(E_Exec_Watch, 1);
   if (!iw) return;
   iw->func = func;
   iw->data = data;
   inst->watchers = eina_list_append(inst->watchers, iw);
}

E_API void
e_exec_instance_watcher_del(E_Exec_Instance *inst, void (*func)(void *data, E_Exec_Instance *inst, E_Exec_Watch_Type type), const void *data)
{
   E_Exec_Watch *iw;
   Eina_List *l, *ln;

   EINA_LIST_FOREACH_SAFE(inst->watchers, l, ln, iw)
     {
        if ((iw->func == func) && (iw->data == data))
          {
             if (inst->ref == 0)
               {
                  inst->watchers = eina_list_remove_list(inst->watchers, l);
                  free(iw);
                  return;
               }
             else
               {
                  iw->delete_me = EINA_TRUE;
                  return;
               }
          }
     }
}

/* local subsystem functions */
static E_Exec_Instance *
_e_exec_cb_exec(void *data, Efreet_Desktop *desktop, char *exec, int remaining)
{
   E_Exec_Instance *inst = NULL;
   E_Exec_Launch *launch;
   Eina_List *l, *lnew;
   Ecore_Exe *exe = NULL;
   char buf[4096];

   launch = data;
   inst = E_NEW(E_Exec_Instance, 1);
   if (!inst) return NULL;
   inst->ref = 1;

   if (startup_id == 0)
     {
        startup_id = e_exehist_startup_id_get();
        if (startup_id < 0) startup_id = 0;
     }
   if (++startup_id < 1) startup_id = 1;
   snprintf(buf, sizeof(buf), "E_START|%i", startup_id);
   e_util_env_set("DESKTOP_STARTUP_ID", buf);

   // don't set vsync for clients - maybe inherited from compositor. fixme:
   // need a way to still inherit from parent env of wm.
   e_util_env_set("__GL_SYNC_TO_VBLANK", NULL);

//// FIXME: seem to be some issues with the pipe and filling up ram - need to
//// check. for now disable.
//   exe = ecore_exe_pipe_run(exec,
//			    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);
   if ((desktop) && (desktop->path) && (desktop->path[0]))
     {
        if (!getcwd(buf, sizeof(buf)))
          {
             free(inst);
             e_util_dialog_show
               (_("Run Error"),
               _("Enlightenment was unable to get current directory"));
             return NULL;
          }
        if (chdir(desktop->path))
          {
             free(inst);
             e_util_dialog_show
               (_("Run Error"),
               _("Enlightenment was unable to change to directory:<ps/>"
                 "<ps/>"
                 "%s"),
               desktop->path);
             return NULL;
          }
        exe = e_util_exe_safe_run(exec, inst);
        if (chdir(buf))
          {
             e_util_dialog_show
               (_("Run Error"),
               _("Enlightenment was unable to restore to directory:<ps/>"
                 "<ps/>"
                 "%s"),
               buf);
             free(inst);
             return NULL;
          }
     }
   else if (exec)
     {
        if ((desktop) && (desktop->terminal))
          {
             Efreet_Desktop *tdesktop;

             tdesktop = e_util_terminal_desktop_get();
             if (tdesktop)
               {
                  if (tdesktop->exec)
                    {
                       Eina_Strbuf *sb;

                       sb = eina_strbuf_new();
                       if (sb)
                         {
                            eina_strbuf_append(sb, tdesktop->exec);
                            eina_strbuf_append(sb, " -e ");
                            eina_strbuf_append_escaped(sb, exec);
                            exe = e_util_exe_safe_run
                              (eina_strbuf_string_get(sb), inst);
                            eina_strbuf_free(sb);
                         }
                    }
                  else
                    exe = e_util_exe_safe_run(exec, inst);
                  efreet_desktop_free(tdesktop);
               }
             else
               exe = e_util_exe_safe_run(exec, inst);
          }
        else if (desktop && desktop->url)
          {
             char *sb;
             size_t size = 65536, len;

             sb = malloc(size);
             snprintf(sb, size, "%s/enlightenment_open ", e_prefix_bin_get());
             len = strlen(sb);
             sb = e_util_string_append_quoted(sb, &size, &len, desktop->url);
             exe = e_util_exe_safe_run(sb, inst);
             free(sb);
          }
        else
          exe = e_util_exe_safe_run(exec, inst);
     }

   if (!exe)
     {
        free(inst);
        e_util_dialog_show(_("Run Error"),
                           _("Enlightenment was unable to fork a child process:<ps/>"
                             "<ps/>"
                             "%s"),
                           exec);
        return NULL;
     }
   /* reset env vars */
   if ((launch->launch_method) && (!desktop))
     e_exehist_add(launch->launch_method, exec);
   /* 20 lines at start and end, 20x100 limit on bytes at each end. */
//// FIXME: seem to be some issues with the pipe and filling up ram - need to
//// check. for now disable.
//   ecore_exe_auto_limits_set(exe, 2000, 2000, 20, 20);
   ecore_exe_tag_set(exe, "E/exec");

   if (desktop)
     {
        inst->desktop = desktop;
        efreet_desktop_ref(desktop);
        inst->key = eina_stringshare_add(desktop->orig_path ?: desktop->name);
     }
   else
     inst->key = eina_stringshare_add(exec);
   inst->exe = exe;
   inst->startup_id = startup_id;
   inst->launch_time = ecore_time_get();
   inst->expire_timer = ecore_timer_loop_add(e_config->exec.expire_timeout,
                                        _e_exec_cb_expire_timer, inst);
   l = eina_hash_find(e_exec_instances, inst->key);
   lnew = eina_list_append(l, inst);
   if (l) eina_hash_modify(e_exec_instances, inst->key, lnew);
   else eina_hash_add(e_exec_instances, inst->key, lnew);
   if (inst->desktop && inst->desktop->exec)
     {
        e_exec_start_pending = eina_list_append(e_exec_start_pending,
                                                inst->desktop);
        e_exehist_add(launch->launch_method, inst->desktop->exec);
     }

   if (!remaining)
     {
        if (launch->launch_method) eina_stringshare_del(launch->launch_method);
        if (launch->zone) e_object_unref(E_OBJECT(launch->zone));
        free(launch);
     }
   free(exec);
   inst->ref++;
   ecore_event_add(E_EVENT_EXEC_NEW, inst, _e_exec_cb_exec_new_free, inst);
   return inst;
}

static Eina_Bool
_e_exec_cb_expire_timer(void *data)
{
   E_Exec_Instance *inst;

   inst = data;
   if (inst->desktop)
     e_exec_start_pending = eina_list_remove(e_exec_start_pending,
                                             inst->desktop);
   inst->expire_timer = NULL;
   _e_exe_instance_watchers_call(inst, E_EXEC_WATCH_TIMEOUT);
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_exec_instance_free(E_Exec_Instance *inst)
{
   Eina_List *instances;

   if (inst->ref) return EINA_FALSE;
   E_FREE_LIST(inst->watchers, free);
   if (inst->key)
     {
        instances = eina_hash_find(e_exec_instances, inst->key);
        if (instances)
          {
             instances = eina_list_remove(instances, inst);
             if (instances)
               eina_hash_modify(e_exec_instances, inst->key, instances);
             else
               eina_hash_del_by_key(e_exec_instances, inst->key);
          }
        eina_stringshare_replace(&inst->key, NULL);
     }
   if (!inst->deleted)
     {
        inst->deleted = 1;
        inst->ref++;
        E_LIST_FOREACH(inst->clients, e_object_ref);
        ecore_event_add(E_EVENT_EXEC_DEL, inst, _e_exec_cb_exec_del_free, inst);
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/*
   static Eina_Bool
   _e_exec_cb_instance_finish(void *data)
   {
   _e_exec_instance_free(data);
   return ECORE_CALLBACK_CANCEL;
   }
 */

static void
_e_exec_cb_exec_new_client_free(void *data, void *ev)
{
   E_Exec_Instance *inst = ev;
   E_Client *ec = data;

   inst->ref--;
   _e_exec_instance_free(inst);
   UNREFD(ec, 1);
   e_object_unref(E_OBJECT(ec));
}

static void
_e_exec_cb_exec_new_free(void *data, void *ev EINA_UNUSED)
{
   E_Exec_Instance *inst = data;

   inst->ref--;
   _e_exec_instance_free(inst);
}

static void
_e_exec_cb_exec_del_free(void *data, void *ev EINA_UNUSED)
{
   E_Exec_Instance *inst = data;
   E_Client *ec;

   inst->ref--;

   if (inst->desktop)
     e_exec_start_pending = eina_list_remove(e_exec_start_pending,
                                             inst->desktop);
   if (inst->expire_timer) ecore_timer_del(inst->expire_timer);

   EINA_LIST_FREE(inst->clients, ec)
     {
        ec->exe_inst = NULL;
        e_object_unref(E_OBJECT(ec));
     }

   if (inst->desktop) efreet_desktop_free(inst->desktop);
   if (!inst->phony)
     {
        if (inst->exe) ecore_exe_data_set(inst->exe, NULL);
     }
   free(inst);
}

static Eina_Bool
_e_exec_cb_exit(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Del *ev;
   E_Exec_Instance *inst;

   ev = event;
   if (!ev->exe)
     {
        inst = e_exec_startup_id_pid_instance_find(-1, ev->pid);
        if (!inst) return ECORE_CALLBACK_PASS_ON;
        ev->exe = inst->exe;
     }
//   if (ecore_exe_tag_get(ev->exe)) printf("  tag %s\n", ecore_exe_tag_get(ev->exe));
   if (!(ecore_exe_tag_get(ev->exe) &&
         (!strcmp(ecore_exe_tag_get(ev->exe), "E/exec"))))
     return ECORE_CALLBACK_PASS_ON;
   inst = ecore_exe_data_get(ev->exe);
   if (!inst) return ECORE_CALLBACK_PASS_ON;
   if (inst->phony) return ECORE_CALLBACK_RENEW;

   /* /bin/sh uses this if cmd not found */
   if ((ev->exited) &&
       ((ev->exit_code == 127) || (ev->exit_code == 255)))
     {
        if (e_config->exec.show_run_dialog)
          {
             E_Dialog *dia;

             dia = e_dialog_new(NULL,
                                "E", "_e_exec_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:<ps/>"
                             "<ps/>"
                             "%s<ps/>"
                             "<ps/>"
                             "The application failed to start."),
                           ecore_exe_cmd_get(ev->exe));
                  e_dialog_text_set(dia, buf);
                  e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
                  e_dialog_button_focus_num(dia, 1);
                  elm_win_center(dia->win, 1, 1);
                  e_dialog_show(dia);
               }
          }
     }
   /* Let's hope that everything returns this properly. */
   else if (!((ev->exited) && (ev->exit_code == EXIT_SUCCESS)))
     {
        if (e_config->exec.show_exit_dialog)
          {
             /* filter out common exits via signals - int/term/quit. not really
              * worth popping up a dialog for */
             if (!((ev->signalled) &&
                   ((ev->exit_signal == SIGINT) ||
                    (ev->exit_signal == SIGQUIT) ||
                    (ev->exit_signal == SIGTERM)))
                 )
               {
                  /* Show the error dialog with details from the exe. */
                  _e_exec_error_dialog(inst->desktop, ecore_exe_cmd_get(ev->exe), ev,
                                       ecore_exe_event_data_get(ev->exe, ECORE_EXE_PIPE_ERROR),
                                       ecore_exe_event_data_get(ev->exe, ECORE_EXE_PIPE_READ));
               }
          }
     }

/* scripts that fork off children with & break child tracking... but this hack
 * also breaks apps that handle single-instance themselves */
/*
   if ((ecore_time_get() - inst->launch_time) < 2.0)
     {
        inst->exe = NULL;
        if (inst->expire_timer) ecore_timer_del(inst->expire_timer);
        inst->expire_timer = ecore_timer_loop_add(e_config->exec.expire_timeout, _e_exec_cb_instance_finish, inst);
     }
   else
 */
   inst->ref--;
   inst->exe = NULL;
   _e_exe_instance_watchers_call(inst, E_EXEC_WATCH_STOPPED);
   _e_exec_instance_free(inst);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_exec_cb_desktop_update(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   const Eina_Hash *execs = e_exec_instances_get();
   Eina_Iterator *it;
   const Eina_List *l, *ll;
   E_Exec_Instance *exe;

   it = eina_hash_iterator_data_new(execs);
   EINA_ITERATOR_FOREACH(it, l)
     {
        EINA_LIST_FOREACH(l, ll, exe)
          {
             if (exe->desktop)
               {
                  efreet_desktop_free(exe->desktop);
                  exe->desktop = NULL;
                  if (exe->key)
                    {
                       exe->desktop = efreet_desktop_get(exe->key);
                       if (!exe->desktop)
                         exe->desktop = efreet_util_desktop_name_find(exe->key);
                    }
               }
          }
     }
   eina_iterator_free(it);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_exec_startup_id_pid_find(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *value, void *data)
{
   E_Exec_Search *search;
   E_Exec_Instance *inst;
   Eina_List *l;

   search = data;
   EINA_LIST_FOREACH(value, l, inst)
     {
        pid_t exe_pid;

        exe_pid = 0;
        if (inst->exe)
          {
             exe_pid = ecore_exe_pid_get(inst->exe);
             if (exe_pid <= 0) inst->exe = NULL;
          }
        if (((search->desktop) &&
             (search->desktop == inst->desktop)) ||

            ((search->startup_id > 0) &&
             (search->startup_id == inst->startup_id)) ||

            ((inst->exe) && (search->pid > 1) && (!inst->phony) &&
             (search->pid == exe_pid)))
          {
             search->inst = inst;
             return EINA_FALSE;
          }
     }
   return EINA_TRUE;
}

static void
_e_exec_error_dialog(Efreet_Desktop *desktop, const char *exec, Ecore_Exe_Event_Del *exe_event,
                     Ecore_Exe_Event_Data *exe_error, Ecore_Exe_Event_Data *exe_read)
{
   E_Config_Dialog_View *v;
   E_Config_Dialog_Data *cfdata;

   v = E_NEW(E_Config_Dialog_View, 1);
   if (!v) return;
   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   if (!cfdata)
     {
        E_FREE(v);
        return;
     }
   cfdata->desktop = desktop;
   if (cfdata->desktop) efreet_desktop_ref(cfdata->desktop);
   if (exec) cfdata->exec = strdup(exec);
   cfdata->error = exe_error;
   cfdata->read = exe_read;
   cfdata->event = *exe_event;

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.create_widgets = _basic_create_widgets;
   v->advanced.create_widgets = _advanced_create_widgets;

   /* Create The Dialog */
   e_config_dialog_new(NULL, _("Application Execution Error"),
                       "E", "_e_exec_error_exit_dialog",
                       NULL, 0, v, cfdata);
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   char buf[4096];

   if (!cfdata->label)
     {
        if (cfdata->desktop)
          snprintf(buf, sizeof(buf), _("%s stopped running unexpectedly."), cfdata->desktop->name);
        else
          snprintf(buf, sizeof(buf), _("%s stopped running unexpectedly."), cfdata->exec);
        cfdata->label = strdup(buf);
     }
   if ((cfdata->event.exited) && (!cfdata->exit))
     {
        snprintf(buf, sizeof(buf),
                 _("An exit code of %i was returned from %s."),
                 cfdata->event.exit_code, cfdata->exec);
        cfdata->exit = strdup(buf);
     }
   if ((cfdata->event.signalled) && (!cfdata->signal))
     {
        if (cfdata->event.exit_signal == SIGINT)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by an Interrupt Signal."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGQUIT)
          snprintf(buf, sizeof(buf), _("%s was interrupted by a Quit Signal."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGABRT)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by an Abort Signal."), cfdata->exec);
        else if (cfdata->event.exit_signal == SIGFPE)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by a Floating Point Error."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGKILL)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by an Uninterruptable Kill Signal."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGSEGV)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by a Segmentation Fault."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGPIPE)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by a Broken Pipe."), cfdata->exec);
        else if (cfdata->event.exit_signal == SIGTERM)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by a Termination Signal."),
                   cfdata->exec);
        else if (cfdata->event.exit_signal == SIGBUS)
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by a Bus Error."), cfdata->exec);
        else
          snprintf(buf, sizeof(buf),
                   _("%s was interrupted by the signal number %i."),
                   cfdata->exec, cfdata->event.exit_signal);
        cfdata->signal = strdup(buf);
        /* FIXME: Add  sigchld_info stuff
         * cfdata->event.data
         *    siginfo_t
         *    {
         *       int      si_signo;     Signal number
         *       int      si_errno;     An errno value
         *       int      si_code;      Signal code
         *       pid_t    si_pid;       Sending process ID
         *       uid_t    si_uid;       Real user ID of sending process
         *       int      si_status;    Exit value or signal
         *       clock_t  si_utime;     User time consumed
         *       clock_t  si_stime;     System time consumed
         *       sigval_t si_value;     Signal value
         *       int      si_int;       POSIX.1b signal
         *       void *   si_ptr;       POSIX.1b signal
         *       void *   si_addr;      Memory location which caused fault
         *       int      si_band;      Band event
         *       int      si_fd;        File descriptor
         *    }
         */
     }
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = cfd->data;
   _fill_data(cfdata);
   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   if (cfdata->error) ecore_exe_event_data_free(cfdata->error);
   if (cfdata->read) ecore_exe_event_data_free(cfdata->read);

   if (cfdata->desktop) efreet_desktop_free(cfdata->desktop);

   E_FREE(cfdata->exec);
   E_FREE(cfdata->signal);
   E_FREE(cfdata->exit);
   E_FREE(cfdata->label);
   E_FREE(cfdata);
}

static Evas_Object *
_dialog_scrolltext_create(Evas *evas, char *title, Ecore_Exe_Event_Data_Line *lines)
{
   Evas_Object *obj, *os;
   char *text;
   char *trunc_note = _("***The remaining output has been truncated. Save the output to view.***\n");
   int tlen, max_lines, i;

   os = e_widget_framelist_add(evas, _(title), 0);

   obj = e_widget_textblock_add(evas);

   tlen = 0;
   for (i = 0; lines[i].line; i++)
     {
        tlen += lines[i].size + 1;
        /* When the program output is extraordinarily long, it can cause
         * significant delays during text rendering. Limit to a fixed
         * number of characters. */
        if (tlen > MAX_OUTPUT_CHARACTERS)
          {
             tlen -= lines[i].size + 1;
             tlen += strlen(trunc_note);
             break;
          }
     }
   max_lines = i;
   text = alloca(tlen + 1);

   text[0] = 0;
   for (i = 0; i < max_lines; i++)
     {
        strcat(text, lines[i].line);
        strcat(text, "\n");
     }

   /* Append the warning about truncated output. */
   if (lines[max_lines].line) strcat(text, trunc_note);

   e_widget_textblock_plain_set(obj, text);
   e_widget_size_min_set(obj, 240, 120);

   e_widget_framelist_object_append(os, obj);

   return os;
}

static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   char buf[4096];
   int error_length = 0;
   Evas_Object *o, *ob, *os;

   _fill_data(cfdata);

   o = e_widget_list_add(evas, 0, 0);

   ob = e_widget_label_add(evas, cfdata->label);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);

   if (cfdata->error) error_length = cfdata->error->size;
   if (error_length)
     {
        os = _dialog_scrolltext_create(evas, _("Error Logs"),
                                       cfdata->error->lines);
        e_widget_list_object_append(o, os, 1, 1, 0.5);
     }
   else
     {
        ob = e_widget_label_add(evas, _("There was no error message."));
        e_widget_list_object_append(o, ob, 1, 1, 0.5);
     }

   ob = e_widget_button_add(evas, _("Save This Message"), "system-run",
                            _dialog_save_cb, NULL, cfdata);
   e_widget_list_object_append(o, ob, 0, 0, 0.5);

   if (cfdata->desktop)
     snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"),
              e_user_homedir_get(), cfdata->desktop->name);
   else
     snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"),
              e_user_homedir_get(), "Error");
   ob = e_widget_label_add(evas, buf);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);

   return o;
}

static Evas_Object *
_advanced_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   char buf[4096];
   int read_length = 0;
   int error_length = 0;
   Evas_Object *o, *of, *ob, *ot;

   _fill_data(cfdata);

   o = e_widget_list_add(evas, 0, 0);
   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   ob = e_widget_label_add(evas, cfdata->label);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);

   if (cfdata->exit)
     {
        of = e_widget_framelist_add(evas, _("Error Information"), 0);
        ob = e_widget_label_add(evas, _(cfdata->exit));
        e_widget_framelist_object_append(of, ob);
        e_widget_list_object_append(o, of, 1, 1, 0.5);
     }

   if (cfdata->signal)
     {
        of = e_widget_framelist_add(evas, _("Error Signal Information"), 0);
        ob = e_widget_label_add(evas, _(cfdata->signal));
        e_widget_framelist_object_append(of, ob);
        e_widget_list_object_append(o, of, 1, 1, 0.5);
     }

   if (cfdata->read) read_length = cfdata->read->size;

   if (read_length)
     {
        of = _dialog_scrolltext_create(evas, _("Output Data"),
                                       cfdata->read->lines);
        /* FIXME: Add stdout "start". */
        /* FIXME: Add stdout "end". */
     }
   else
     {
        of = e_widget_framelist_add(evas, _("Output Data"), 0);
        ob = e_widget_label_add(evas, _("There was no output."));
        e_widget_framelist_object_append(of, ob);
     }
   e_widget_table_object_append(ot, of, 0, 0, 1, 1, 1, 1, 1, 1);

   if (cfdata->error) error_length = cfdata->error->size;
   if (error_length)
     {
        of = _dialog_scrolltext_create(evas, _("Error Logs"),
                                       cfdata->error->lines);
        /* FIXME: Add stderr "start". */
        /* FIXME: Add stderr "end". */
     }
   else
     {
        of = e_widget_framelist_add(evas, _("Error Logs"), 0);
        ob = e_widget_label_add(evas, _("There was no error message."));
        e_widget_framelist_object_append(of, ob);
     }
   e_widget_table_object_append(ot, of, 1, 0, 1, 1, 1, 1, 1, 1);

   e_widget_list_object_append(o, ot, 1, 1, 0.5);

   ob = e_widget_button_add(evas, _("Save This Message"), "system-run",
                            _dialog_save_cb, NULL, cfdata);
   e_widget_list_object_append(o, ob, 0, 0, 0.5);

   if (cfdata->desktop)
     snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"),
              e_user_homedir_get(), cfdata->desktop->name);
   else
     snprintf(buf, sizeof(buf), _("This error log will be saved as %s/%s.log"),
              e_user_homedir_get(), "Error");
   ob = e_widget_label_add(evas, buf);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);

   return o;
}

static void
_dialog_save_cb(void *data EINA_UNUSED, void *data2)
{
   E_Config_Dialog_Data *cfdata;
   FILE *f;
   char *text;
   char buf[1024];
   char buffer[4096];
   int read_length = 0;
   int i, tlen;

   cfdata = data2;

   if (cfdata->desktop)
     snprintf(buf, sizeof(buf), "%s/%s.log", e_user_homedir_get(),
              e_util_filename_escape(cfdata->desktop->name));
   else
     snprintf(buf, sizeof(buf), "%s/%s.log", e_user_homedir_get(),
              "Error");
   f = fopen(buf, "w");
   if (!f) return;

   if (cfdata->exit)
     {
        snprintf(buffer, sizeof(buffer), "Error Information:\n\t%s\n\n",
                 cfdata->exit);
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }
   if (cfdata->signal)
     {
        snprintf(buffer, sizeof(buffer), "Error Signal Information:\n\t%s\n\n",
                 cfdata->signal);
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }

   if (cfdata->read) read_length = cfdata->read->size;

   if (read_length)
     {
        tlen = 0;
        for (i = 0; cfdata->read->lines[i].line; i++)
          tlen += cfdata->read->lines[i].size + 2;
        text = alloca(tlen + 1);
        text[0] = 0;
        for (i = 0; cfdata->read->lines[i].line; i++)
          {
             strcat(text, "\t");
             strcat(text, cfdata->read->lines[i].line);
             strcat(text, "\n");
          }
        snprintf(buffer, sizeof(buffer), "Output Data:\n%s\n\n", text);
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }
   else
     {
        snprintf(buffer, sizeof(buffer), "Output Data:\n\tThere was no output\n\n");
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }

   /* Reusing this var */
   read_length = 0;
   if (cfdata->error) read_length = cfdata->error->size;

   if (read_length)
     {
        tlen = 0;
        for (i = 0; cfdata->error->lines[i].line; i++)
          tlen += cfdata->error->lines[i].size + 1;
        text = alloca(tlen + 1);
        text[0] = 0;
        for (i = 0; cfdata->error->lines[i].line; i++)
          {
             strcat(text, "\t");
             strcat(text, cfdata->error->lines[i].line);
             strcat(text, "\n");
          }
        snprintf(buffer, sizeof(buffer), "Error Logs:\n%s\n", text);
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }
   else
     {
        snprintf(buffer, sizeof(buffer), "Error Logs:\n\tThere was no error message\n");
        fwrite(buffer, sizeof(char), strlen(buffer), f);
     }

   fclose(f);
}

E_API const Eina_List *
e_exec_desktop_instances_find(const Efreet_Desktop *desktop)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, NULL);
   return eina_hash_find(e_exec_instances, desktop->orig_path ?: desktop->name);
}

E_API const Eina_Hash *
e_exec_instances_get(void)
{
   return e_exec_instances;
}
#include "e.h"
#include <libgen.h>

E_API int E_EVENT_EXEHIST_UPDATE = 0;

/* local subsystem functions */
typedef struct _E_Exehist      E_Exehist;
typedef struct _E_Exehist_Item E_Exehist_Item;

struct _E_Exehist
{
   Eina_List *history;
   Eina_List *mimes;
   int startup_id;
};

struct _E_Exehist_Item
{
   const char  *exe;
   const char  *normalized_exe;
   const char  *launch_method;
   double       exetime;
   unsigned int count;
};

static void        _e_exehist_unload_queue(void);
static void        _e_exehist_load(void);
static void        _e_exehist_clear(void);
static void        _e_exehist_unload(void);
static void        _e_exehist_limit(void);
static const char *_e_exehist_normalize_exe(const char *exe);
static void        _e_exehist_cb_unload(void *data);
static int         _e_exehist_sort_exe_cb(const void *d1, const void *d2);
static int         _e_exehist_sort_pop_cb(const void *d1, const void *d2);

/* local subsystem globals */
static E_Config_DD *_e_exehist_config_edd = NULL;
static E_Config_DD *_e_exehist_config_item_edd = NULL;
static E_Exehist *_e_exehist = NULL;
static E_Powersave_Deferred_Action *_e_exehist_unload_defer = NULL;
static int _e_exehist_changes = 0;

static void
_upgrade_defaults_to_mimeapps(void)
{
   char buf[PATH_MAX], buf2[PATH_MAX];

   snprintf(buf, sizeof(buf), "%s/mimeapps.list",
            efreet_config_home_get());
   if (ecore_file_exists(buf)) return;
   ecore_file_mkpath(efreet_config_home_get());
   snprintf(buf2, sizeof(buf2), "%s/applications/defaults.list",
            efreet_data_home_get());
   ecore_file_cp(buf2, buf);
}

/* externally accessible functions */
EINTERN int
e_exehist_init(void)
{
   _e_exehist_config_item_edd = E_CONFIG_DD_NEW("E_Exehist_Item", E_Exehist_Item);
#undef T
#undef D
#define T E_Exehist_Item
#define D _e_exehist_config_item_edd
   E_CONFIG_VAL(D, T, exe, STR);
   E_CONFIG_VAL(D, T, normalized_exe, STR);
   E_CONFIG_VAL(D, T, launch_method, STR);
   E_CONFIG_VAL(D, T, exetime, DOUBLE);

   _e_exehist_config_edd = E_CONFIG_DD_NEW("E_Exehist", E_Exehist);
#undef T
#undef D
#define T E_Exehist
#define D _e_exehist_config_edd
   E_CONFIG_LIST(D, T, history, _e_exehist_config_item_edd);
   E_CONFIG_LIST(D, T, mimes, _e_exehist_config_item_edd);
   E_CONFIG_VAL(D, T, startup_id, INT);

   E_EVENT_EXEHIST_UPDATE = ecore_event_type_new();

   _upgrade_defaults_to_mimeapps();

   return 1;
}

EINTERN int
e_exehist_shutdown(void)
{
   if (_e_exehist_unload_defer)
     {
        e_powersave_deferred_action_del(_e_exehist_unload_defer);
        _e_exehist_unload_defer = NULL;
     }
   _e_exehist_cb_unload(NULL);
   E_CONFIG_DD_FREE(_e_exehist_config_item_edd);
   E_CONFIG_DD_FREE(_e_exehist_config_edd);
   return 1;
}

E_API void
e_exehist_startup_id_set(int id)
{
   _e_exehist_load();
   if (!_e_exehist) return;
   _e_exehist->startup_id = id;
   _e_exehist_changes++;
   _e_exehist_unload_queue();
}

E_API int
e_exehist_startup_id_get(void)
{
   int id;
   _e_exehist_load();
   if (!_e_exehist) return 0;
   id = _e_exehist->startup_id;
   _e_exehist_unload_queue();
   return id;
}

E_API void
e_exehist_add(const char *launch_method, const char *exe)
{
   E_Exehist_Item *ei;

   _e_exehist_load();
   if (!_e_exehist) return;
   ei = E_NEW(E_Exehist_Item, 1);
   if (!ei)
     {
        _e_exehist_unload_queue();
        return;
     }
   ei->launch_method = eina_stringshare_add(launch_method);
   ei->exe = eina_stringshare_add(exe);
   ei->normalized_exe = _e_exehist_normalize_exe(exe);
   ei->exetime = ecore_time_unix_get();
   _e_exehist->history = eina_list_append(_e_exehist->history, ei);
   _e_exehist_limit();
   _e_exehist_changes++;
   ecore_event_add(E_EVENT_EXEHIST_UPDATE, NULL, NULL, NULL);
   _e_exehist_unload_queue();
}

E_API void
e_exehist_del(const char *exe)
{
   E_Exehist_Item *ei;
   Eina_List *l;
   Eina_Bool ok = EINA_FALSE;

   _e_exehist_load();
   if (!_e_exehist) return;
   EINA_LIST_FOREACH(_e_exehist->history, l, ei)
     {
        if ((ei->exe) && (!strcmp(exe, ei->exe)))
          {
             eina_stringshare_del(ei->exe);
             eina_stringshare_del(ei->normalized_exe);
             eina_stringshare_del(ei->launch_method);
             free(ei);
             _e_exehist->history = eina_list_remove_list(_e_exehist->history,
                                                         l);
             _e_exehist_changes++;
             _e_exehist_unload_queue();
             ok = EINA_TRUE;
          }
     }
   if (ok)
     ecore_event_add(E_EVENT_EXEHIST_UPDATE, NULL, NULL, NULL);
}

E_API void
e_exehist_clear(void)
{
   _e_exehist_load();
   if (!_e_exehist) return;
   _e_exehist_clear();
   _e_exehist_changes++;
   ecore_event_add(E_EVENT_EXEHIST_UPDATE, NULL, NULL, NULL);
   _e_exehist_unload_queue();
}

E_API int
e_exehist_popularity_get(const char *exe)
{
   Eina_List *l;
   E_Exehist_Item *ei;
   const char *normal;
   int count = 0;

   _e_exehist_load();
   if (!_e_exehist) return 0;
   normal = _e_exehist_normalize_exe(exe);
   if (!normal) return 0;
   EINA_LIST_FOREACH(_e_exehist->history, l, ei)
     {
        if ((ei->normalized_exe) && (!strcmp(normal, ei->normalized_exe)))
          count++;
     }
   eina_stringshare_del(normal);
   _e_exehist_unload_queue();
   return count;
}

E_API double
e_exehist_newest_run_get(const char *exe)
{
   Eina_List *l;
   E_Exehist_Item *ei;
   const char *normal;

   _e_exehist_load();
   if (!_e_exehist) return 0.0;
   normal = _e_exehist_normalize_exe(exe);
   if (!normal) return 0.0;
   EINA_LIST_REVERSE_FOREACH(_e_exehist->history, l, ei)
     {
        if ((ei->normalized_exe) && (!strcmp(normal, ei->normalized_exe)))
          {
             eina_stringshare_del(normal);
             _e_exehist_unload_queue();
             return ei->exetime;
          }
     }
   eina_stringshare_del(normal);
   _e_exehist_unload_queue();
   return 0.0;
}

E_API Eina_List *
e_exehist_list_get(void)
{
   return e_exehist_sorted_list_get(E_EXEHIST_SORT_BY_DATE, 0);
}

E_API Eina_List *
e_exehist_sorted_list_get(E_Exehist_Sort sort_type, int max)
{
   Eina_List *list = NULL, *pop = NULL, *l = NULL, *m;
   Eina_Iterator *iter;
   E_Exehist_Item *ei;
   int count = 1;
   E_Exehist_Item *prev = NULL;

   if (!max) max = 20;
   _e_exehist_load();
   switch (sort_type)
     {
      case E_EXEHIST_SORT_BY_EXE:
      case E_EXEHIST_SORT_BY_POPULARITY:
        l = eina_list_clone(_e_exehist->history);
        l = eina_list_sort(l, 0, _e_exehist_sort_exe_cb);
        iter = eina_list_iterator_new(l);
        break;

      default:
        iter = eina_list_iterator_reversed_new(_e_exehist->history);
        break;
     }
   EINA_ITERATOR_FOREACH(iter, ei)
     {
        int bad = 0;

        if (!(ei->normalized_exe)) continue;
        if (sort_type == E_EXEHIST_SORT_BY_POPULARITY)
          {
             if (!prev || (strcmp(prev->normalized_exe, ei->normalized_exe)))
               {
                  prev = ei;
                  pop = eina_list_append(pop, ei);
               }
             prev->count++;
          }
        else
          {
             const char *exe;

             EINA_LIST_FOREACH(list, m, exe)
               {
                  if (!exe) continue;
                  if (!strcmp(exe, ei->exe))
                    {
                       bad = 1;
                       break;
                    }
               }
             if (!(bad))
               {
                  list = eina_list_append(list, ei->exe);
                  count++;
               }
          }
        if (count > max) break;
     }
   if (sort_type == E_EXEHIST_SORT_BY_POPULARITY)
     {
        count = 1;
        pop = eina_list_sort(pop, 0, _e_exehist_sort_pop_cb);
        EINA_LIST_FOREACH(pop, l, prev)
          {
             list = eina_list_append(list, prev->exe);
             count++;
             if (count > max) break;
          }
        eina_list_free(pop);
     }
   eina_list_free(l);
   eina_iterator_free(iter);
   _e_exehist_unload_queue();
   return list;
}

E_API void
e_exehist_mime_desktop_add(const char *mime, Efreet_Desktop *desktop)
{
   const char *f;
   E_Exehist_Item *ei;
   Eina_List *l;
   char buf[PATH_MAX];
   Efreet_Ini *ini;

   if ((!mime) || (!desktop)) return;
   if (!desktop->orig_path) return;
   _e_exehist_load();
   if (!_e_exehist) return;

   f = efreet_util_path_to_file_id(desktop->orig_path);
   if (!f) return;

   snprintf(buf, sizeof(buf), "%s/mimeapps.list",
            efreet_config_home_get());
   ini = efreet_ini_new(buf);
   //fprintf(stderr, "try open %s = %p\n", buf, ini);
   if (ini)
     {
        //fprintf(stderr, "SAVE mime %s with %s\n", mime, desktop->orig_path);
        if (!efreet_ini_section_set(ini, "Default Applications"))
          {
             efreet_ini_section_add(ini, "Default Applications");
             efreet_ini_section_set(ini, "Default Applications");
          }
        if (desktop->orig_path)
          efreet_ini_string_set(ini, mime, ecore_file_file_get(desktop->orig_path));
        efreet_ini_save(ini, buf);
        efreet_ini_free(ini);
     }

   EINA_LIST_FOREACH(_e_exehist->mimes, l, ei)
     {
        if ((ei->launch_method) && (!strcmp(mime, ei->launch_method)))
          {
             if ((ei->exe) && (!strcmp(f, ei->exe)))
               {
                  _e_exehist_unload_queue();
                  return;
               }
             if (ei->exe) eina_stringshare_del(ei->exe);
             if (ei->launch_method) eina_stringshare_del(ei->launch_method);
             free(ei);
             _e_exehist->mimes = eina_list_remove_list(_e_exehist->mimes, l);
             _e_exehist_changes++;
             break;
          }
     }
   ei = E_NEW(E_Exehist_Item, 1);
   if (!ei)
     {
        _e_exehist_unload_queue();
        return;
     }
   ei->launch_method = eina_stringshare_add(mime);
   ei->exe = eina_stringshare_add(f);
   ei->exetime = ecore_time_unix_get();
   _e_exehist->mimes = eina_list_append(_e_exehist->mimes, ei);
   _e_exehist_limit();
   _e_exehist_changes++;
   _e_exehist_unload_queue();
}

E_API Efreet_Desktop *
e_exehist_mime_desktop_get(const char *mime)
{
   Efreet_Desktop *desktop;
   E_Exehist_Item *ei;
   Eina_List *l;

   //fprintf(stderr, "e_exehist_mime_desktop_get(%s)\n", mime);
   if (!mime) return NULL;
   _e_exehist_load();
   //fprintf(stderr, "x\n");
   if (!_e_exehist) return NULL;
   EINA_LIST_FOREACH(_e_exehist->mimes, l, ei)
     {
        //fprintf(stderr, "look for %s == %s\n", mime, ei->launch_method);
        if ((ei->launch_method) && (!strcmp(mime, ei->launch_method)))
          {
             desktop = NULL;
             if (ei->exe) desktop = efreet_util_desktop_file_id_find(ei->exe);
             //fprintf(stderr, "  desk = %p\n", desktop);
             if (desktop)
               {
                  _e_exehist_unload_queue();
                  return desktop;
               }
          }
     }
   _e_exehist_unload_queue();
   return NULL;
}

/* local subsystem functions */
static void
_e_exehist_unload_queue(void)
{
   if (_e_exehist_unload_defer)
     e_powersave_deferred_action_del(_e_exehist_unload_defer);
   _e_exehist_unload_defer =
     e_powersave_deferred_action_add(_e_exehist_cb_unload, NULL);
}

static void
_e_exehist_load(void)
{
   if (!_e_exehist)
     _e_exehist = e_config_domain_load("exehist", _e_exehist_config_edd);
   if (!_e_exehist)
     _e_exehist = E_NEW(E_Exehist, 1);
}

static void
_e_exehist_clear(void)
{
   if (_e_exehist)
     {
        E_Exehist_Item *ei;
        EINA_LIST_FREE(_e_exehist->history, ei)
          {
             eina_stringshare_del(ei->exe);
             eina_stringshare_del(ei->normalized_exe);
             eina_stringshare_del(ei->launch_method);
             free(ei);
          }
        EINA_LIST_FREE(_e_exehist->mimes, ei)
          {
             eina_stringshare_del(ei->exe);
             eina_stringshare_del(ei->launch_method);
             free(ei);
          }
     }
}

static void
_e_exehist_unload(void)
{
   _e_exehist_clear();
   E_FREE(_e_exehist);
}

static void
_e_exehist_limit(void)
{
   /* go from first item in hist on and either delete all items before a
    * specific timestamp, or if the list count > limit then delete items
    *
    * for now - limit to 500
    */
   if (_e_exehist)
     {
        while (eina_list_count(_e_exehist->history) > 500)
          {
             E_Exehist_Item *ei;

             ei = eina_list_data_get(_e_exehist->history);
             eina_stringshare_del(ei->exe);
             eina_stringshare_del(ei->normalized_exe);
             eina_stringshare_del(ei->launch_method);
             free(ei);
             _e_exehist->history = eina_list_remove_list(_e_exehist->history, _e_exehist->history);
          }
        while (eina_list_count(_e_exehist->mimes) > 500)
          {
             E_Exehist_Item *ei;

             ei = eina_list_data_get(_e_exehist->mimes);
             eina_stringshare_del(ei->exe);
             eina_stringshare_del(ei->launch_method);
             free(ei);
             _e_exehist->mimes = eina_list_remove_list(_e_exehist->mimes, _e_exehist->mimes);
          }
     }
}

static const char *
_e_exehist_normalize_exe(const char *exe)
{
   char *base, *buf, *cp, *space = NULL;
   const char *ret;
   Eina_Bool flag = EINA_FALSE;

   buf = strdup(exe);
   base = basename(buf);
   if ((base[0] == '.') && (base[1] == '\0'))
     {
        free(buf);
        return NULL;
     }

   cp = base;
   while (*cp)
     {
        if (isspace(*cp))
          {
             if (!space) space = cp;
             if (flag) flag = EINA_FALSE;
          }
        else if (!flag)
          {
             /* usually a variable in the desktop exe field */
             if (space && *cp == '%')
               flag = EINA_TRUE;
             else
               {
                  char lower = tolower(*cp);

                  space = NULL;
                  if (lower != *cp) *cp = lower;
               }
          }
        cp++;
     }

   if (space) *space = '\0';

   ret = eina_stringshare_add(base);
   free(buf);

   return ret;
}

static void
_e_exehist_cb_unload(void *data EINA_UNUSED)
{
   if (_e_exehist_changes)
     {
        e_config_domain_save("exehist", _e_exehist_config_edd, _e_exehist);
        _e_exehist_changes = 0;
     }
   _e_exehist_unload();
   _e_exehist_unload_defer = NULL;
}

static int
_e_exehist_sort_exe_cb(const void *d1, const void *d2)
{
   const E_Exehist_Item *ei1, *ei2;

   ei1 = d1;
   ei2 = d2;

   if ((!ei1) || (!ei1->normalized_exe)) return 1;
   if ((!ei2) || (!ei2->normalized_exe)) return -1;

   return strcmp(ei1->normalized_exe, ei2->normalized_exe);
}

static int
_e_exehist_sort_pop_cb(const void *d1, const void *d2)
{
   const E_Exehist_Item *ei1, *ei2;

   if (!(ei1 = d1)) return 1;
   if (!(ei2 = d2)) return -1;

   return ei2->count - ei1->count;
}

#include "e.h"

/*
 * Implementation of a protected file registry. Any files that are
 * currently being used by E in core components should be registered
 * here and will be protected as best as E can. :)
 */
static Eina_Hash *_e_filereg = NULL;

typedef struct _Filereg_Item Filereg_Item;
struct _Filereg_Item
{
   const char *path;
   int         ref_count;
};

static Eina_Bool _filereg_hash_cb_free(const Eina_Hash *hash EINA_UNUSED,
                                       const void *key EINA_UNUSED,
                                       void *data, void *fdata EINA_UNUSED);

/* Externally accessible functions */
EINTERN int
e_filereg_init(void)
{
   _e_filereg = eina_hash_string_superfast_new(NULL);

   return 1;
}

EINTERN int
e_filereg_shutdown(void)
{
   eina_hash_foreach(_e_filereg, _filereg_hash_cb_free, NULL);
   eina_hash_free(_e_filereg);
   _e_filereg = NULL;
   return 1;
}

E_API int
e_filereg_register(const char *path)
{
   Filereg_Item *fi = NULL;

   fi = eina_hash_find(_e_filereg, path);
   if (fi)
     {
        fi->ref_count++;
        return 1;
     }
   fi = E_NEW(Filereg_Item, 1);
   if (!fi) return 0;
   fi->path = eina_stringshare_add(path);
   fi->ref_count = 1;
   eina_hash_add(_e_filereg, path, fi);
   return 1;
}

E_API void
e_filereg_deregister(const char *path)
{
   Filereg_Item *fi = NULL;

   fi = eina_hash_find(_e_filereg, path);
   if (fi)
     {
        fi->ref_count--;
        if (fi->ref_count == 0)
          {
             eina_hash_del(_e_filereg, path, fi);
             if (fi->path) eina_stringshare_del(fi->path);
             E_FREE(fi);
          }
     }
}

E_API Eina_Bool
e_filereg_file_protected(const char *path)
{
   if (eina_hash_find(_e_filereg, path)) return EINA_TRUE;
   return EINA_FALSE;
}

/* Private Functions */
static Eina_Bool
_filereg_hash_cb_free(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED,
                      void *data, void *fdata EINA_UNUSED)
{
   Filereg_Item *fi;

   fi = data;
   if (!fi) return EINA_TRUE;
   if (fi->path) eina_stringshare_del(fi->path);
   E_FREE(fi);
   return EINA_TRUE;
}

#include "e.h"

typedef struct _E_Smart_Data      E_Smart_Data;
typedef struct _E_Flowlayout_Item E_Flowlayout_Item;

struct _E_Smart_Data
{
   Evas_Coord    x, y, w, h;
   Evas_Object  *obj;
   Evas_Object  *clip;
   int           frozen;
   unsigned char changed E_BITFIELD;
   unsigned char horizontal E_BITFIELD;
   unsigned char homogenous E_BITFIELD;
   unsigned char fill E_BITFIELD;
   unsigned char flowright E_BITFIELD;
   unsigned char flowbottom E_BITFIELD;
   Eina_List    *items;
   struct
   {
      Evas_Coord w, h;
   } min, max;
   struct
   {
      double x, y;
   } align;
   int           rows;
   int           cols;
};

struct _E_Flowlayout_Item
{
   E_Smart_Data *sd;
   unsigned char fill_w E_BITFIELD;
   unsigned char fill_h E_BITFIELD;
   unsigned char expand_w E_BITFIELD;
   unsigned char expand_h E_BITFIELD;
   struct
   {
      Evas_Coord w, h;
   } min, max;
   struct
   {
      double x, y;
   } align;
   Evas_Object  *obj;
};

/* local subsystem functions */
static E_Flowlayout_Item *_e_flowlayout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj);
static void               _e_flowlayout_smart_disown(Evas_Object *obj);
static void               _e_flowlayout_smart_item_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void               _e_flowlayout_smart_reconfigure(E_Smart_Data *sd);
static void               _e_flowlayout_smart_extents_calcuate(E_Smart_Data *sd);

static void               _e_flowlayout_smart_init(void);
static void               _e_flowlayout_smart_add(Evas_Object *obj);
static void               _e_flowlayout_smart_del(Evas_Object *obj);
static void               _e_flowlayout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void               _e_flowlayout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void               _e_flowlayout_smart_show(Evas_Object *obj);
static void               _e_flowlayout_smart_hide(Evas_Object *obj);
static void               _e_flowlayout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void               _e_flowlayout_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void               _e_flowlayout_smart_clip_unset(Evas_Object *obj);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_flowlayout_add(Evas *evas)
{
   _e_flowlayout_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API int
e_flowlayout_freeze(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   sd->frozen++;
   return sd->frozen;
}

E_API int
e_flowlayout_thaw(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   sd->frozen--;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
   return sd->frozen;
}

E_API void
e_flowlayout_orientation_set(Evas_Object *obj, int horizontal)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (((sd->horizontal) && (horizontal)) ||
       ((!sd->horizontal) && (!horizontal))) return;
   sd->horizontal = horizontal;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

E_API int
e_flowlayout_orientation_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   return sd->horizontal;
}

E_API void
e_flowlayout_flowdirection_set(Evas_Object *obj, int right, int bottom)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((((sd->flowbottom) && (bottom)) ||
        ((!sd->flowbottom) && (!bottom))) &&
       (((sd->flowright) && (right)) ||
        ((!sd->flowright) && (!right)))) return;
   sd->flowright = right;
   sd->flowbottom = bottom;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

E_API void
e_flowlayout_flowdirection_get(Evas_Object *obj, int *right, int *bottom)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (right) *right = sd->flowright;
   if (bottom) *bottom = sd->flowright;
}

E_API void
e_flowlayout_homogenous_set(Evas_Object *obj, int homogenous)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->homogenous == homogenous) return;
   sd->homogenous = homogenous;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

E_API int
e_flowlayout_homogenous_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   return sd->homogenous;
}

E_API void
e_flowlayout_fill_set(Evas_Object *obj, int fill)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->fill == fill) return;
   sd->fill = fill;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

E_API int
e_flowlayout_fill_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   return sd->fill;
}

E_API int
e_flowlayout_pack_start(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   _e_flowlayout_smart_adopt(sd, child);
   sd->items = eina_list_prepend(sd->items, child);
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
   return 0;
}

E_API int
e_flowlayout_pack_end(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   _e_flowlayout_smart_adopt(sd, child);
   sd->items = eina_list_append(sd->items, child);
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
   return (int)eina_list_count(sd->items) - 1;
}

E_API int
e_flowlayout_pack_before(Evas_Object *obj, Evas_Object *child, Evas_Object *before)
{
   E_Smart_Data *sd;
   int i = 0;
   Eina_List *l;
   Evas_Object *item;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   _e_flowlayout_smart_adopt(sd, child);
   sd->items = eina_list_prepend_relative(sd->items, child, before);
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        if (item == child) break;
        i++;
     }
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
   return i;
}

E_API int
e_flowlayout_pack_after(Evas_Object *obj, Evas_Object *child, Evas_Object *after)
{
   E_Smart_Data *sd;
   int i = 0;
   Eina_List *l;
   Evas_Object *item;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   _e_flowlayout_smart_adopt(sd, child);
   sd->items = eina_list_append_relative(sd->items, child, after);
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        if (item == child) break;
        i++;
     }
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
   return i;
}

E_API int
e_flowlayout_pack_count_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   return (int)eina_list_count(sd->items);
}

E_API Evas_Object *
e_flowlayout_pack_object_nth(Evas_Object *obj, int n)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return eina_list_nth(sd->items, n);
}

E_API Evas_Object *
e_flowlayout_pack_object_first(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return eina_list_data_get(sd->items);
}

E_API Evas_Object *
e_flowlayout_pack_object_last(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return eina_list_last_data_get(sd->items);
}

E_API Evas_Object *
e_flowlayout_pack_object_prev(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;
   Evas_Object *o;
   Eina_List *l;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   EINA_LIST_FOREACH(sd->items, l, o)
     if (o == child) return eina_list_data_get(l->prev);
   return NULL;
}

E_API Evas_Object *
e_flowlayout_pack_object_next(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;
   Evas_Object *o;
   Eina_List *l;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   EINA_LIST_FOREACH(sd->items, l, o)
     if (o == child) return eina_list_data_get(l->next);
   return NULL;
}

E_API void
e_flowlayout_pack_options_set(Evas_Object *obj, int fill_w, int fill_h, int expand_w, int expand_h, double align_x, double align_y, Evas_Coord min_w, Evas_Coord min_h, Evas_Coord max_w, Evas_Coord max_h)
{
   E_Flowlayout_Item *bi;

   bi = evas_object_data_get(obj, "e_flowlayout_data");
   if (!bi) return;
   bi->fill_w = fill_w;
   bi->fill_h = fill_h;
   bi->expand_w = expand_w;
   bi->expand_h = expand_h;
   bi->align.x = align_x;
   bi->align.y = align_y;
   bi->min.w = min_w;
   bi->min.h = min_h;
   bi->max.w = max_w;
   bi->max.h = max_h;
   bi->sd->changed = 1;
   if (bi->sd->frozen <= 0) _e_flowlayout_smart_reconfigure(bi->sd);
}

E_API void
e_flowlayout_unpack(Evas_Object *obj)
{
   E_Flowlayout_Item *bi;
   E_Smart_Data *sd;

   bi = evas_object_data_get(obj, "e_flowlayout_data");
   if (!bi) return;
   sd = bi->sd;
   if (!sd) return;
   sd->items = eina_list_remove(sd->items, obj);
   _e_flowlayout_smart_disown(obj);
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

E_API void
e_flowlayout_size_min_get(Evas_Object *obj, Evas_Coord *minw, Evas_Coord *minh)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->changed) _e_flowlayout_smart_extents_calcuate(sd);
   if (minw) *minw = sd->min.w;
   if (minh) *minh = sd->min.h;
}

E_API void
e_flowlayout_size_max_get(Evas_Object *obj, Evas_Coord *maxw, Evas_Coord *maxh)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->changed) _e_flowlayout_smart_extents_calcuate(sd);
   if (maxw) *maxw = sd->max.w;
   if (maxh) *maxh = sd->max.h;
}

E_API int
e_flowlayout_max_children(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return -1;
   if (!sd->homogenous) return -1;
   return sd->rows * sd->cols;
}

E_API void
e_flowlayout_align_get(Evas_Object *obj, double *ax, double *ay)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (ax) *ax = sd->align.x;
   if (ay) *ay = sd->align.y;
}

E_API void
e_flowlayout_align_set(Evas_Object *obj, double ax, double ay)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (EINA_DBL_EQ(sd->align.x, ax) && EINA_DBL_EQ(sd->align.y, ay)) return;
   sd->align.x = ax;
   sd->align.y = ay;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_flowlayout_smart_reconfigure(sd);
}

/* local subsystem functions */
static E_Flowlayout_Item *
_e_flowlayout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj)
{
   E_Flowlayout_Item *bi;

   bi = calloc(1, sizeof(E_Flowlayout_Item));
   if (!bi) return NULL;
   bi->sd = sd;
   bi->obj = obj;
   /* defaults */
   bi->fill_w = 0;
   bi->fill_h = 0;
   bi->expand_w = 0;
   bi->expand_h = 0;
   bi->align.x = 0.5;
   bi->align.y = 0.5;
   bi->min.w = 0;
   bi->min.h = 0;
   bi->max.w = 0;
   bi->max.h = 0;
   evas_object_clip_set(obj, sd->clip);
   evas_object_smart_member_add(obj, bi->sd->obj);
   evas_object_data_set(obj, "e_flowlayout_data", bi);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
                                  _e_flowlayout_smart_item_del_hook, NULL);
   if ((!evas_object_visible_get(sd->clip)) &&
       (evas_object_visible_get(sd->obj)))
     evas_object_show(sd->clip);
   return bi;
}

static void
_e_flowlayout_smart_disown(Evas_Object *obj)
{
   E_Flowlayout_Item *bi;

   bi = evas_object_data_get(obj, "e_flowlayout_data");
   if (!bi) return;
   if (!bi->sd->items)
     {
        if (evas_object_visible_get(bi->sd->clip))
          evas_object_hide(bi->sd->clip);
     }
   evas_object_event_callback_del(obj,
                                  EVAS_CALLBACK_DEL,
                                  _e_flowlayout_smart_item_del_hook);
   evas_object_smart_member_del(obj);
   evas_object_clip_unset(obj);
   evas_object_data_del(obj, "e_flowlayout_data");
   free(bi);
}

static void
_e_flowlayout_smart_item_del_hook(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_flowlayout_unpack(obj);
}

static void
_e_flowlayout_smart_reconfigure(E_Smart_Data *sd)
{
   Evas_Coord x, y, w, h, xx, yy, cr, cc;
   Eina_List *l;
   Evas_Object *obj;
   int minw, minh;
   int count, expand;

   if (!sd->changed) return;

   /* local variables */
   x = sd->x;
   y = sd->y;
   w = sd->w;
   h = sd->h;

   /* Calculate extents */
   _e_flowlayout_smart_extents_calcuate(sd);
   minw = sd->min.w;
   minh = sd->min.h;
   count = eina_list_count(sd->items);
   expand = 0;

   /* Too small? move a little */
   if (w < minw)
     {
        x = x + ((w - minw) * (1.0 - sd->align.x));
        w = minw;
     }
   if (h < minh)
     {
        y = y + ((h - minh) * (1.0 - sd->align.y));
        h = minh;
     }

   /* Some odd expanding counter */
   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        E_Flowlayout_Item *bi;

        bi = evas_object_data_get(obj, "e_flowlayout_data");
        if (bi)
          {
             if (sd->horizontal)
               {
                  if (bi->expand_w) expand++;
               }
             else
               {
                  if (bi->expand_h) expand++;
               }
          }
     }

   /* If no expansion, w is minw, h is minh */
   if (expand == 0)
     {
        if (sd->horizontal)
          {
             x += (double)(w - minw) * sd->align.x;
             w = minw;
          }
        else
          {
             y += (double)(h - minh) * sd->align.y;
             h = minh;
          }
     }

   /* Some calculations */
   if (sd->flowright)
     xx = x;
   else
     xx = x + w;
   if (sd->flowbottom)
     yy = y;
   else
     yy = y + h;
   cr = 0;
   cc = 0;

   /* Now for the real deal */
   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        E_Flowlayout_Item *bi;

        bi = evas_object_data_get(obj, "e_flowlayout_data");
        if (bi)
          {
             /* Horiziontal */
             if (sd->horizontal)
               {
                  /* Homogenous */
                  if (sd->homogenous)
                    {
                       Evas_Coord ww, hh, ow, oh;
                       ww = w / sd->cols;
                       hh = h / sd->rows;
                       if (sd->fill)
                         {
                            int num = count - (cr) * (sd->cols);
                            if (num < sd->cols)
                              {
                                 ww = w / num;
                              }
                         }
                       ow = bi->min.w;
                       if (bi->fill_w) ow = ww;
                       if ((bi->max.w >= 0) && (bi->max.w < ow)) ow = bi->max.w;
                       oh = bi->min.h;
                       if (bi->fill_h) oh = hh;
                       if ((bi->max.h >= 0) && (bi->max.h < oh)) oh = bi->max.h;
                       if (sd->flowright)
                         {
                            if (sd->flowbottom)
                              {
                                 evas_object_move(obj,
                                                  xx + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            else
                              {
                                 evas_object_move(obj,
                                                  xx + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy - hh + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            evas_object_resize(obj, ow, oh);
                            xx += ww;
                         }
                       else
                         {
                            if (sd->flowbottom)
                              {
                                 evas_object_move(obj,
                                                  xx - ww + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            else
                              {
                                 evas_object_move(obj,
                                                  xx - ww + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy - hh + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            evas_object_resize(obj, ow, oh);
                            xx -= ww;
                         }
                       cc++;
                       if (cc >= sd->cols)
                         {
                            cc = 0;
                            cr++;
                            if (sd->flowright)
                              xx = x;
                            else
                              xx = x + w;
                            if (sd->flowbottom)
                              yy += hh;
                            else
                              yy -= hh;
                         }
                    }
                  /* TODO Nonhomogenous Horizontal */
                  else
                    {
                    }
               }
             /* Vertical */
             else
               {
                  if (sd->homogenous)
                    {
                       Evas_Coord ww, hh, ow, oh;
                       ww = w / sd->cols;
                       hh = h / sd->rows;
                       if (sd->fill)
                         {
                            int num = count - (cc) * (sd->rows);
                            if (num < sd->rows)
                              {
                                 hh = h / num;
                              }
                         }
                       oh = bi->min.h;
                       if (bi->fill_h) oh = hh;
                       if ((bi->max.h >= 0) && (bi->max.h < oh)) oh = bi->max.h;
                       ow = bi->min.w;
                       if (bi->fill_w) ow = ww;
                       if ((bi->max.w >= 0) && (bi->max.w < ow)) ow = bi->max.w;
                       if (sd->flowbottom)
                         {
                            if (sd->flowright)
                              {
                                 evas_object_move(obj,
                                                  xx + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            else
                              {
                                 evas_object_move(obj,
                                                  xx - ww + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            evas_object_resize(obj, ow, oh);
                            yy += hh;
                         }
                       else
                         {
                            if (sd->flowright)
                              {
                                 evas_object_move(obj,
                                                  xx + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy - hh + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            else
                              {
                                 evas_object_move(obj,
                                                  xx - ww + (Evas_Coord)(((double)(ww - ow)) * bi->align.x),
                                                  yy - hh + (Evas_Coord)(((double)(hh - oh)) * bi->align.y));
                              }
                            evas_object_resize(obj, ow, oh);
                            yy -= hh;
                         }
                       cr++;
                       if (cr >= sd->rows)
                         {
                            cr = 0;
                            cc++;
                            if (sd->flowbottom)
                              yy = y;
                            else
                              yy = y + h;
                            if (sd->flowright)
                              xx += ww;
                            else
                              xx -= ww;
                         }
                    }
                  /* TODO Nonhomogeneous Vertical */
                  else
                    {
                    }
               }
          }
     }
   sd->changed = 0;
}

static void
_e_flowlayout_smart_extents_calcuate(E_Smart_Data *sd)
{
   Eina_List *l;
   Evas_Object *obj;
   int minw, minh, count;

   /* FIXME: need to calc max */
   sd->max.w = -1; /* max < 0 == unlimited */
   sd->max.h = -1;

   minw = 1;
   minh = 1;
   if (sd->homogenous)
     {
        EINA_LIST_FOREACH(sd->items, l, obj)
          {
             E_Flowlayout_Item *bi;

             bi = evas_object_data_get(obj, "e_flowlayout_data");
             if (bi)
               {
                  if (sd->horizontal)
                    {
                       if (minh < bi->min.h) minh = bi->min.h;
                       if (minw < bi->min.w) minw = bi->min.w;
                    }
                  else
                    {
                       if (minw < bi->min.w) minw = bi->min.w;
                       if (minh < bi->min.h) minh = bi->min.h;
                    }
               }
          }
        if (sd->horizontal)
          {
             sd->rows = sd->h / minh;
             if (sd->rows < 1)
               sd->rows = 1;
             count = eina_list_count(sd->items);
             sd->cols = sd->w / minw;
             if (count < sd->cols) sd->cols = count;
             sd->rows = 0;
             if (sd->cols > 0)
               {
                  while (count > 0)
                    {
                       count -= sd->cols;
                       sd->rows++;
                    }
               }
          }
        else
          {
             sd->cols = sd->w / minw;
             if (sd->cols < 1)
               sd->cols = 1;
             count = eina_list_count(sd->items);
             sd->rows = sd->h / minh;
             if (count < sd->rows) sd->rows = count;
             sd->cols = 0;
             if (sd->rows > 0)
               {
                  while (count > 0)
                    {
                       count -= sd->rows;
                       sd->cols++;
                    }
               }
          }

        minw *= sd->cols;
        minh *= sd->rows;
     }
   /* TODO nonhomogenous */
   else
     {
        EINA_LIST_FOREACH(sd->items, l, obj)
          {
             E_Flowlayout_Item *bi;

             bi = evas_object_data_get(obj, "e_flowlayout_data");
             if (bi)
               {
                  if (sd->horizontal)
                    {
                       if (minh < bi->min.h) minh = bi->min.h * sd->rows;
                       minw += bi->min.w;
                    }
                  else
                    {
                       if (minw < bi->min.w) minw = bi->min.w * sd->cols;
                       minh += bi->min.h;
                    }
               }
          }
        if (sd->horizontal)
          {
             sd->rows = sd->h / minh;
             minh *= sd->rows;
             sd->cols = -1;
             minw = minw / sd->cols + 0.2 * minw;
          }
        else
          {
             sd->cols = sd->w / minw;
             minw *= sd->cols;
             sd->rows = -1;
             minh = minh / sd->cols + 0.2 * minh;
          }
     }
   sd->min.w = minw;
   sd->min.h = minh;
   if (sd->rows < 1)
     sd->rows = 1;
   if (sd->cols < 1)
     sd->cols = 1;
}

static void
_e_flowlayout_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         "e_flowlayout",
         EVAS_SMART_CLASS_VERSION,
         _e_flowlayout_smart_add,
         _e_flowlayout_smart_del,
         _e_flowlayout_smart_move,
         _e_flowlayout_smart_resize,
         _e_flowlayout_smart_show,
         _e_flowlayout_smart_hide,
         _e_flowlayout_smart_color_set,
         _e_flowlayout_smart_clip_set,
         _e_flowlayout_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

static void
_e_flowlayout_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   sd->obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->align.x = 0.5;
   sd->align.y = 0.5;
   sd->clip = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(sd->clip, obj);
   evas_object_move(sd->clip, -100004, -100004);
   evas_object_resize(sd->clip, 200008, 200008);
   evas_object_color_set(sd->clip, 255, 255, 255, 255);
   evas_object_smart_data_set(obj, sd);
}

static void
_e_flowlayout_smart_del(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Evas_Object *child;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   EINA_LIST_FREE(sd->items, child)
     {
        evas_object_event_callback_del(child, EVAS_CALLBACK_DEL, _e_flowlayout_smart_item_del_hook);
        free(evas_object_data_get(child, "e_flowlayout_data"));
     }
   free(sd);
}

static void
_e_flowlayout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((x == sd->x) && (y == sd->y)) return;
   {
      Eina_List *l;
      Evas_Object *item;
      Evas_Coord dx, dy;

      dx = x - sd->x;
      dy = y - sd->y;
      EINA_LIST_FOREACH(sd->items, l, item)
        {
           Evas_Coord ox, oy;

           evas_object_geometry_get(item, &ox, &oy, NULL, NULL);
           evas_object_move(item, ox + dx, oy + dy);
        }
   }
   sd->x = x;
   sd->y = y;
}

static void
_e_flowlayout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((w == sd->w) && (h == sd->h)) return;
   sd->w = w;
   sd->h = h;
   sd->changed = 1;
   _e_flowlayout_smart_reconfigure(sd);
}

static void
_e_flowlayout_smart_show(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->items) evas_object_show(sd->clip);
}

static void
_e_flowlayout_smart_hide(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_hide(sd->clip);
}

static void
_e_flowlayout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_color_set(sd->clip, r, g, b, a);
}

static void
_e_flowlayout_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_set(sd->clip, clip);
}

static void
_e_flowlayout_smart_clip_unset(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_unset(sd->clip);
}

#include "e.h"
#include "e_fm_device.h"
#include "e_fm_op.h"

#define OVERCLIP          128
#define ICON_BOTTOM_SPACE 100

/* in order to check files (ie: extensions) use simpler and faster
 * strcasecmp version that instead of checking case for each
 * character, check just the first and if it's upper, assume
 * everything is uppercase, otherwise assume everything is lowercase.
 */
//#define E_FM2_SIMPLE_STRCASE_FILES 1

/* FIXME: this is NOT complete. in icon view dnd needs to be much better about placement of icons and
 * being able to save/load icon placement. it doesn't support custom frames or icons yet
 */

#define EFM_SMART_CHECK(args ...)                                           \
  E_Fm2_Smart_Data * sd;                                                    \
                                                                            \
  if (evas_object_smart_smart_get(obj) != _e_fm2_smart) SMARTERRNR() args;  \
  sd = evas_object_smart_data_get(obj);                                     \
  if ((!sd) || (e_util_strcmp(evas_object_type_get(obj), "e_fm")))          \
    return args

typedef enum _E_Fm2_Action_Type
{
   FILE_ADD,
   FILE_DEL,
   FILE_CHANGE
} E_Fm2_Action_Type;

typedef struct _E_Fm2_Smart_Data        E_Fm2_Smart_Data;
typedef struct _E_Fm2_Region            E_Fm2_Region;
typedef struct _E_Fm2_Finfo             E_Fm2_Finfo;
typedef struct _E_Fm2_Action            E_Fm2_Action;
typedef struct _E_Fm2_Client            E_Fm2_Client;
typedef struct _E_Fm2_Uri               E_Fm2_Uri;
typedef struct _E_Fm2_Context_Menu_Data E_Fm2_Context_Menu_Data;

struct _E_Fm2_Smart_Data
{
   int          id;
   Evas_Coord   x, y, w, h, pw, ph;
   Eina_List   *icons;
   Evas_Object *obj;
   Evas_Object *clip;
   Evas_Object *underlay;
   unsigned int overlay_count;
   Evas_Object *overlay;
   Evas_Object *overlay_clip;
   Evas_Object *drop;
   Evas_Object *drop_in;
   Evas_Object *sel_rect;
   const char  *dev;
   const char  *path;
   const char  *realpath;

   struct
   {
      Evas_Coord w, h;
   } max, pmax, min; /* min is actually the size of the largest icon, updated each placement */
   struct
   {
      Evas_Coord x, y;
   } pos;
   struct
   {
      Eina_List *list;
      int        member_max;
   } regions;
   struct
   {
      struct
      {
         E_Fm_Cb func;
         void   *data;
      } start, end, replace;
      E_Fm2_Menu_Flags flags;
   } icon_menu;

   struct
   {
      Ecore_Thread *thread;
      const char   *filename;
      Eina_Bool done E_BITFIELD;
   } new_file;

   E_Fm2_Icon      *last_selected;
   E_Fm2_Icon      *range_selected;
   E_Fm2_Icon      *range_select_anchor;
   Eina_List       *selected_icons;
   Eina_List       *icons_place;
   Eina_List       *queue;
   Ecore_Timer     *scan_timer;
   Ecore_Idler     *sort_idler;
   Ecore_Job       *scroll_job;
   Ecore_Job       *resize_job;
   Ecore_Job       *refresh_job;
   E_Menu          *menu;
   Eina_List       *rename_dialogs;
   E_Entry_Dialog  *entry_dialog;
   E_Dialog        *image_dialog;
   Eina_Bool        iconlist_changed E_BITFIELD;
   Eina_Bool        order_file E_BITFIELD;
   Eina_Bool        typebuf_visible E_BITFIELD;
   Eina_Bool        show_hidden_files E_BITFIELD;
   Eina_Bool        listing E_BITFIELD;
   Eina_Bool        inherited_dir_props E_BITFIELD;
   signed char      view_mode;  /* -1 = unset */
   signed short     icon_size;  /* -1 = unset */
   E_Fm2_View_Flags view_flags;

   E_Fm2_Icon      *last_placed;
   E_Fm2_Config    *config;
   const char      *custom_theme;
   const char      *custom_theme_content;

   struct
   {
      Evas_Object *obj, *obj2;
      Eina_List   *last_insert;
      Eina_List  **list_index;
      int          iter;
   } tmp;

   struct
   {
      Eina_List   *actions;
      Ecore_Idler *idler;
      Ecore_Timer *timer;
      Eina_Bool    deletions E_BITFIELD;
   } live;

   struct
   {
      char        *buf;
      const char  *start;
      Ecore_Timer *timer;
      unsigned int wildcard;
      Eina_Bool    setting E_BITFIELD;
      Eina_Bool    disabled E_BITFIELD;
   } typebuf;

   int                  busy_count;

   E_Object            *eobj;
   Evas_Object         *win;
   E_Drop_Handler      *drop_handler;
   E_Fm2_Icon          *drop_icon;
   Ecore_Animator      *dnd_scroller;
   Evas_Point           dnd_current;
   Eina_List           *mount_ops;
   E_Fm2_Mount         *mount;
   signed char          drop_after;
   Eina_Bool            drop_show E_BITFIELD;
   Eina_Bool            drop_in_show E_BITFIELD;
   Eina_Bool            drop_all E_BITFIELD;
   Eina_Bool            drag E_BITFIELD;
   Eina_Bool            selecting E_BITFIELD;
   Eina_Bool            toomany E_BITFIELD;
   struct
   {
      int ox, oy;
      int x, y, w, h;
   } selrect;

   E_Fm2_Icon          *iop_icon;

   Eina_List           *handlers;
   Efreet_Desktop      *desktop;
};

struct _E_Fm2_Region
{
   E_Fm2_Smart_Data *sd;
   Evas_Coord        x, y, w, h;
   Eina_List        *list;
   Eina_Bool         realized E_BITFIELD;
};

struct _E_Fm2_Icon
{
   int               saved_x, saved_y;
   double            selected_time;
   E_Fm2_Smart_Data *sd;
   E_Fm2_Region     *region;
   Evas_Coord        x, y, w, h, min_w, min_h;
   Evas_Object      *obj, *obj_icon;
   E_Menu           *menu;
   E_Entry_Dialog   *entry_dialog;
   Evas_Object      *entry_widget;
   E_Client_Hook    *focus_hook;
   Eio_File         *eio;
   Ecore_X_Window    keygrab;
   E_Config_Dialog  *prop_dialog;
   E_Dialog         *dialog;

   E_Fm2_Icon_Info   info;
   E_Fm2_Mount      *mount; // for dnd into unmounted dirs
   Ecore_Timer      *mount_timer; // autounmount in 15s

   struct
   {
      Evas_Coord   x, y;
      Ecore_Timer *dnd_end_timer; //we need this for XDirectSave drops so we don't lose the icon
      Eina_Bool    start E_BITFIELD;
      Eina_Bool    dnd E_BITFIELD; // currently dragging
      Eina_Bool    src E_BITFIELD; // drag source
      Eina_Bool    hidden E_BITFIELD; // dropped into different dir
   } drag;

   int       saved_rel;

   Eina_Bool realized E_BITFIELD;
   Eina_Bool selected E_BITFIELD;
   Eina_Bool last_selected E_BITFIELD;
   Eina_Bool saved_pos E_BITFIELD;
   Eina_Bool odd E_BITFIELD;
   Eina_Bool down_sel E_BITFIELD;
   Eina_Bool removable_state_change E_BITFIELD;
   Eina_Bool thumb_failed E_BITFIELD;
   Eina_Bool queued E_BITFIELD;
   Eina_Bool inserted E_BITFIELD;
};

struct _E_Fm2_Finfo
{
   struct stat st;
   int         broken_link;
   const char *lnk;
   const char *rlnk;
};

struct _E_Fm2_Action
{
   E_Fm2_Action_Type type;
   const char       *file;
   const char       *file2;
   int               flags;
   E_Fm2_Finfo       finf;
};

struct _E_Fm2_Client
{
   Ecore_Ipc_Client *cl;
   int               req;
};

struct _E_Fm2_Uri
{
   const char *hostname;
   const char *path;
};

struct _E_Fm2_Context_Menu_Data
{
   E_Fm2_Icon         *icon;
   E_Fm2_Smart_Data   *sd;
   E_Fm2_Mime_Handler *handler;
};
static Eina_Bool     _e_fm2_cb_drag_finished_show(E_Fm2_Icon *ic);
static const char   *_e_fm2_dev_path_map(E_Fm2_Smart_Data *sd, const char *dev, const char *path);
static void          _e_fm2_file_add(Evas_Object *obj, const char *file, int unique, Eina_Stringshare *file_rel, int after, E_Fm2_Finfo *finf);
static void          _e_fm2_file_del(Evas_Object *obj, const char *file);
static void          _e_fm2_queue_process(Evas_Object *obj);
static void          _e_fm2_queue_free(Evas_Object *obj);
static void          _e_fm2_regions_free(Evas_Object *obj);
static void          _e_fm2_regions_populate(Evas_Object *obj);
static void          _e_fm2_icons_place(Evas_Object *obj);
static void          _e_fm2_icons_free(Evas_Object *obj);
static void          _e_fm2_regions_eval(Evas_Object *obj);
static void          _e_fm2_config_free(E_Fm2_Config *cfg);

static void          _e_fm2_dir_load_props(E_Fm2_Smart_Data *sd);
static void          _e_fm2_dir_save_props(E_Fm2_Smart_Data *sd);

static Eina_List    *_e_fm2_file_fm2_find(const char *file);
static E_Fm2_Icon   *_e_fm2_icon_find(Evas_Object *obj, const char *file);
static const char   *_e_fm2_uri_escape(const char *path);
static Eina_List    *_e_fm2_uri_selected_icon_list_get(Eina_List *uri);

static E_Fm2_Icon   *_e_fm2_icon_new(E_Fm2_Smart_Data *sd, const char *file, E_Fm2_Finfo *finf);
static void          _e_fm2_icon_unfill(E_Fm2_Icon *ic);
static int           _e_fm2_icon_fill(E_Fm2_Icon *ic, E_Fm2_Finfo *finf);
static void          _e_fm2_icon_free(E_Fm2_Icon *ic);
static void          _e_fm2_icon_realize(E_Fm2_Icon *ic);
static void          _e_fm2_icon_unrealize(E_Fm2_Icon *ic);
static Eina_Bool     _e_fm2_icon_visible(const E_Fm2_Icon *ic);
static void          _e_fm2_icon_label_set(E_Fm2_Icon *ic, Evas_Object *obj);
static Evas_Object  *_e_fm2_icon_icon_direct_set(E_Fm2_Icon *ic, Evas_Object *o, Evas_Smart_Cb gen_func, void *data, int force_gen);
static void          _e_fm2_icon_icon_set(E_Fm2_Icon *ic);
static void          _e_fm2_icon_thumb(const E_Fm2_Icon *ic, Evas_Object *oic, int force);
static void          _e_fm2_icon_select(E_Fm2_Icon *ic);
static void          _e_fm2_icon_deselect(E_Fm2_Icon *ic);
static int           _e_fm2_icon_desktop_load(E_Fm2_Icon *ic);

static E_Fm2_Region *_e_fm2_region_new(E_Fm2_Smart_Data *sd);
static void          _e_fm2_region_free(E_Fm2_Region *rg);
static void          _e_fm2_region_realize(E_Fm2_Region *rg);
static void          _e_fm2_region_unrealize(E_Fm2_Region *rg);
static int           _e_fm2_region_visible(E_Fm2_Region *rg);

static void          _e_fm2_icon_make_visible(E_Fm2_Icon *ic);
static void          _e_fm2_icon_desel_any(Evas_Object *obj);
static E_Fm2_Icon   *_e_fm2_icon_first_selected_find(Evas_Object *obj);
static E_Fm2_Icon   *_e_fm2_icon_next_find(Evas_Object *obj, int next, int (*match_func)(E_Fm2_Icon *ic, void *data), void *data);

static void          _e_fm2_icon_sel_any(Evas_Object *obj);
static void          _e_fm2_icon_sel_first(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_sel_last(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_sel_prev(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_sel_next(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_sel_down(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_sel_up(Evas_Object *obj, Eina_Bool add);
static void          _e_fm2_icon_range_select(E_Fm2_Icon *ic);

static void          _e_fm2_typebuf_show(Evas_Object *obj);
static void          _e_fm2_typebuf_hide(Evas_Object *obj);
//static void _e_fm2_typebuf_history_prev(Evas_Object *obj);
//static void _e_fm2_typebuf_history_next(Evas_Object *obj);
static void          _e_fm2_typebuf_run(Evas_Object *obj);
static E_Fm2_Icon   *_e_fm2_typebuf_match(Evas_Object *obj, int next);
static void          _e_fm2_typebuf_complete(Evas_Object *obj);
static void          _e_fm2_typebuf_char_append(Evas_Object *obj, const char *ch);
static void          _e_fm2_typebuf_char_backspace(Evas_Object *obj);

static void          _e_fm2_cb_dnd_enter(void *data, const char *type, void *event);
static void          _e_fm2_cb_dnd_move(void *data, const char *type, void *event);
static void          _e_fm2_cb_dnd_leave(void *data, const char *type, void *event);
static void          _e_fm2_cb_dnd_selection_notify(void *data, const char *type, void *event);
static void          _e_fm2_cb_icon_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_mouse_in(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_mouse_out(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_thumb_dnd_gen(void *data, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_icon_thumb_gen(void *data, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_cb_scroll_job(void *data);
static void          _e_fm2_cb_resize_job(void *data);
static int           _e_fm2_cb_icon_sort(const void *data1, const void *data2);
static Eina_Bool     _e_fm2_cb_scan_timer(void *data);
static Eina_Bool     _e_fm2_cb_sort_idler(void *data);
static Eina_Bool     _e_fm2_cb_theme(void *data, int type EINA_UNUSED, void *event EINA_UNUSED);

static void          _e_fm2_obj_icons_place(E_Fm2_Smart_Data *sd);

static void          _e_fm2_smart_add(Evas_Object *object);
static void          _e_fm2_smart_del(Evas_Object *object);
static void          _e_fm2_smart_move(Evas_Object *object, Evas_Coord x, Evas_Coord y);
static void          _e_fm2_smart_resize(Evas_Object *object, Evas_Coord w, Evas_Coord h);
static void          _e_fm2_smart_show(Evas_Object *object);
static void          _e_fm2_smart_hide(Evas_Object *object);
static void          _e_fm2_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void          _e_fm2_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void          _e_fm2_smart_clip_unset(Evas_Object *obj);

static void          _e_fm2_menu(Evas_Object *obj, unsigned int timestamp);
static void          _e_fm2_menu_post_cb(void *data, E_Menu *m);
static void          _e_fm2_icon_menu(E_Fm2_Icon *ic, Evas_Object *obj, unsigned int timestamp);
static void          _e_fm2_icon_menu_post_cb(void *data, E_Menu *m);
static void          _e_fm2_icon_menu_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_icon_view_menu_pre(void *data, E_Menu *m);
static void          _e_fm2_options_menu_pre(void *data, E_Menu *m);
static void          _e_fm2_add_menu_pre(void *data, E_Menu *m);
static void          _e_fm2_toggle_inherit_dir_props(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_pre(void *data, E_Menu *m);
static void          _e_fm2_view_menu_grid_icons_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_custom_icons_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_list_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_use_default_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_set_background_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_menu_set_overlay_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_view_image_sel(E_Fm2_Smart_Data *sd, const char *title, void (*ok_cb)(void *data, E_Dialog *dia));
static void          _e_fm2_view_image_sel_close(void *data, E_Dialog *dia);
static void          _e_fm2_refresh(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_toggle_hidden_files(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_toggle_single_click(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_toggle_secure_rm(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_toggle_ordering(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_sort(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_new_directory(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_rename(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_rename_delete_cb(void *obj);
static void          _e_fm2_file_rename_yes_cb(void *data, char *text);
static void          _e_fm2_file_rename_no_cb(void *data);
static void          _e_fm2_file_application_properties(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED);
static void          _e_fm2_file_properties(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_properties_delete_cb(void *obj);
static int           _e_fm2_file_do_rename(const char *text, E_Fm2_Icon *ic);

static Evas_Object  *_e_fm2_icon_entry_widget_add(E_Fm2_Icon *ic);
static void          _e_fm2_icon_entry_widget_del(E_Fm2_Icon *ic);
static void          _e_fm2_icon_entry_widget_cb_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void          _e_fm2_icon_entry_widget_accept(E_Fm2_Icon *ic);

static E_Dialog     *_e_fm_retry_abort_dialog(int pid, const char *str);
static void          _e_fm_retry_abort_retry_cb(void *data, E_Dialog *dialog);
static void          _e_fm_retry_abort_abort_cb(void *data, E_Dialog *dialog);

static E_Dialog     *_e_fm_overwrite_dialog(int pid, const char *str);
static void          _e_fm_overwrite_no_cb(void *data, E_Dialog *dialog);
static void          _e_fm_overwrite_no_all_cb(void *data, E_Dialog *dialog);
static void          _e_fm_overwrite_rename(void *data, E_Dialog *dialog);
static void          _e_fm_overwrite_yes_cb(void *data, E_Dialog *dialog);
static void          _e_fm_overwrite_yes_all_cb(void *data, E_Dialog *dialog);

static E_Dialog     *_e_fm_error_dialog(int pid, const char *str);
static void          _e_fm_error_retry_cb(void *data, E_Dialog *dialog);
static void          _e_fm_error_abort_cb(void *data, E_Dialog *dialog);
static void          _e_fm_error_link_source(void *data, E_Dialog *dialog);
static void          _e_fm_error_ignore_this_cb(void *data, E_Dialog *dialog);
static void          _e_fm_error_ignore_all_cb(void *data, E_Dialog *dialog);

static void          _e_fm_device_error_dialog(const char *title, const char *msg, const char *pstr);

static void          _e_fm2_file_delete(Evas_Object *obj);
static void          _e_fm2_file_delete_menu(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_delete_delete_cb(void *obj);
static void          _e_fm2_file_delete_yes_cb(void *data, E_Dialog *dialog);
static void          _e_fm2_file_delete_no_cb(void *data, E_Dialog *dialog);
static void          _e_fm2_refresh_job_cb(void *data);
static void          _e_fm_file_buffer_clear(void);
static void          _e_fm2_file_cut(Evas_Object *obj);
static void          _e_fm2_file_copy(Evas_Object *obj);
static void          _e_fm2_file_paste(Evas_Object *obj);
static void          _e_fm2_file_symlink(Evas_Object *obj);
static void          _e_fm2_file_cut_menu(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_copy_menu(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_paste_menu(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_file_symlink_menu(void *data, E_Menu *m, E_Menu_Item *mi);

static void          _e_fm2_live_file_add(Evas_Object *obj, const char *file, const char *file_rel, int after, E_Fm2_Finfo *finf);
static void          _e_fm2_live_file_del(Evas_Object *obj, const char *file);
static void          _e_fm2_live_file_changed(Evas_Object *obj, const char *file, E_Fm2_Finfo *finf);
static void          _e_fm2_live_process_begin(Evas_Object *obj);
static void          _e_fm2_live_process_end(Evas_Object *obj);
static void          _e_fm2_live_process(Evas_Object *obj);
static Eina_Bool     _e_fm2_cb_live_idler(void *data);
static Eina_Bool     _e_fm2_cb_live_timer(void *data);

static int           _e_fm2_theme_edje_object_set(E_Fm2_Smart_Data *sd, Evas_Object *o, const char *category, const char *group);
static int           _e_fm2_theme_edje_icon_object_set(E_Fm2_Smart_Data *sd, Evas_Object *o, const char *category, const char *group);

static void          _e_fm2_mouse_2_handler(E_Fm2_Icon *ic, void *evas_event);

static void          _e_fm2_client_spawn(void);
static E_Fm2_Client *_e_fm2_client_get(void);
static int           _e_fm2_client_monitor_add(const char *path);
static void          _e_fm2_client_monitor_del(int id, const char *path);
static int           _e_fm_client_file_del(const char *files, Eina_Bool secure, Evas_Object *e_fm);
//static int _e_fm2_client_file_trash(const char *path, Evas_Object *e_fm);
//static int           _e_fm2_client_file_mkdir(const char *path, const char *rel, int rel_to, int x, int y, int res_w, int res_h, Evas_Object *e_fm);

static void          _e_fm2_sel_rect_update(void *data);
static void          _e_fm2_context_menu_append(E_Fm2_Smart_Data *sd, const char *path, const Eina_List *l, E_Menu *mn, E_Fm2_Icon *ic);
static int           _e_fm2_context_list_sort(const void *data1, const void *data2);

void                 _e_fm2_path_parent_set(Evas_Object *obj, const char *path);

static void          _e_fm2_volume_mount(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_volume_unmount(void *data, E_Menu *m, E_Menu_Item *mi);
static void          _e_fm2_volume_eject(void *data, E_Menu *m, E_Menu_Item *mi);

static void          _e_fm2_icon_removable_update(E_Fm2_Icon *ic);
static void          _e_fm2_volume_icon_update(E_Volume *v);
static int           _e_fm2_desktop_open(E_Fm2_Smart_Data *sd);
static void          _e_fm2_operation_abort_internal(E_Fm2_Op_Registry_Entry *ere);

static Eina_Bool     _e_fm2_sys_suspend_hibernate(void *, int, void *);

static void          _e_fm2_favorites_thread_cb(void *d, Ecore_Thread *et);
static void          _e_fm2_thread_cleanup_cb(void *d, Ecore_Thread *et);

static void _e_fm2_new_file(void *data, E_Menu *m, E_Menu_Item *mi);

static Ecore_Exe *_e_fm2_exe = NULL;
static char *_e_fm2_meta_path = NULL;
static Evas_Smart *_e_fm2_smart = NULL;
static Eina_List *_e_fm2_list = NULL;
static Eina_List *_e_fm2_list_remove = NULL;
static int _e_fm2_list_walking = 0;
static Eina_List *_e_fm2_client_list = NULL;
static Eina_List *_e_fm2_menu_contexts = NULL;
static Eina_List *_e_fm_file_buffer = NULL; /* Files for copy&paste are saved here. */
static int _e_fm_file_buffer_copying = 0;
static const char *_e_fm2_icon_desktop_str = NULL;
static const char *_e_fm2_icon_thumb_str = NULL;
static const char *_e_fm2_mime_inode_directory = NULL;
static const char *_e_fm2_mime_app_desktop = NULL;
static const char *_e_fm2_mime_app_edje = NULL;
static const char *_e_fm2_mime_text_uri_list = NULL;
static const char *_e_fm2_mime_xmozurl = NULL;
static const char *_e_fm2_xds = NULL;

static Eina_List *_e_fm_handlers = NULL;

static const char **_e_fm2_dnd_types[] =
{
   &_e_fm2_mime_text_uri_list,
   &_e_fm2_xds,
   &_e_fm2_mime_xmozurl,
   NULL
};

static Ecore_Timer *_e_fm2_mime_flush = NULL;
static Ecore_Timer *_e_fm2_mime_clear = NULL;
static Ecore_Thread *_e_fm2_favorites_thread = NULL;

/* contains:
 * _e_volume_edd
 * _e_storage_edd
 * _e_volume_free()
 * _e_storage_free()
 * _e_volume_edd_new()
 * _e_storage_edd_new()
 * _e_storage_volume_edd_init()
 * _e_storage_volume_edd_shutdown()
 */

#include "e_fm_shared_codec.h"

static inline Eina_Bool
_e_fm2_icon_realpath(const E_Fm2_Icon *ic, char *buf, int buflen)
{
   int r = snprintf(buf, buflen, "%s/%s", ic->sd->realpath, ic->info.file);
   return r < buflen;
}

static inline Eina_Bool
_e_fm2_icon_path(const E_Fm2_Icon *ic, char *buf, int buflen)
{
   int r;

   if (ic->info.real_link)
     r = snprintf(buf, buflen, "%s", ic->info.real_link);
   else
     r = snprintf(buf, buflen, "%s/%s", ic->sd->path, ic->info.file);

   return r < buflen;
}

static inline Eina_Bool
_e_fm2_ext_is_edje(const char *ext)
{
#ifdef E_FM2_SIMPLE_STRCASE_FILES
   if ((ext[0] == 'e') && (ext[1] == 'd') && (ext[2] == 'j'))
     return 1;
   else if ((ext[0] == 'E') && (ext[1] == 'D') && (ext[2] == 'J'))
     return 1;
   else
     return 0;
#else
   return strcasecmp(ext, "edj") == 0;
#endif
}

static inline Eina_Bool
_e_fm2_ext_is_desktop(const char *ext)
{
#ifdef E_FM2_SIMPLE_STRCASE_FILES
   if ((ext[0] == 'd') &&
       ((strcmp(ext + 1, "esktop") == 0) ||
        (strcmp(ext + 1, "irectory") == 0)))
     return 1;
   else if ((ext[0] == 'D') &&
            ((strcmp(ext + 1, "ESKTOP") == 0) ||
             (strcmp(ext + 1, "IRECTORY") == 0)))
     return 1;
   else
     return 0;
#else
   if ((ext[0] != 'd') && (ext[0] != 'D'))
     return 0;

   ext++;
   return (strcasecmp(ext, "esktop") == 0) ||
          (strcasecmp(ext, "irectory") == 0);
#endif
}

static inline Eina_Bool
_e_fm2_ext_is_imc(const char *ext)
{
#ifdef E_FM2_SIMPLE_STRCASE_FILES
   if ((ext[0] == 'i') && (ext[1] == 'm') && (ext[2] == 'c'))
     return 1;
   else if ((ext[0] == 'I') && (ext[1] == 'M') && (ext[2] == 'C'))
     return 1;
   else
     return 0;
#else
   return strcasecmp(ext, "imc") == 0;
#endif
}

static inline Eina_Bool
_e_fm2_file_is_edje(const char *file)
{
   const char *p = strrchr(file, '.');
   return (p) && (_e_fm2_ext_is_edje(p + 1));
}

static inline Eina_Bool
_e_fm2_file_is_desktop(const char *file)
{
   const char *p = strrchr(file, '.');
   return (p) && (_e_fm2_ext_is_desktop(p + 1));
}

static inline Eina_Bool
_e_fm2_toomany_get(const E_Fm2_Smart_Data *sd)
{
   char mode;

   mode = sd->config->view.mode;
   if (sd->view_mode > -1) mode = sd->view_mode;
   if (((mode >= E_FM2_VIEW_MODE_CUSTOM_ICONS) &&
        (mode <= E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS)) &&
       (eina_list_count(sd->icons) >= 500))
     return EINA_TRUE;
   return EINA_FALSE;
}

static inline char
_e_fm2_view_mode_get(const E_Fm2_Smart_Data *sd)
{
   char mode;

   mode = sd->config->view.mode;
   if (sd->view_mode > -1) mode = sd->view_mode;
   if ((sd->toomany) &&
       ((mode >= E_FM2_VIEW_MODE_CUSTOM_ICONS) &&
        (mode <= E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS)))
     return E_FM2_VIEW_MODE_GRID_ICONS;
   return mode;
}

static inline Evas_Coord
_e_fm2_icon_w_get(const E_Fm2_Smart_Data *sd)
{
   if (sd->icon_size > -1)
     return sd->icon_size * e_scale;
   if (sd->config->icon.icon.w)
     return sd->config->icon.icon.w;
   return sd->config->icon.list.w;
}

static inline Evas_Coord
_e_fm2_icon_h_get(const E_Fm2_Smart_Data *sd)
{
   if (sd->icon_size > -1)
     return sd->icon_size * e_scale;
   if (sd->config->icon.icon.h)
     return sd->config->icon.icon.h;
   return sd->config->icon.list.h;
}

static inline unsigned int
_e_fm2_icon_mime_size_normalize(const E_Fm2_Icon *ic)
{
   return e_util_icon_size_normalize(_e_fm2_icon_w_get(ic->sd));
}

static Eina_Bool
_e_fm2_mime_flush_cb(void *data EINA_UNUSED)
{
   efreet_mime_type_cache_flush();
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_fm2_mime_clear_cb(void *data EINA_UNUSED)
{
   efreet_mime_type_cache_clear();
   return ECORE_CALLBACK_RENEW;
}

static void
_e_fm2_op_registry_go_on(int id)
{
   E_Fm2_Op_Registry_Entry *ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;
   ere->status = E_FM2_OP_STATUS_IN_PROGRESS;
   ere->needs_attention = 0;
   ere->dialog = NULL;
   e_fm2_op_registry_entry_changed(ere);
}

static void
_e_fm2_op_registry_aborted(int id)
{
   E_Fm2_Op_Registry_Entry *ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;
   ere->status = E_FM2_OP_STATUS_ABORTED;
   ere->needs_attention = 0;
   ere->dialog = NULL;
   ere->finished = 1;
   e_fm2_op_registry_entry_changed(ere);
   // XXX e_fm2_op_registry_entry_del(id);
}

static void
_e_fm2_op_registry_error(int id, E_Dialog *dlg)
{
   E_Fm2_Op_Registry_Entry *ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;
   ere->status = E_FM2_OP_STATUS_ERROR;
   ere->needs_attention = 1;
   ere->dialog = dlg;
   e_fm2_op_registry_entry_changed(ere);
}

static void
_e_fm2_op_registry_needs_attention(int id, E_Dialog *dlg)
{
   E_Fm2_Op_Registry_Entry *ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;
   ere->needs_attention = 1;
   ere->dialog = dlg;
   e_fm2_op_registry_entry_changed(ere);
}

/////////////// DBG:
static void
_e_fm2_op_registry_entry_print(const E_Fm2_Op_Registry_Entry *ere)
{
   const char *status_strings[] =
   {
      "UNKNOWN", "IN_PROGRESS", "SUCCESSFUL", "ABORTED", "ERROR"
   };
   const char *status;

   if (ere->status <= E_FM2_OP_STATUS_ERROR)
     status = status_strings[ere->status];
   else
     status = status_strings[0];

   DBG("id: %8d, op: %2d [%s] finished: %hhu, needs_attention: %hhu\n"
       "    %3d%% (%" PRIi64 "/%" PRIi64 "), start_time: %10.0f, eta: %5ds, xwin: %#x\n"
                                         "    src=[%s]\n"
                                         "    dst=[%s]",
       ere->id, ere->op, status, ere->finished, ere->needs_attention,
       ere->percent, ere->done, ere->total, ere->start_time, ere->eta,
       e_fm2_op_registry_entry_xwin_get(ere),
       ere->src, ere->dst);
}

static Eina_Bool
_e_fm2_exe_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Del *ev = event;
   if (ev->exe == _e_fm2_exe) _e_fm2_exe = NULL;
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_fm2_op_registry_entry_add_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   const E_Fm2_Op_Registry_Entry *ere = event;
   DBG("E FM OPERATION STARTED: id=%d, op=%d", ere->id, ere->op);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_fm2_op_registry_entry_del_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   const E_Fm2_Op_Registry_Entry *ere = event;
   puts("E FM OPERATION FINISHED:");
   _e_fm2_op_registry_entry_print(ere);
   puts("---");
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_fm2_op_registry_entry_changed_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   const E_Fm2_Op_Registry_Entry *ere = event;
   puts("E FM OPERATION CHANGED:");
   _e_fm2_op_registry_entry_print(ere);
   puts("---");
   return ECORE_CALLBACK_RENEW;
}

/////////////// DBG:

/***/

EINTERN int
e_fm2_init(void)
{
   char path[PATH_MAX];

   eina_init();
   ecore_init();
   _e_storage_volume_edd_init();
   e_user_dir_concat_static(path, "fileman/metadata");
   ecore_file_mkpath(path);
   _e_fm2_meta_path = strdup(path);

   {
      static const Evas_Smart_Class sc =
      {
         "e_fm",
         EVAS_SMART_CLASS_VERSION,
         _e_fm2_smart_add, /* add */
         _e_fm2_smart_del, /* del */
         _e_fm2_smart_move, /* move */
         _e_fm2_smart_resize, /* resize */
         _e_fm2_smart_show, /* show */
         _e_fm2_smart_hide, /* hide */
         _e_fm2_smart_color_set, /* color_set */
         _e_fm2_smart_clip_set, /* clip_set */
         _e_fm2_smart_clip_unset, /* clip_unset */
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_fm2_smart = evas_smart_class_new(&sc);
   }
//   _e_fm2_client_spawn();
   e_fm2_custom_file_init();
   e_fm2_op_registry_init();
   efreet_mime_init();

   /* XXX: move this to a central/global place? */
   _e_fm2_mime_flush = ecore_timer_loop_add(60.0, _e_fm2_mime_flush_cb, NULL);
   _e_fm2_mime_clear = ecore_timer_loop_add(600.0, _e_fm2_mime_clear_cb, NULL);

   _e_fm2_icon_desktop_str = eina_stringshare_add("DESKTOP");
   _e_fm2_icon_thumb_str = eina_stringshare_add("THUMB");
   _e_fm2_mime_inode_directory = eina_stringshare_add("inode/directory");
   _e_fm2_mime_app_desktop = eina_stringshare_add("application/x-desktop");
   _e_fm2_mime_app_edje = eina_stringshare_add("application/x-extension-edj");
   _e_fm2_mime_text_uri_list = eina_stringshare_add("text/uri-list");
   _e_fm2_xds = eina_stringshare_add("XdndDirectSave0");
   _e_fm2_mime_xmozurl = eina_stringshare_add("text/x-moz-url");

   _e_fm2_favorites_thread = ecore_thread_run(_e_fm2_favorites_thread_cb,
                                              _e_fm2_thread_cleanup_cb,
                                              _e_fm2_thread_cleanup_cb, NULL);

   E_LIST_HANDLER_APPEND(_e_fm_handlers, ECORE_EXE_EVENT_DEL, _e_fm2_exe_del, NULL);
   /// DBG
   E_LIST_HANDLER_APPEND(_e_fm_handlers, E_EVENT_FM_OP_REGISTRY_ADD, _e_fm2_op_registry_entry_add_cb, NULL);
   E_LIST_HANDLER_APPEND(_e_fm_handlers, E_EVENT_FM_OP_REGISTRY_DEL, _e_fm2_op_registry_entry_del_cb, NULL);
   E_LIST_HANDLER_APPEND(_e_fm_handlers, E_EVENT_FM_OP_REGISTRY_CHANGED, _e_fm2_op_registry_entry_changed_cb, NULL);
   /// DBG
   E_LIST_HANDLER_APPEND(_e_fm_handlers, E_EVENT_SYS_HIBERNATE, _e_fm2_sys_suspend_hibernate, NULL);
   E_LIST_HANDLER_APPEND(_e_fm_handlers, E_EVENT_SYS_RESUME, _e_fm2_sys_suspend_hibernate, NULL);

   return 1;
}

EINTERN int
e_fm2_shutdown(void)
{
   e_fm2_die();

   E_FREE_LIST(_e_fm2_list, evas_object_del);

   eina_stringshare_replace(&_e_fm2_icon_desktop_str, NULL);
   eina_stringshare_replace(&_e_fm2_icon_thumb_str, NULL);
   eina_stringshare_replace(&_e_fm2_mime_inode_directory, NULL);
   eina_stringshare_replace(&_e_fm2_mime_app_desktop, NULL);
   eina_stringshare_replace(&_e_fm2_mime_app_edje, NULL);
   eina_stringshare_replace(&_e_fm2_mime_text_uri_list, NULL);
   eina_stringshare_replace(&_e_fm2_xds, NULL);
   eina_stringshare_replace(&_e_fm2_mime_xmozurl, NULL);

   E_FREE_LIST(_e_fm_handlers, ecore_event_handler_del);

   ecore_timer_del(_e_fm2_mime_flush);
   _e_fm2_mime_flush = NULL;
   ecore_timer_del(_e_fm2_mime_clear);
   _e_fm2_mime_clear = NULL;

   if (_e_fm2_favorites_thread) ecore_thread_cancel(_e_fm2_favorites_thread);
   _e_fm2_favorites_thread = NULL;

   evas_smart_free(_e_fm2_smart);
   _e_fm2_smart = NULL;
   E_FREE(_e_fm2_meta_path);
   e_fm2_custom_file_shutdown();
   _e_storage_volume_edd_shutdown();
   e_fm2_op_registry_shutdown();
   efreet_mime_shutdown();
   ecore_shutdown();
   eina_shutdown();
   return 1;
}

E_API void
e_fm2_die(void)
{
   if (_e_fm2_exe)
     {
        ecore_exe_kill(_e_fm2_exe);
        ecore_exe_free(_e_fm2_exe);
        _e_fm2_exe = NULL;
     }
}

E_API Evas_Object *
e_fm2_add(Evas *evas)
{
   return evas_object_smart_add(evas, _e_fm2_smart);
}

static Eina_Bool
_e_fm2_cb_dnd_drop(void *data, const char *type)
{
   E_Fm2_Smart_Data *sd = data;
   Eina_Bool allow;
   char buf[PATH_MAX];

   if (sd->config->view.link_drop && (type == _e_fm2_xds))
     allow = EINA_FALSE;
   else
     {
        if (sd->drop_icon)
          {
             snprintf(buf, sizeof(buf), "%s/%s", sd->realpath, sd->drop_icon->info.file);
             allow = ecore_file_can_write(buf);
          }
        else
          allow = (sd->realpath && ecore_file_can_write(sd->realpath));
     }

   e_drop_xds_update(allow, sd->drop_icon ? buf : sd->realpath);
   if (sd->dnd_scroller) ecore_animator_del(sd->dnd_scroller);
   sd->dnd_scroller = NULL;
   sd->dnd_current.x = sd->dnd_current.y = 0;
   return allow;
}

static void
_e_fm2_cb_mount_ok(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;  // safety

   if (sd->mount->volume->efm_mode != EFM_MODE_USING_HAL_MOUNT)
     {
        /* Clean up typebuf. */
        _e_fm2_typebuf_hide(data);
        /* we only just now have the mount point so we should do stuff we couldn't do before */
        eina_stringshare_replace(&sd->realpath, sd->mount->volume->mount_point);
        eina_stringshare_replace(&sd->mount->mount_point, sd->mount->volume->mount_point);
        _e_fm2_dir_load_props(sd);
     }

   if ((sd->realpath) && (strcmp(sd->mount->mount_point, sd->realpath)))
     {
        e_fm2_path_set(sd->obj, "/", sd->mount->mount_point);
     }
   else
     {
        sd->id = _e_fm2_client_monitor_add(sd->mount->mount_point);
        sd->listing = EINA_TRUE;
        evas_object_smart_callback_call(data, "dir_changed", NULL);
        sd->tmp.iter = EINA_FALSE;
     }
}

static void
_e_fm2_cb_mount_fail(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;  // safety
   if (sd->mount)
     {
        // At this moment E_Fm2_Mount object already deleted in e_fm_device.c
        sd->mount = NULL;
        if (sd->config->view.open_dirs_in_place)
          e_fm2_path_set(data, "favorites", "/");
        else
          evas_object_smart_callback_call(data, "dir_deleted", NULL);
     }
}

static void
_e_fm2_cb_unmount_ok(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;
   if (sd->mount)
     {
        sd->mount = NULL;
        if (sd->config->view.open_dirs_in_place && sd->realpath)
          _e_fm2_path_parent_set(data, sd->realpath);
        else
          evas_object_smart_callback_call(data, "dir_deleted", NULL);
     }
}

void
_e_fm2_path_parent_set(Evas_Object *obj, const char *path)
{
   char buf[PATH_MAX], *p;
   int idx;

   p = strrchr(path, '/');
   if (!p || (p == path))
     e_fm2_path_set(obj, "/", "/");
   else
     {
        idx = p - path;
        if (idx < PATH_MAX)
          {
             strncpy(buf, path, idx);
             buf[idx] = '\0';
             e_fm2_path_set(obj, "/", buf);
          }
        else
          e_fm2_path_set(obj, "/", "/");
     }
}

static void
_e_fm2_favorites_thread_cb(void *d EINA_UNUSED, Ecore_Thread *et EINA_UNUSED)
{
   e_fm2_favorites_init();
}

static void
_e_fm2_thread_cleanup_cb(void *d EINA_UNUSED, Ecore_Thread *et EINA_UNUSED)
{
   _e_fm2_favorites_thread = NULL;
}

E_API void
e_fm2_path_set(Evas_Object *obj, const char *dev, const char *path)
{
   const char *real_path;
   EFM_SMART_CHECK();

   /* internal config for now - don't see a point making this configurable */
   sd->regions.member_max = 64;

   if (!sd->config)
     {
        sd->config = E_NEW(E_Fm2_Config, 1);
        if (!sd->config) return;
//	sd->config->view.mode = E_FM2_VIEW_MODE_ICONS;
        sd->config->view.mode = E_FM2_VIEW_MODE_LIST;
        sd->config->view.open_dirs_in_place = EINA_TRUE;
        sd->config->view.selector = EINA_TRUE;
        sd->config->view.single_click = e_config->filemanager_single_click;
        sd->config->view.single_click_delay = EINA_FALSE;
        sd->config->view.no_subdir_jump = EINA_FALSE;
        sd->config->icon.max_thumb_size = 5;
        sd->config->icon.icon.w = 128;
        sd->config->icon.icon.h = 128;
        sd->config->icon.list.w = 24;
        sd->config->icon.list.h = 24;
        sd->config->icon.fixed.w = EINA_TRUE;
        sd->config->icon.fixed.h = EINA_TRUE;
        sd->config->icon.extension.show = EINA_FALSE;
        sd->config->list.sort.no_case = EINA_TRUE;
        sd->config->list.sort.dirs.first = EINA_TRUE;
        sd->config->list.sort.dirs.last = EINA_FALSE;
        sd->config->selection.single = EINA_FALSE;
        sd->config->selection.windows_modifiers = EINA_FALSE;
        sd->config->theme.background = NULL;
        sd->config->theme.frame = NULL;
        sd->config->theme.icons = NULL;
        sd->config->theme.fixed = EINA_FALSE;
     }

   real_path = _e_fm2_dev_path_map(sd, dev, path);

   /* If the path doesn't exist, popup a dialog */
   if (dev && strncmp(dev, "removable:", 10)
       && !ecore_file_exists(real_path))
     {
        E_Dialog *dialog;
        char text[4096 + 256];

        dialog = e_dialog_new(NULL, "E", "_fm_file_unexisting_path_dialog");
        e_dialog_button_add(dialog, _("Close"), NULL, NULL, dialog);
        e_dialog_button_focus_num(dialog, 0);
        e_dialog_title_set(dialog, _("Nonexistent path"));
        e_dialog_icon_set(dialog, "dialog-error", 64);

        snprintf(text, sizeof(text), _("%s doesn't exist."), real_path);

        e_dialog_text_set(dialog, text);
        elm_win_center(dialog->win, 1, 1);
        e_dialog_show(dialog);
        return;
     }
   if (real_path && (sd->realpath == real_path)) return;

   E_FREE_FUNC(sd->scan_timer, ecore_timer_del);
   E_FREE_FUNC(sd->sort_idler, ecore_idler_del);
   if (sd->busy_count)
     sd->busy_count--;
   if (sd->busy_count == 0)
     {
        edje_object_signal_emit(sd->overlay, "e,state,busy,stop", "e");
        e_fm2_custom_file_flush();
     }
   if (sd->realpath) _e_fm2_client_monitor_del(sd->id, sd->realpath);
   sd->listing = EINA_FALSE;
   if (sd->new_file.thread) ecore_thread_cancel(sd->new_file.thread);
   sd->new_file.thread = NULL;
   eina_stringshare_replace(&sd->new_file.filename, NULL);
   eina_stringshare_replace(&sd->dev, dev);
   eina_stringshare_replace(&sd->path, path);
   eina_stringshare_del(sd->realpath);
   sd->realpath = real_path;
   _e_fm2_queue_free(obj);
   _e_fm2_regions_free(obj);
   _e_fm2_icons_free(obj);
   sd->overlay_count = 0;
   edje_object_part_text_set(sd->overlay, "e.text.busy_label", "");

   _e_fm2_dir_load_props(sd);

   /* If the path change from a mountpoint to something else, we fake-unmount */
   if (sd->mount && sd->mount->mount_point && real_path
       && strncmp(sd->mount->mount_point, sd->realpath,
                  strlen(sd->mount->mount_point)))
     {
        e_fm2_device_unmount(sd->mount);
        sd->mount = NULL;
     }

   /* Clean up typebuf. */
   _e_fm2_typebuf_hide(obj);

   /* If the path is of type removable: we add a new mountpoint */
   if (sd->dev && !sd->mount && !strncmp(sd->dev, "removable:", 10))
     {
        E_Volume *v = NULL;

        v = e_fm2_device_volume_find(sd->dev + sizeof("removable:") - 1);
        if (v)
          {
             sd->mount = e_fm2_device_mount(v, _e_fm2_cb_mount_ok, _e_fm2_cb_mount_fail,
                                            _e_fm2_cb_unmount_ok, NULL, obj);
             if ((v->efm_mode == EFM_MODE_USING_HAL_MOUNT) && (!sd->mount->mounted)) return;
          }
     }
   else if (sd->config->view.open_dirs_in_place == 0)
     {
        E_Fm2_Mount *m;
        m = e_fm2_device_mount_find(sd->realpath);
        if (m)
          {
             sd->mount = e_fm2_device_mount(m->volume,
                                            _e_fm2_cb_mount_ok, _e_fm2_cb_mount_fail,
                                            _e_fm2_cb_unmount_ok, NULL, obj);
             if ((m->volume->efm_mode != EFM_MODE_USING_HAL_MOUNT) && (!sd->mount->mounted)) return;
          }
     }
   if (!sd->realpath) return;

   if (!sd->mount || sd->mount->mounted)
     {
        if (sd->dev && (!strcmp(sd->dev, "favorites")) && (!_e_fm2_favorites_thread))
          _e_fm2_favorites_thread = ecore_thread_run(_e_fm2_favorites_thread_cb,
                                                     _e_fm2_thread_cleanup_cb,
                                                     _e_fm2_thread_cleanup_cb, NULL);
        sd->id = _e_fm2_client_monitor_add(sd->realpath);
        sd->listing = EINA_TRUE;
     }

   evas_object_smart_callback_call(obj, "dir_changed", NULL);
   sd->tmp.iter = EINA_FALSE;
}

E_API void
e_fm2_underlay_show(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   evas_object_show(sd->underlay);
}

E_API void
e_fm2_underlay_hide(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   evas_object_hide(sd->underlay);
}

E_API void
e_fm2_all_unsel(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icon_desel_any(obj);
}

E_API void
e_fm2_all_sel(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icon_sel_any(obj);
}

E_API void
e_fm2_first_sel(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icon_sel_first(obj, EINA_FALSE);
}

E_API void
e_fm2_last_sel(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icon_sel_last(obj, EINA_FALSE);
}

E_API void
e_fm2_custom_theme_set(Evas_Object *obj, const char *path)
{
   EFM_SMART_CHECK();
   eina_stringshare_replace(&sd->custom_theme, path);
   _e_fm2_theme_edje_object_set(sd, sd->drop, "base/theme/fileman",
                                "list/drop_between");
   _e_fm2_theme_edje_object_set(sd, sd->drop_in, "base/theme/fileman",
                                "list/drop_in");
   _e_fm2_theme_edje_object_set(sd, sd->overlay, "base/theme/fileman",
                                "overlay");
   _e_fm2_theme_edje_object_set(sd, sd->sel_rect, "base/theme/fileman",
                                "rubberband");
}

E_API void
e_fm2_custom_theme_content_set(Evas_Object *obj, const char *content)
{
   EFM_SMART_CHECK();
   eina_stringshare_replace(&sd->custom_theme_content, content);
   _e_fm2_theme_edje_object_set(sd, sd->drop, "base/theme/fileman",
                                "list/drop_between");
   _e_fm2_theme_edje_object_set(sd, sd->drop_in, "base/theme/fileman",
                                "list/drop_in");
   _e_fm2_theme_edje_object_set(sd, sd->overlay, "base/theme/fileman",
                                "overlay");
}

E_API void
e_fm2_path_get(Evas_Object *obj, const char **dev, const char **path)
{
   if (dev) *dev = NULL;
   if (path) *path = NULL;
   EFM_SMART_CHECK();
   if (dev) *dev = sd->dev;
   if (path) *path = sd->path;
}

static E_Fm2_Custom_File *
_e_fm2_dir_load_props_from_parent(const char *path)
{
   E_Fm2_Custom_File *cf;
   char *parent;

   if ((!path) || (path[0] == '\0') || (strcmp(path, "/") == 0))
     return NULL;

   parent = ecore_file_dir_get(path);
   cf = e_fm2_custom_file_get(parent);
   if (((cf) && (cf->dir) && (cf->dir->prop.in_use)) || (!strcmp(parent, path)))
     {
        free(parent);
        return cf;
     }

   cf = _e_fm2_dir_load_props_from_parent(parent);
   free(parent);
   return cf;
}

static void
_e_fm2_dir_load_props(E_Fm2_Smart_Data *sd)
{
   E_Fm2_Custom_File *cf;

   if (!sd->realpath) return;  /* come back later */
   if (!(sd->view_flags & E_FM2_VIEW_LOAD_DIR_CUSTOM)) return;

   cf = e_fm2_custom_file_get(sd->realpath);
   if ((cf) && (cf->dir))
     {
        Evas_Coord x, y;

        if (sd->max.w - sd->w > 0)
          x = (sd->max.w - sd->w) * cf->dir->pos.x;
        else
          x = 0;

        if (sd->max.h - sd->h > 0)
          y = (sd->max.h - sd->h) * cf->dir->pos.y;
        else
          y = 0;

        e_fm2_pan_set(sd->obj, x, y);

        if (cf->dir->prop.in_use)
          {
             sd->view_mode = cf->dir->prop.view_mode;
             sd->icon_size = cf->dir->prop.icon_size;
             sd->order_file = !!cf->dir->prop.order_file;
             sd->show_hidden_files = !!cf->dir->prop.show_hidden_files;
             sd->inherited_dir_props = EINA_FALSE;
             sd->config->list.sort.no_case = cf->dir->prop.sort.no_case;
             sd->config->list.sort.size = cf->dir->prop.sort.size;
             sd->config->list.sort.extension = cf->dir->prop.sort.extension;
             sd->config->list.sort.mtime = cf->dir->prop.sort.mtime;
             sd->config->list.sort.dirs.first = cf->dir->prop.sort.dirs.first;
             sd->config->list.sort.dirs.last = cf->dir->prop.sort.dirs.last;
             return;
          }
     }
   else
     {
        sd->pos.x = 0;
        sd->pos.y = 0;
     }

   if (!(sd->view_flags & E_FM2_VIEW_INHERIT_DIR_CUSTOM))
     {
        sd->view_mode = -1;
        sd->icon_size = -1;
        sd->order_file = EINA_FALSE;
        sd->show_hidden_files = EINA_FALSE;
        sd->inherited_dir_props = EINA_FALSE;
        return;
     }

   sd->inherited_dir_props = EINA_TRUE;

   cf = _e_fm2_dir_load_props_from_parent(sd->realpath);
   if ((cf) && (cf->dir) && (cf->dir->prop.in_use))
     {
        sd->view_mode = cf->dir->prop.view_mode;
        sd->icon_size = cf->dir->prop.icon_size;
        sd->order_file = !!cf->dir->prop.order_file;
        sd->show_hidden_files = !!cf->dir->prop.show_hidden_files;
        sd->config->list.sort.no_case = cf->dir->prop.sort.no_case;
        sd->config->list.sort.size = cf->dir->prop.sort.size;
        sd->config->list.sort.extension = cf->dir->prop.sort.extension;
        sd->config->list.sort.mtime = cf->dir->prop.sort.mtime;
        sd->config->list.sort.dirs.first = cf->dir->prop.sort.dirs.first;
        sd->config->list.sort.dirs.last = cf->dir->prop.sort.dirs.last;
     }
   else
     {
        sd->view_mode = -1;
        sd->icon_size = -1;
        sd->order_file = EINA_FALSE;
        sd->show_hidden_files = EINA_FALSE;
     }
}

static void
_e_fm2_dir_save_props(E_Fm2_Smart_Data *sd)
{
   E_Fm2_Custom_File *cf, cf0;
   E_Fm2_Custom_Dir dir0;

   if (!(sd->view_flags & E_FM2_VIEW_SAVE_DIR_CUSTOM)) return;

   cf = e_fm2_custom_file_get(sd->realpath);
   if (!cf)
     {
        cf = &cf0;
        memset(cf, 0, sizeof(*cf));
        cf->dir = &dir0;
     }
   else if (!cf->dir)
     {
        E_Fm2_Custom_File *cf2 = cf;
        cf = &cf0;
        memcpy(cf, cf2, sizeof(*cf2));
        cf->dir = &dir0;
     }

   if (sd->max.w - sd->w > 0)
     cf->dir->pos.x = sd->pos.x / (double)(sd->max.w - sd->w);
   else
     cf->dir->pos.x = 0.0;

   if (sd->max.h - sd->h)
     cf->dir->pos.y = sd->pos.y / (double)(sd->max.h - sd->h);
   else
     cf->dir->pos.y = 0.0;

   cf->dir->prop.icon_size = sd->icon_size;
   cf->dir->prop.view_mode = sd->view_mode;
   cf->dir->prop.order_file = sd->order_file;
   cf->dir->prop.show_hidden_files = sd->show_hidden_files;
   cf->dir->prop.in_use = !sd->inherited_dir_props;

   if (sd->config)
     {
        cf->dir->prop.sort.no_case = sd->config->list.sort.no_case;
        cf->dir->prop.sort.size = sd->config->list.sort.size;
        cf->dir->prop.sort.extension = sd->config->list.sort.extension;
        cf->dir->prop.sort.mtime = sd->config->list.sort.mtime;
        cf->dir->prop.sort.dirs.first = sd->config->list.sort.dirs.first;
        cf->dir->prop.sort.dirs.last = sd->config->list.sort.dirs.last;
     }

   e_fm2_custom_file_set(sd->realpath, cf);
   e_fm2_custom_file_flush();
}

E_API void
e_fm2_refresh(Evas_Object *obj)
{
   EFM_SMART_CHECK();

   evas_object_smart_callback_call(sd->obj, "changed", NULL);

   _e_fm2_dir_save_props(sd);

   _e_fm2_queue_free(obj);
   _e_fm2_regions_free(obj);
   _e_fm2_icons_free(obj);

   sd->order_file = EINA_FALSE;

   if (sd->realpath)
     {
        sd->listing = EINA_FALSE;
        _e_fm2_client_monitor_del(sd->id, sd->realpath);
        sd->id = _e_fm2_client_monitor_add(sd->realpath);
        sd->listing = EINA_TRUE;
     }

   sd->tmp.iter = EINA_FALSE;
}

E_API int
e_fm2_has_parent_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(0);
   if (!sd->path) return 0;
   if (strcmp(sd->path, "/")) return 1;
   if (!sd->realpath) return 0;
   if ((sd->realpath[0] == 0) || (!strcmp(sd->realpath, "/"))) return 0;
   return 1;
}

E_API const char *
e_fm2_real_path_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   return sd->realpath;
}

E_API void
e_fm2_parent_go(Evas_Object *obj)
{
   char *p, *path;
   char buf[PATH_MAX];
   EFM_SMART_CHECK();
   if (!sd->path) return;
   path = memcpy(buf, sd->path, strlen(sd->path) + 1);
   if ((p = strrchr(path, '/'))) *p = 0;
   if (*path)
     e_fm2_path_set(obj, sd->dev, path);
   else if (sd->realpath)
     {
        path = ecore_file_dir_get(sd->realpath);
        e_fm2_path_set(obj, "/", path);
        free(path);
     }
}

E_API void
e_fm2_config_set(Evas_Object *obj, E_Fm2_Config *cfg)
{
   EFM_SMART_CHECK();
   if (sd->config == cfg) return;
   if (sd->config) _e_fm2_config_free(sd->config);
   sd->config = NULL;
   if (!cfg) return;
   sd->config = E_NEW(E_Fm2_Config, 1);
   if (!sd->config) return;
   memcpy(sd->config, cfg, sizeof(E_Fm2_Config));
   sd->config->icon.key_hint = eina_stringshare_add(cfg->icon.key_hint);
   sd->config->theme.background = eina_stringshare_add(cfg->theme.background);
   sd->config->theme.frame = eina_stringshare_add(cfg->theme.frame);
   sd->config->theme.icons = eina_stringshare_add(cfg->theme.icons);
}

E_API E_Fm2_Config *
e_fm2_config_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   return sd->config;
}

E_API Eina_List *
e_fm2_selected_list_get(Evas_Object *obj)
{
   Eina_List *list = NULL, *l;
   E_Fm2_Icon *ic;

   EFM_SMART_CHECK(NULL);
   EINA_LIST_FOREACH(sd->selected_icons, l, ic)
     list = eina_list_append(list, &(ic->info));
   return list;
}

E_API Eina_List *
e_fm2_all_list_get(Evas_Object *obj)
{
   Eina_List *list = NULL, *l;
   E_Fm2_Icon *ic;

   EFM_SMART_CHECK(NULL);
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        list = eina_list_append(list, &(ic->info));
     }
   return list;
}

E_API E_Fm2_Icon_Info *
e_fm2_icon_file_get(Evas_Object *obj, const char *file)
{
   Eina_List *l;
   E_Fm2_Icon *ic;

   EFM_SMART_CHECK(NULL);
   if (!file) return NULL;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if ((ic->info.file) && (!strcmp(ic->info.file, file)))
          return &(ic->info);
     }
   return NULL;
}

E_API void
e_fm2_deselect_all(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icon_desel_any(obj);
}

E_API void
e_fm2_select_set(Evas_Object *obj, const char *file, int select_)
{
   Eina_List *l;
   E_Fm2_Icon *ic;

   EFM_SMART_CHECK();
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if ((file) && (!strcmp(ic->info.file, file)))
          {
             if (select_) _e_fm2_icon_select(ic);
             else _e_fm2_icon_deselect(ic);
          }
        else
          {
             if (ic->sd->config->selection.single)
               _e_fm2_icon_deselect(ic);
             ic->last_selected = EINA_FALSE;
          }
     }
}

E_API void
e_fm2_file_show(Evas_Object *obj, const char *file)
{
   Eina_List *l;
   E_Fm2_Icon *ic;

   EFM_SMART_CHECK();
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!strcmp(ic->info.file, file))
          {
             _e_fm2_icon_make_visible(ic);
             return;
          }
     }
}

E_API void
e_fm2_icon_menu_replace_callback_set(Evas_Object *obj, E_Fm_Cb func, void *data)
{
   EFM_SMART_CHECK();
   sd->icon_menu.replace.func = func;
   sd->icon_menu.replace.data = data;
}

E_API void
e_fm2_icon_menu_start_extend_callback_set(Evas_Object *obj, E_Fm_Cb func, void *data)
{
   EFM_SMART_CHECK();
   sd->icon_menu.start.func = func;
   sd->icon_menu.start.data = data;
}

E_API void
e_fm2_icon_menu_end_extend_callback_set(Evas_Object *obj, E_Fm_Cb func, void *data)
{
   EFM_SMART_CHECK();
   sd->icon_menu.end.func = func;
   sd->icon_menu.end.data = data;
}

E_API void
e_fm2_icon_menu_flags_set(Evas_Object *obj, E_Fm2_Menu_Flags flags)
{
   EFM_SMART_CHECK();
   sd->icon_menu.flags = flags;
}

E_API E_Fm2_Menu_Flags
e_fm2_icon_menu_flags_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(0);
   return sd->icon_menu.flags;
}

E_API void
e_fm2_view_flags_set(Evas_Object *obj, E_Fm2_View_Flags flags)
{
   EFM_SMART_CHECK();
   sd->view_flags = flags;
}

E_API E_Fm2_View_Flags
e_fm2_view_flags_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(0);
   return sd->view_flags;
}

E_API E_Object *
e_fm2_window_object_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   return sd->eobj;
}

E_API void
e_fm2_window_object_set(Evas_Object *obj, E_Object *eobj)
{
   const char *drop[] = {"text/uri-list", "text/x-moz-url", "XdndDirectSave0"};

   EFM_SMART_CHECK();
   sd->eobj = eobj;
   sd->win = NULL;
   if (sd->drop_handler) e_drop_handler_del(sd->drop_handler);
   sd->drop_handler = e_drop_handler_add(sd->eobj, NULL,
                                         sd,
                                         _e_fm2_cb_dnd_enter,
                                         _e_fm2_cb_dnd_move,
                                         _e_fm2_cb_dnd_leave,
                                         _e_fm2_cb_dnd_selection_notify,
                                         drop, 3,
                                         sd->x, sd->y, sd->w, sd->h);
   e_drop_handler_responsive_set(sd->drop_handler);
   e_drop_handler_xds_set(sd->drop_handler, _e_fm2_cb_dnd_drop);
}

E_API void
e_fm2_window_set(Evas_Object *obj, Evas_Object *win)
{
   const char *drop[] = {"text/uri-list", "text/x-moz-url", "XdndDirectSave0"};

   EFM_SMART_CHECK();
   sd->win = win;
   sd->eobj = NULL;
   if (sd->drop_handler) e_drop_handler_del(sd->drop_handler);
   sd->drop_handler = e_drop_handler_add(NULL, win,
                                         sd,
                                         _e_fm2_cb_dnd_enter,
                                         _e_fm2_cb_dnd_move,
                                         _e_fm2_cb_dnd_leave,
                                         _e_fm2_cb_dnd_selection_notify,
                                         drop, 3,
                                         sd->x, sd->y, sd->w, sd->h);
   e_drop_handler_responsive_set(sd->drop_handler);
   e_drop_handler_xds_set(sd->drop_handler, _e_fm2_cb_dnd_drop);
}

static void
_e_fm2_icons_update_helper(E_Fm2_Smart_Data *sd, Eina_Bool icon_only)
{
   Eina_List *l;
   E_Fm2_Icon *ic;
   char buf[PATH_MAX], *pfile;
   int bufused, buffree;

   if ((!sd->realpath) || (!sd->icons)) return;
   bufused = eina_strlcpy(buf, sd->realpath, sizeof(buf));
   if (bufused >= (int)(sizeof(buf) - 2))
     return;

   if ((bufused > 0) && (buf[bufused - 1] != '/'))
     {
        buf[bufused] = '/';
        bufused++;
     }

   pfile = buf + bufused;
   buffree = sizeof(buf) - bufused;

   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        E_Fm2_Custom_File *cf;

        eina_stringshare_del(ic->info.icon);
        ic->info.icon = NULL;
        ic->info.icon_type = EINA_FALSE;

        if (_e_fm2_file_is_desktop(ic->info.file))
          _e_fm2_icon_desktop_load(ic);

        if ((int)eina_strlcpy(pfile, ic->info.file, buffree) >= buffree)
          continue;

        cf = e_fm2_custom_file_get(buf);
        if (cf)
          {
             if (cf->icon.valid)
               {
                  eina_stringshare_replace(&ic->info.icon, cf->icon.icon);
                  ic->info.icon_type = cf->icon.type;
               }
          }

        if (!ic->realized) continue;
        if (icon_only)
          {
             E_FREE_FUNC(ic->obj_icon, evas_object_del);
             _e_fm2_icon_icon_set(ic);
          }
        else
          {
             _e_fm2_icon_unrealize(ic);
             _e_fm2_icon_realize(ic);
          }
     }
   e_fm2_custom_file_flush();
}

E_API void
e_fm2_icons_update(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_icons_update_helper(sd, EINA_FALSE);
}

E_API void
e_fm2_pan_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   EFM_SMART_CHECK();
   x = MIN(x, sd->max.w - sd->w);
   if (x < 0) x = 0;
   y = MIN(y, sd->max.h - sd->h);
   if (y < 0) y = 0;
   if ((sd->pos.x == x) && (sd->pos.y == y)) return;
   sd->pos.x = x;
   sd->pos.y = y;
   if (sd->scroll_job) ecore_job_del(sd->scroll_job);
   sd->scroll_job = ecore_job_add(_e_fm2_cb_scroll_job, obj);
}

E_API void
e_fm2_pan_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   EFM_SMART_CHECK();
   if (x) *x = sd->pos.x;
   if (y) *y = sd->pos.y;
}

E_API void
e_fm2_pan_max_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   Evas_Coord mx, my;
   EFM_SMART_CHECK();
   mx = sd->max.w - sd->w;
   if (mx < 0) mx = 0;
   my = sd->max.h - sd->h;
   if (my < 0) my = 0;
   if (x) *x = mx;
   if (y) *y = my;
}

E_API void
e_fm2_pan_child_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   EFM_SMART_CHECK();
   if (w) *w = sd->max.w;
   if (h) *h = sd->max.h;
}

E_API void
e_fm2_all_icons_update(void)
{
   Evas_Object *o;
   const Eina_List *l;

   _e_fm2_list_walking++;
   EINA_LIST_FOREACH(_e_fm2_list, l, o)
     {
        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, o))) continue;
        e_fm2_icons_update(o);
     }
   _e_fm2_list_walking--;
   if (_e_fm2_list_walking == 0)
     {
        EINA_LIST_FREE(_e_fm2_list_remove, o)
          {
             _e_fm2_list = eina_list_remove(_e_fm2_list, o);
          }
     }
}

static const char *
_e_fm2_path_join(char *buf, int buflen, const char *base, const char *component)
{
   if ((!buf) || (!component))
     return NULL;

   if (component[0] == '/')
     return component;
   else if (component[0] == '\0')
     return base;
   else if (component[0] == '.')
     {
        if (component[1] == '/')
          {
             component += 2;

             if (!base)
               return component;

             if (snprintf(buf, buflen, "%s/%s", base, component) < buflen)
               return buf;
             else
               return NULL;
          }
        else if ((component[1] == '.') && (component[2] == '/'))
          {
             const char *p;
             int len;

             component += 3;

             if (!base)
               return component;

             p = strrchr(base, '/');
             if (!p)
               return component;

             len = p - base;
             if (snprintf(buf, buflen, "%.*s/%s", len, base, component) < buflen)
               return buf;
             else
               return NULL;
          }
     }

   if (snprintf(buf, buflen, "%s/%s", base, component) < buflen)
     return buf;
   else
     return NULL;
}

/**
 * Explicitly set an Edje icon from the given icon path.
 *
 * @param iconpath path to edje file that contains 'icon' group.
 *
 * @see _e_fm2_icon_explicit_get()
 */
static Evas_Object *
_e_fm2_icon_explicit_edje_get(Evas *evas, const E_Fm2_Icon *ic EINA_UNUSED, const char *iconpath, const char **type_ret)
{
   Evas_Object *o = edje_object_add(evas);
   if (!o)
     return NULL;

   if (!edje_object_file_set(o, iconpath, "icon"))
     {
        evas_object_del(o);
        return NULL;
     }

   if (type_ret) *type_ret = "CUSTOM";
   return o;
}

/**
 * Explicitly set icon from theme using its name.
 *
 * @param name will be prefixed by 'e/icons/' to form the group name in theme.
 *
 * @see e_util_edje_icon_set()
 * @see _e_fm2_icon_explicit_get()
 */
static Evas_Object *
_e_fm2_icon_explicit_theme_icon_get(Evas *evas, const E_Fm2_Icon *ic EINA_UNUSED, const char *name, const char **type_ret)
{
   Evas_Object *o = e_icon_add(evas);

   if (!o) return NULL;

   e_icon_scale_size_set(o, _e_fm2_icon_mime_size_normalize(ic));

   if (!e_util_icon_theme_set(o, name))
     {
        evas_object_del(o);
        return NULL;
     }

   if (type_ret) *type_ret = "THEME_ICON";
   return o;
}

/**
 * Explicitly set icon from file manager theem using its name.
 *
 * @param name will be prefixed with 'base/theme/fileman' to form the
 *    group name in theme.
 *
 * @see _e_fm2_theme_edje_icon_object_set()
 * @see _e_fm2_icon_explicit_get()
 */
static Evas_Object *
_e_fm2_icon_explicit_theme_get(Evas *evas, const E_Fm2_Icon *ic, const char *name, const char **type_ret)
{
   Evas_Object *o = edje_object_add(evas);
   if (!o)
     return NULL;

   if (!_e_fm2_theme_edje_icon_object_set(ic->sd, o, "base/theme/fileman", name))
     {
        evas_object_del(o);
        return NULL;
     }

   if (type_ret) *type_ret = "THEME";
   return o;
}

/**
 * Explicitly set icon to given value.
 *
 * This will try to identify if icon is an edje or regular file or even
 * an icon name to get from icon set.
 *
 * @param icon might be an absolute or relative path, or icon name or edje path.
 */
static Evas_Object *
_e_fm2_icon_explicit_get(Evas *evas, const E_Fm2_Icon *ic, const char *icon, const char **type_ret)
{
   char buf[PATH_MAX];
   const char *iconpath;

   iconpath = _e_fm2_path_join(buf, sizeof(buf), ic->sd->realpath, icon);
   if (!iconpath)
     {
        ERR("could not create icon \"%s\".", icon);
        return NULL;
     }

   if (_e_fm2_file_is_edje(iconpath))
     return _e_fm2_icon_explicit_edje_get(evas, ic, iconpath, type_ret);
   else
     {
        Evas_Object *o = e_icon_add(evas);
        if (!o)
          return NULL;

        e_icon_scale_size_set(o, _e_fm2_icon_mime_size_normalize(ic));
        e_icon_file_set(o, iconpath);
        e_icon_fill_inside_set(o, 1);
        if (type_ret) *type_ret = "CUSTOM";
        return o;
     }

   return NULL;
}

/**
 * Creates an icon that generates a thumbnail if required.
 *
 * @param group if given, will be used in e_thumb_icon_file_set()
 * @param cb function to callback when thumbnail generation is over.
 * @param data extra data to give to @p cb
 * @param force_gen whenever to force generation of thumbnails, even it exists.
 */
static Evas_Object *
_e_fm2_icon_thumb_get(Evas *evas, const E_Fm2_Icon *ic, const char *group, Evas_Smart_Cb cb, void *data, int force_gen, const char **type_ret)
{
   Evas_Object *o;
   char buf[PATH_MAX];

   if (ic->thumb_failed)
     return NULL;

   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return NULL;

   o = e_thumb_icon_add(evas);
   if (!o)
     return NULL;

   if (cb) evas_object_smart_callback_add(o, "e_thumb_gen", cb, data);
   e_thumb_icon_size_set(o, 256, 256);
   e_thumb_icon_file_set(o, buf, group);
   _e_fm2_icon_thumb(ic, o, force_gen);
   if (type_ret) *type_ret = "THUMB";
   return o;
}

/**
 * Generates the thumbnail of the given edje file.
 *
 * It will use 'icon.key_hint' from config if set and then try some well
 * known groups like 'icon', 'e/desktop/background' and 'e/init/splash'.
 */
static Evas_Object *
_e_fm2_icon_thumb_edje_get(Evas *evas, const E_Fm2_Icon *ic, Evas_Smart_Cb cb, void *data, int force_gen, const char **type_ret)
{
   char buf[PATH_MAX];
   const char **itr = NULL, *group = NULL;
   const char *known_groups[] = {
      NULL,
      "e/desktop/background",
      "icon",
      "e/init/splash",
      /* XXX TODO: add more? example 'screenshot', 'preview' */
      NULL
   };
   Evas_Object *o;

   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf))) return NULL;

   known_groups[0] = ic->sd->config->icon.key_hint;
   if (known_groups[0]) itr = known_groups;
   else itr = known_groups + 1;

   for (; *itr; itr++)
     {
        if (edje_file_group_exists(buf, *itr)) break;
     }

   if (*itr)
     group = eina_stringshare_add(*itr);
   else
     {
        Eina_List *l;

        l = edje_file_collection_list(buf);
        group = eina_stringshare_ref(eina_list_data_get(l));
        edje_file_collection_list_free(l);
     }
   if (!group) return NULL;
   o = _e_fm2_icon_thumb_get(evas, ic, group, cb, data, force_gen, type_ret);
   eina_stringshare_del(group);
   return o;
}

static Eina_Bool
_e_fm2_icon_cache_update(void *data, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   _e_fm2_icons_update_helper(data, EINA_TRUE);
   return ECORE_CALLBACK_RENEW;
}

/**
 * Machinery for _e_fm2_icon_desktop_get() and others with instances of desktop.
 */
static Evas_Object *
_e_fm2_icon_desktop_get_internal(Evas *evas, const E_Fm2_Icon *ic, Efreet_Desktop *desktop, const char **type_ret)
{
   Evas_Object *o;

   if (!desktop->icon)
     return NULL;

   if (_e_fm2_file_is_edje(desktop->icon))
     return _e_fm2_icon_explicit_edje_get(evas, ic, desktop->icon, type_ret);

   o = _e_fm2_icon_explicit_theme_icon_get(evas, ic, desktop->icon, type_ret);
   if (o) return o;

   o = e_util_desktop_icon_add(desktop, 48, evas);
//   o = e_util_icon_theme_icon_add(desktop->icon, 48, evas);
   if (o && type_ret) *type_ret = "DESKTOP";
   return o;
}

/**
 * Use freedesktop.org '.desktop' files to set icon.
 */
static Evas_Object *
_e_fm2_icon_desktop_get(Evas *evas, const E_Fm2_Icon *ic, const char **type_ret)
{
   Efreet_Desktop *ef;
   Evas_Object *o;
   char buf[PATH_MAX];

   if (!ic->info.file) return NULL;

   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return NULL;

   ef = efreet_desktop_new(buf);
   if (!ef) return NULL;

   o = _e_fm2_icon_desktop_get_internal(evas, ic, ef, type_ret);
   efreet_desktop_free(ef);
   return o;
}

static inline const char *
_e_fm2_icon_mime_type_special_match(const E_Fm2_Icon *ic)
{
   const Eina_List *l;
   const E_Config_Mime_Icon *mi;
   const char *mime = ic->info.mime;

   EINA_LIST_FOREACH(e_config->mime_icons, l, mi)
     if (mi->mime == mime) /* both in the same stringshare pool */
       return mi->icon;

   return NULL;
}

static Evas_Object *
_e_fm2_icon_mime_fdo_get(Evas *evas, const E_Fm2_Icon *ic, const char **type_ret)
{
   const char *icon;
   unsigned int size;

   size = _e_fm2_icon_mime_size_normalize(ic);
   icon = efreet_mime_type_icon_get(ic->info.mime, e_config->icon_theme, size);
   if (icon)
     return _e_fm2_icon_explicit_get(evas, ic, icon, type_ret);
   return NULL;
}

static Evas_Object *
_e_fm2_icon_mime_theme_get(Evas *evas, const E_Fm2_Icon *ic, const char **type_ret EINA_UNUSED)
{
   char buf[1024];
   const char *file;

   if (snprintf(buf, sizeof(buf), "e/icons/fileman/mime/%s", ic->info.mime) >=
       (int)sizeof(buf))
     return NULL;

   file = e_theme_edje_file_get("base/theme/icons", buf);
   if (file && file[0])
     {
        Evas_Object *o = edje_object_add(evas);
        if (!o) return NULL;
        if (!edje_object_file_set(o, file, buf))
          {
             evas_object_del(o);
             return NULL;
          }
        return o;
     }

   return NULL;
}

/**
 * Use mime type information to set icon.
 */
static Evas_Object *
_e_fm2_icon_mime_get(Evas *evas, const E_Fm2_Icon *ic, Evas_Smart_Cb gen_func EINA_UNUSED, void *data EINA_UNUSED, int force_gen EINA_UNUSED, const char **type_ret)
{
   Evas_Object *o = NULL;

   if (e_config->icon_theme_overrides)
     o = _e_fm2_icon_mime_fdo_get(evas, ic, type_ret);
   else
     o = _e_fm2_icon_mime_theme_get(evas, ic, type_ret);

   if (o) return o;

   if (!e_config->icon_theme_overrides)
     o = _e_fm2_icon_mime_fdo_get(evas, ic, type_ret);
   else
     o = _e_fm2_icon_mime_theme_get(evas, ic, type_ret);

   return o;
}

/**
 * Discovers the executable of Input Method Config file and set icon.
 */
static Evas_Object *
_e_fm2_icon_imc_get(Evas *evas, const E_Fm2_Icon *ic, const char **type_ret)
{
   E_Input_Method_Config *imc;
   Efreet_Desktop *desktop;
   Eet_File *imc_ef;
   Evas_Object *o = NULL;
   char buf[PATH_MAX];

   if (!ic->info.file)
     return NULL;

   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return NULL;

   imc_ef = eet_open(buf, EET_FILE_MODE_READ);
   if (!imc_ef)
     return NULL;

   imc = e_intl_input_method_config_read(imc_ef);
   eet_close(imc_ef);

   if (!imc->e_im_setup_exec)
     {
        e_intl_input_method_config_free(imc);
        return NULL;
     }

   desktop = efreet_util_desktop_exec_find(imc->e_im_setup_exec);
   if (desktop)
     {
        o = _e_fm2_icon_desktop_get_internal(evas, ic, desktop, type_ret);
        efreet_desktop_free(desktop);
     }
   e_intl_input_method_config_free(imc);

   if ((o) && (type_ret)) *type_ret = "IMC";
   return o;
}

/**
 * Use heuristics to discover and set icon.
 */
static Evas_Object *
_e_fm2_icon_discover_get(Evas *evas, const E_Fm2_Icon *ic, Evas_Smart_Cb gen_func, void *data, int force_gen, const char **type_ret)
{
   const char *p;

   p = strrchr(ic->info.file, '.');
   if (!p) return NULL;

   p++;
   if (_e_fm2_ext_is_edje(p))
     return _e_fm2_icon_thumb_edje_get(evas, ic, gen_func,
                                       data, force_gen, type_ret);
   if (_e_fm2_ext_is_desktop(p))
     return _e_fm2_icon_desktop_get(evas, ic, type_ret);
   if (_e_fm2_ext_is_imc(p))
     return _e_fm2_icon_imc_get(evas, ic, type_ret);
   if (evas_object_image_extension_can_load_get(p - 1))
     return _e_fm2_icon_thumb_get(evas, ic, NULL, gen_func, data, force_gen, type_ret);
   return NULL;
}

/**
 * Get the object representing the icon.
 *
 * @param evas canvas instance to use to store the icon.
 * @param ic icon to get information in order to find the icon.
 * @param gen_func if thumbnails need to be generated, call this function
 *     when it's over.
 * @param data extra data to give to @p gen_func.
 * @param force_gen force thumbnail generation.
 * @param type_ret string that identifies type of icon.
 */
E_API Evas_Object *
e_fm2_icon_get(Evas *evas, E_Fm2_Icon *ic,
               Evas_Smart_Cb gen_func,
               void *data, int force_gen, const char **type_ret)
{
   const char *icon;
   Evas_Object *o = NULL;

   if (ic->info.icon)
     {
        if ((ic->info.icon[0] == '/') ||
            ((ic->info.icon[0] == '.') &&
             ((ic->info.icon[1] == '/') ||
              ((ic->info.icon[1] == '.') && (ic->info.icon[2] == '/')))))
          {
             o = _e_fm2_icon_explicit_get(evas, ic, ic->info.icon, type_ret);
             if (o) return o;
          }

        o = _e_fm2_icon_explicit_theme_icon_get(evas, ic, ic->info.icon, type_ret);
        if (o) return o;
     }

   if (ic->sd->config->icon.max_thumb_size && (ic->info.statinfo.st_size > ic->sd->config->icon.max_thumb_size * 1024 * 1024))
     ic->thumb_failed = EINA_TRUE;

   /* create thumbnails for edje files */
   if ((ic->info.file) && (_e_fm2_file_is_edje(ic->info.file)))
     {
        o = _e_fm2_icon_thumb_edje_get
            (evas, ic, gen_func, data, force_gen, type_ret);
        if (o) return o;
     }

   /* disabled until everyone has edje in mime.types:
    *  use mimetype to identify edje.
    * if (ic->info.mime == _e_fm2_mime_app_edje)
    *   return _e_fm2_icon_thumb_edje_get
    *     (evas, ic, gen_func, data, force_gen, type_ret); */

   /* check user preferences */
   icon = _e_fm2_icon_mime_type_special_match(ic);
   if (icon)
     {
        if (icon == _e_fm2_icon_desktop_str)
          o = _e_fm2_icon_desktop_get(evas, ic, type_ret);
        else if (icon == _e_fm2_icon_thumb_str)
          {
             if (!ic->thumb_failed)
               o = _e_fm2_icon_thumb_get
                   (evas, ic, NULL, gen_func, data, force_gen, type_ret);
          }
        else if (strncmp(icon, "e/icons/fileman/", 16) == 0)
          o = _e_fm2_icon_explicit_theme_get(evas, ic, icon + 16, type_ret);
        else
          o = _e_fm2_icon_explicit_get(evas, ic, icon, type_ret);

        if (o) return o;
     }

   if ((!ic->thumb_failed) && (ic->info.icon_type == 1))
     {
        o = _e_fm2_icon_thumb_get(evas, ic, NULL,
                                  gen_func, data, force_gen, type_ret);
        if (o) return o;
     }

   if (ic->info.file)
     {
        o = _e_fm2_icon_discover_get(evas, ic, gen_func, data,
                                     force_gen, type_ret);
        if (o) return o;
     }
   if (ic->info.mime)
     {
        o = _e_fm2_icon_mime_get(evas, ic, gen_func, data, force_gen, type_ret);
        if (o) return o;
     }

//fallback:
   o = _e_fm2_icon_explicit_theme_icon_get(evas, ic, "unknown", type_ret);
   if (o) return o;

   return _e_fm2_icon_explicit_theme_get(evas, ic, "text/plain", type_ret);
}

E_API E_Fm2_Icon_Info *
e_fm2_icon_file_info_get(E_Fm2_Icon *ic)
{
   if (!ic) return NULL;
   return &(ic->info);
}

E_API void
e_fm2_icon_geometry_get(E_Fm2_Icon *ic, int *x, int *y, int *w, int *h)
{
   int xx, yy, ww, hh;

   if (x) *x = 0;
   if (y) *y = 0;
   if (w) *w = 0;
   if (h) *h = 0;

   if (ic)
     {
        evas_object_geometry_get(ic->obj, &xx, &yy, &ww, &hh);
        if (x) *x = xx;
        if (y) *y = yy;
        if (w) *w = ww;
        if (h) *h = hh;
     }
}

/* FIXME: track real exe with exe del events etc. */
static int _e_fm2_client_spawning = 0;

static void
_e_fm2_client_spawn(void)
{
   char buf[4096];

   if (_e_fm2_client_spawning) return;
   if (e_sys_on_the_way_out_get()) return;
   snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_fm", e_prefix_lib_get());
   if (_e_fm2_exe) e_fm2_die();
   _e_fm2_exe = ecore_exe_pipe_run(buf, ECORE_EXE_NOT_LEADER | ECORE_EXE_TERM_WITH_PARENT, NULL);
   _e_fm2_client_spawning = 1;
}

static E_Fm2_Client *
_e_fm2_client_get(void)
{
   Eina_List *l;
   E_Fm2_Client *cl, *cl_chosen = NULL;
   int min_req = 0x7fffffff;

   /* if we don't have a slave - spawn one */
   if (!_e_fm2_client_list)
     {
        _e_fm2_client_spawn();
        return NULL;
     }
   EINA_LIST_FOREACH(_e_fm2_client_list, l, cl)
     {
        if (cl->req < min_req)
          {
             min_req = cl->req;
             cl_chosen = cl;
          }
     }
   return cl_chosen;
}

typedef struct _E_Fm2_Message E_Fm2_Message;

struct _E_Fm2_Message
{
   int   major, minor, ref, ref_to, response;
   void *data;
   int   size;
};

static Eina_List *_e_fm2_messages = NULL;

static void
_e_fm2_client_message_queue(int major, int minor, int ref, int ref_to, int response, const void *data, int size)
{
   E_Fm2_Message *msg;

   msg = E_NEW(E_Fm2_Message, 1);
   if (!msg) return;
   msg->major = major;
   msg->minor = minor;
   msg->ref = ref;
   msg->ref_to = ref_to;
   msg->response = response;
   if (data)
     {
        msg->size = size;
        msg->data = malloc(size);
        if (msg->data)
          memcpy(msg->data, data, size);
        else
          {
             free(msg);
             return;
          }
     }
   _e_fm2_messages = eina_list_append(_e_fm2_messages, msg);
}

static void
_e_fm2_client_message_flush(E_Fm2_Client *cl, E_Fm2_Message *msg)
{
   _e_fm2_messages = eina_list_remove(_e_fm2_messages, msg);
   ecore_ipc_client_send(cl->cl, msg->major, msg->minor,
                         msg->ref, msg->ref_to, msg->response,
                         msg->data, msg->size);
   cl->req++;
   free(msg->data);
   free(msg);
}

static void
_e_fm2_client_messages_flush(void)
{
   while (_e_fm2_messages)
     {
        E_Fm2_Client *cl;

        cl = _e_fm2_client_get();
        if (!cl) break;
        _e_fm2_client_message_flush(cl, eina_list_data_get(_e_fm2_messages));
     }
}

static int
_e_fm_client_send_new(int minor, void *data, int size)
{
   static int id = 0;
   E_Fm2_Client *cl;

   id++;
   cl = _e_fm2_client_get();
   if (!cl)
     {
        _e_fm2_client_message_queue(E_IPC_DOMAIN_FM, minor,
                                    id, 0, 0,
                                    data, size);
     }
   else
     {
        ecore_ipc_client_send(cl->cl, E_IPC_DOMAIN_FM, minor,
                              id, 0, 0,
                              data, size);
        cl->req++;
     }

   return id;
}

static int
_e_fm_client_send(int minor, int id, void *data, int size)
{
   E_Fm2_Client *cl;

   cl = _e_fm2_client_get();
   if (!cl)
     {
        _e_fm2_client_message_queue(E_IPC_DOMAIN_FM, minor,
                                    id, 0, 0,
                                    data, size);
     }
   else
     {
        ecore_ipc_client_send(cl->cl, E_IPC_DOMAIN_FM, minor,
                              id, 0, 0,
                              data, size);
        cl->req++;
     }

   return id;
}

static int
_e_fm2_client_monitor_add(const char *path)
{
   return _e_fm_client_send_new(E_FM_OP_MONITOR_START, (void *)path, strlen(path) + 1);
}

static void
_e_fm2_client_monitor_del(int id, const char *path)
{
   _e_fm_client_send(E_FM_OP_MONITOR_END, id, (void *)path, strlen(path) + 1);
}

static int
_e_fm_client_file_del(const char *files, Eina_Bool secure, Evas_Object *e_fm)
{
   int id, op = E_FM_OP_REMOVE;

   if (secure) op = E_FM_OP_SECURE_REMOVE;

   id = _e_fm_client_send_new(op, (void *)files, strlen(files) + 1);
   e_fm2_op_registry_entry_add(id, e_fm, op, _e_fm2_operation_abort_internal);
   return id;
}

#if 0
static int
_e_fm2_client_file_trash(const char *path, Evas_Object *e_fm)
{
   int id = _e_fm_client_send_new(E_FM_OP_TRASH, (void *)path, strlen(path) + 1);
   e_fm2_op_registry_entry_add(id, e_fm, E_FM_OP_TRASH, _e_fm2_operation_abort_internal);
   return id;
}

static int
_e_fm2_client_file_mkdir(const char *path, const char *rel, int rel_to, int x, int y, int res_w EINA_UNUSED, int res_h EINA_UNUSED, Evas_Object *e_fm)
{
   char *d;
   int l1, l2, l, id;

   l1 = strlen(path);
   l2 = strlen(rel);
   l = l1 + 1 + l2 + 1 + (sizeof(int) * 3);
   d = alloca(l);
   strcpy(d, path);
   strcpy(d + l1 + 1, rel);
   memcpy(d + l1 + 1 + l2 + 1, &rel_to, sizeof(int));
   memcpy(d + l1 + 1 + l2 + 1 + sizeof(int), &x, sizeof(int));
   memcpy(d + l1 + 1 + l2 + 1 + (2 * sizeof(int)), &y, sizeof(int));

   id = _e_fm_client_send_new(E_FM_OP_MKDIR, (void *)d, l);
   e_fm2_op_registry_entry_add(id, e_fm, E_FM_OP_MKDIR, _e_fm2_operation_abort_internal);
   return id;
}

#endif

E_API int
e_fm2_client_file_move(Evas_Object *e_fm, const char *args)
{
   int id;
   E_Fm_Op_Type op = e_config->filemanager_copy ? E_FM_OP_MOVE : E_FM_OP_RENAME;

   id = _e_fm_client_send_new(op, (void *)args, strlen(args) + 1);
   e_fm2_op_registry_entry_add(id, e_fm, op, _e_fm2_operation_abort_internal);
   return id;
}

E_API int
e_fm2_client_file_copy(Evas_Object *e_fm, const char *args)
{
   int id = _e_fm_client_send_new(E_FM_OP_COPY, (void *)args, strlen(args) + 1);
   e_fm2_op_registry_entry_add(id, e_fm, E_FM_OP_COPY, _e_fm2_operation_abort_internal);
   return id;
}

E_API int
e_fm2_client_file_symlink(Evas_Object *e_fm, const char *args)
{
   int id = _e_fm_client_send_new(E_FM_OP_SYMLINK, (void *)args, strlen(args) + 1);
   e_fm2_op_registry_entry_add(id, e_fm, E_FM_OP_SYMLINK, _e_fm2_operation_abort_internal);
   return id;
}

E_API int
_e_fm2_client_mount(const char *udi, const char *mountpoint)
{
   char *d;
   int l, l1, l2 = 0;

   if (!udi)
     return 0;

   l1 = strlen(udi);
   if (mountpoint)
     {
        l2 = strlen(mountpoint);
        l = l1 + 1 + l2 + 1;
     }
   else
     l = l1 + 1;
   d = alloca(l);
   strcpy(d, udi);
   if (mountpoint)
     strcpy(d + l1 + 1, mountpoint);

   return _e_fm_client_send_new(E_FM_OP_MOUNT, (void *)d, l);
}

E_API int
_e_fm2_client_unmount(const char *udi)
{
   char *d;
   int l, l1;

   if (!udi)
     return 0;

   l1 = strlen(udi);
   l = l1 + 1;
   d = alloca(l);
   strcpy(d, udi);

   _e_fm2_client_get();

   elm_cache_all_flush();
   edje_file_cache_flush();
   edje_collection_cache_flush();
   if (e_comp)
     {
        evas_image_cache_flush(e_comp->evas);
        evas_font_cache_flush(e_comp->evas);
     }

   return _e_fm_client_send_new(E_FM_OP_UNMOUNT, (void *)d, l);
}

E_API int
_e_fm2_client_eject(const char *udi)
{
   char *data;
   int size;

   if (!udi)
     return 0;

   size = strlen(udi) + 1;
   data = alloca(size);
   strcpy(data, udi);

   fprintf(stderr, "EJECT [%s]\n", udi); // udi == /dev/sdd1 :)
   return _e_fm_client_send_new(E_FM_OP_EJECT, data, size);
}

static void
_e_fm2_client_monitor_list_end(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   sd->busy_count--;
   if (sd->busy_count == 0)
     {
        edje_object_signal_emit(sd->overlay, "e,state,busy,stop", "e");
        e_fm2_custom_file_flush();
     }
   if (sd->tmp.obj)
     {
        evas_object_del(sd->tmp.obj);
        sd->tmp.obj = NULL;
     }
   if (sd->tmp.obj2)
     {
        evas_object_del(sd->tmp.obj2);
        sd->tmp.obj2 = NULL;
     }
   if (sd->scan_timer)
     {
        ecore_timer_del(sd->scan_timer);
        sd->scan_timer = NULL;
     }
   if (sd->sort_idler)
     {
        ecore_idler_del(sd->sort_idler);
        sd->sort_idler = NULL;
     }
   E_FREE(sd->tmp.list_index);
   _e_fm2_queue_free(obj);
   _e_fm2_obj_icons_place(sd);
   _e_fm2_live_process_begin(obj);
}

E_API void
_e_fm2_file_force_update(const char *path)
{
   Evas_Object *o;
   char *dir;
   Eina_List *l;

   dir = ecore_file_dir_get(path);
   if (!dir) return;
   EINA_LIST_FOREACH(_e_fm2_list, l, o)
     {
        const char *rp;
        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, o))) continue;
        rp = e_fm2_real_path_get(o);
        if (!rp) continue;
        if (!strcmp(rp, dir))
          {
             E_Fm2_Icon *ic;

             ic = _e_fm2_icon_find(o, ecore_file_file_get(path));
             if (ic)
               {
                  E_Fm2_Finfo finf;

                  memset(&finf, 0, sizeof(E_Fm2_Finfo));
                  memcpy(&(finf.st), &(ic->info.statinfo),
                         sizeof(struct stat));
                  finf.broken_link = ic->info.broken_link;
                  finf.lnk = ic->info.link;
                  finf.rlnk = ic->info.real_link;
                  ic->removable_state_change = EINA_TRUE;
                  _e_fm2_live_file_changed(o, ecore_file_file_get(path),
                                           &finf);
               }
          }
     }
   free(dir);
}

E_API void
e_fm2_client_data(Ecore_Ipc_Event_Client_Data *e)
{
   Evas_Object *obj;
   Eina_List *l, *dels = NULL;
   E_Fm2_Client *cl;

   if (e->major != 6 /*E_IPC_DOMAIN_FM*/) return;
   EINA_LIST_FOREACH(_e_fm2_client_list, l, cl)
     {
        if (cl->cl == e->client) break;
     }
   if (!l)
     {
        cl = E_NEW(E_Fm2_Client, 1);
        cl->cl = e->client;
        _e_fm2_client_list = eina_list_prepend(_e_fm2_client_list, cl);
        /* FIXME: new client - send queued msgs */
        _e_fm2_client_spawning = 0;
        _e_fm2_client_messages_flush();
     }

   _e_fm2_list_walking++;
   EINA_LIST_FOREACH(_e_fm2_list, l, obj)
     {
        unsigned char *p;
        char *evdir;
        const char *dir, *path, *lnk, *rlnk, *file;
        struct stat st;
        int broken_link;
        E_Fm2_Smart_Data *sd;

        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, obj))) continue;
        dir = e_fm2_real_path_get(obj);
        sd = evas_object_smart_data_get(obj);
        switch (e->minor)
          {
           case E_FM_OP_HELLO: /*hello*/
//             printf("E_FM_OP_HELLO\n");
             break;

           case E_FM_OP_OK: /*req ok*/
//             printf("E_FM_OP_OK\n");
             cl->req--;
             break;

           case E_FM_OP_FILE_ADD: /*file add*/
//             printf("E_FM_OP_FILE_ADD\n");
           case E_FM_OP_FILE_CHANGE: /*file change*/
//             printf("E_FM_OP_FILE_CHANGE\n");
           {
              E_Fm2_Finfo finf;

              p = e->data;
              /* NOTE: i am NOT converting this data to portable arch/os independent
               * format. i am ASSUMING e_fm_main and e are local and built together
               * and thus this will work. if this ever changes this here needs to
               * change */
              memcpy(&st, p, sizeof(struct stat));
              p += sizeof(struct stat);

              broken_link = p[0];
              p += 1;

              path = (char *)p;
              p += strlen(path) + 1;

              lnk = (char *)p;
              p += strlen(lnk) + 1;

              rlnk = (char *)p;

              memcpy(&(finf.st), &st, sizeof(struct stat));
              finf.broken_link = broken_link;
              finf.lnk = lnk;
              finf.rlnk = rlnk;

              evdir = ecore_file_dir_get(path);
              if ((evdir) && (sd->id == e->ref_to) &&
                  ((!strcmp(evdir, "") || ((dir) && (!strcmp(dir, evdir))))))
                {
//                       printf(" ch/add response = %i\n", e->response);
                   free(evdir);
                   if (e->response == 0)    /*live changes*/
                     {
                        if (e->minor == E_FM_OP_FILE_ADD)    /*file add*/
                          {
                             _e_fm2_live_file_add
                               (obj, ecore_file_file_get(path),
                               NULL, 0, &finf);
                             break;
                          }
                        if (e->minor == E_FM_OP_FILE_CHANGE)    /*file change*/
                          {
                             _e_fm2_live_file_changed
                               (obj, (char *)ecore_file_file_get(path),
                               &finf);
                          }
                        break;
                     }
                   /*file add - listing*/
                   if (e->minor == E_FM_OP_FILE_ADD)    /*file add*/
                     {
                        if (!sd->scan_timer)
                          {
                             sd->scan_timer =
                               ecore_timer_loop_add(0.5,
                                               _e_fm2_cb_scan_timer,
                                               sd->obj);
                             sd->busy_count++;
                             if (sd->busy_count == 1)
                               edje_object_signal_emit(sd->overlay, "e,state,busy,start", "e");
                          }
                        else
                          {
                             if ((eina_list_count(sd->icons) > 50) && (ecore_timer_interval_get(sd->scan_timer) < 1.5))
                               {
                                  /* increase timer interval when loading large directories to
                                   * dramatically improve load times
                                   */
                                  ecore_timer_interval_set(sd->scan_timer, 1.5);
                                  ecore_timer_loop_reset(sd->scan_timer);
                               }
                          }
                        if (path[0] != 0)
                          {
                             file = ecore_file_file_get(path);
                             if ((!strcmp(file, ".order")))
                               sd->order_file = EINA_TRUE;
                             else
                               {
                                  unsigned int n;

                                  n = eina_list_count(sd->queue) + eina_list_count(sd->icons);
                                  if (!((file[0] == '.') &&
                                        (!sd->show_hidden_files)))
                                    {
                                       char buf[1024];

                                       _e_fm2_file_add(obj, file,
                                                       sd->order_file,
                                                       NULL, 0, &finf);
                                       if (n - sd->overlay_count > 150)
                                         {
                                            sd->overlay_count = n + 1;
                                            snprintf(buf, sizeof(buf), P_("%u file", "%u files", sd->overlay_count), sd->overlay_count);
                                            edje_object_part_text_set(sd->overlay, "e.text.busy_label", buf);
                                         }
                                    }
                               }
                          }
                        if (e->response == 2)    /* end of scan */
                          {
                             sd->listing = EINA_FALSE;
                             if (sd->scan_timer)
                               {
                                  ecore_timer_interval_set(sd->scan_timer, 0.0001);
                                  ecore_timer_loop_reset(sd->scan_timer);
                               }
                             else
                               {
                                  _e_fm2_client_monitor_list_end(obj);
                               }
                          }
                     }
                   break;
                }
              free(evdir);
//                       printf(" ...\n");
              if ((sd->id != e->ref_to) || (path[0] != 0)) break;
//                            printf(" end response = %i\n", e->response);
              if (e->response == 2)     /* end of scan */
                {
                   sd->listing = EINA_FALSE;
                   if (sd->scan_timer)
                     {
                        ecore_timer_del(sd->scan_timer);
                        sd->scan_timer =
                          ecore_timer_loop_add(0.0001,
                                          _e_fm2_cb_scan_timer,
                                          sd->obj);
                     }
                   else
                     {
                        _e_fm2_client_monitor_list_end(obj);
                     }
                }
           }
           break;

           case E_FM_OP_FILE_DEL: /*file del*/
//             printf("E_FM_OP_FILE_DEL\n");
             path = e->data;
             evdir = ecore_file_dir_get(path);
             if ((dir) && (evdir) && (sd->id == e->ref_to) && (!strcmp(dir, evdir)))
               {
                  _e_fm2_live_file_del
                    (obj, ecore_file_file_get(path));
               }
             free(evdir);
             break;

           case E_FM_OP_MONITOR_END: /*mon dir del*/
//             printf("E_FM_OP_MONITOR_END\n");
             path = e->data;
             /* FIXME dir should not be null but can. fix segv
                when mounting cdrom with udisk here
                btw monitor end seems to be a strange event for
                mounting disks.
              */
             if ((dir) && (path) && (sd->id == e->ref_to) && (!strcmp(dir, path)))
               {
                  dels = eina_list_append(dels, obj);
               }
             break;

           default:
             break;
          }
     }
   EINA_LIST_FREE(dels, obj)
     {
        E_Fm2_Smart_Data *sd;

        sd = evas_object_smart_data_get(obj);
        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, obj))) continue;
        if (sd->config->view.open_dirs_in_place)
          _e_fm2_path_parent_set(obj, sd->realpath);
        else
          evas_object_smart_callback_call(obj, "dir_deleted", NULL);
     }
   _e_fm2_list_walking--;
   if (_e_fm2_list_walking == 0)
     {
        EINA_LIST_FREE(_e_fm2_list_remove, obj)
          {
             _e_fm2_list = eina_list_remove(_e_fm2_list, obj);
          }
     }
   switch (e->minor)
     {
      case E_FM_OP_INIT:
        e_config->device_detect_mode = strtoul((char*)e->data, NULL, 10);
        break;
      case E_FM_OP_MONITOR_SYNC:  /*mon list sync*/
        ecore_ipc_client_send(cl->cl, E_IPC_DOMAIN_FM, E_FM_OP_MONITOR_SYNC,
                              0, 0, e->response,
                              NULL, 0);
        break;

      case E_FM_OP_STORAGE_ADD:  /*storage add*/
        if ((e->data) && (e->size > 0))
          {
             E_Storage *s;

             s = _e_fm_shared_codec_storage_decode(e->data, e->size);
             if (s) e_fm2_device_storage_add(s);
          }
        break;

      case E_FM_OP_STORAGE_DEL:  /*storage del*/
        if ((e->data) && (e->size > 0))
          {
             char *udi;
             E_Storage *s;

             udi = e->data;
             s = e_fm2_device_storage_find(udi);
             if (s) e_fm2_device_storage_del(s);
          }
        break;

      case E_FM_OP_VOLUME_LIST_DONE:
        e_fm2_device_check_desktop_icons();
        break;

      case E_FM_OP_VOLUME_ADD:  /*volume add*/
        if ((e->data) && (e->size > 0))
          {
             E_Volume *v;

             v = _e_fm_shared_codec_volume_decode(e->data, e->size);
             if (!v) break;
             e_config->device_detect_mode = v->efm_mode;
             e_fm2_device_volume_add(v);
             if (v->mounted)
               e_fm2_device_mount(v, NULL, NULL, NULL, NULL, NULL);
             else if ((!starting) && e_config->device_auto_mount && v->first_time)
               {
                  v->auto_unmount = !v->mounted;
                  _e_fm2_client_mount(v->udi, v->mount_point);
               }
             if (e_config->device_desktop)
               e_fm2_device_show_desktop_icons();
             else
               e_fm2_device_hide_desktop_icons();
             v->first_time = 0;
          }
        break;

      case E_FM_OP_VOLUME_DEL:  /*volume del*/
        if ((e->data) && (e->size > 0))
          {
             char *udi;
             E_Volume *v;

             udi = e->data;
             v = e_fm2_device_volume_find(udi);
             if (v) e_fm2_device_volume_del(v);
          }
        break;

      case E_FM_OP_MOUNT_DONE:  /*mount done*/
        if ((e->data) && (e->size > 1))
          {
             E_Volume *v;
             char *udi, *mountpoint = NULL;

             udi = e->data;
             if ((unsigned int)e->size != (strlen(udi) + 1))
               mountpoint = udi + strlen(udi) + 1;
             v = e_fm2_device_volume_find(udi);
             if (v)
               {
                  e_fm2_device_mount_add(v, mountpoint);
                  _e_fm2_volume_icon_update(v);
                  if (e_config->device_auto_open && !eina_list_count(v->mounts))
                    {
                       E_Action *a;
                       unsigned int count;

                       a = e_action_find("fileman");
                       count = eina_list_count(_e_fm2_list);
                       if (a && a->func.go && mountpoint)
                         {
                            Evas_Object *fm;
                            Eina_List *m;
                            Eina_Bool auto_unmount = v->auto_unmount;

                            a->func.go(E_OBJECT(e_comp), mountpoint);
                            if (count == eina_list_count(_e_fm2_list)) break;
                            EINA_LIST_REVERSE_FOREACH(_e_fm2_list, m, fm)
                              {
                                 E_Fm2_Smart_Data *sd = evas_object_smart_data_get(fm);
                                 if (e_util_strcmp(sd->realpath, mountpoint)) continue;
                                 if (sd->mount) break;
                                 sd->mount = e_fm2_device_mount(v, _e_fm2_cb_mount_ok,
                                                                _e_fm2_cb_mount_fail,
                                                                _e_fm2_cb_unmount_ok, NULL, fm);
                                 v->auto_unmount = auto_unmount;
                                 break;
                              }
                         }
                    }
               }
          }
        break;

      case E_FM_OP_UNMOUNT_DONE:  /*unmount done*/
        if ((e->data) && (e->size > 1))
          {
             E_Volume *v;
             char *udi;

             udi = e->data;
             v = e_fm2_device_volume_find(udi);
             if (v)
               {
                  e_fm2_device_mount_del(v);
                  _e_fm2_volume_icon_update(v);
               }
          }
        break;

      case E_FM_OP_EJECT_DONE:
        break;

      case E_FM_OP_MOUNT_ERROR:  /*mount error*/
        if (e->data && (e->size > 1))
          {
             E_Volume *v;
             char *udi;

             udi = e->data;
             v = e_fm2_device_volume_find(udi);
             if (v)
               {
                  _e_fm_device_error_dialog(_("Mount Error"), _("Can't mount device"), e->data);
                  e_fm2_device_mount_fail(v);
               }
          }
        break;

      case E_FM_OP_UNMOUNT_ERROR:  /*unmount error*/
        if (e->data && (e->size > 1))
          {
             E_Volume *v;
             char *udi;

             udi = e->data;
             v = e_fm2_device_volume_find(udi);
             if (v)
               {
                  _e_fm_device_error_dialog(_("Unmount Error"), _("Can't unmount device"), e->data);
                  e_fm2_device_unmount_fail(v);
               }
          }
        break;

      case E_FM_OP_EJECT_ERROR:
        if (e->data && (e->size > 1))
          {
             E_Volume *v;
             char *udi;

             udi = e->data;
             v = e_fm2_device_volume_find(udi);
             if (v)
               _e_fm_device_error_dialog(_("Eject Error"), _("Can't eject device"), e->data);
          }
        break;

      case E_FM_OP_ERROR:  /*error*/
      {
         E_Dialog *dlg;
         ERR("Error from slave #%d: %s", e->ref, (char *)e->data);
         dlg = _e_fm_error_dialog(e->ref, e->data);
         _e_fm2_op_registry_error(e->ref, dlg);
      }
      break;

      case E_FM_OP_ERROR_RETRY_ABORT:  /*error*/
      {
         E_Dialog *dlg;
         ERR("Error from slave #%d: %s", e->ref, (char *)e->data);
         dlg = _e_fm_retry_abort_dialog(e->ref, (char *)e->data);
         _e_fm2_op_registry_error(e->ref, dlg);
      }
      break;

      case E_FM_OP_OVERWRITE:  /*overwrite*/
      {
         E_Dialog *dlg;
         ERR("Overwrite from slave #%d: %s", e->ref, (char *)e->data);
         dlg = _e_fm_overwrite_dialog(e->ref, (char *)e->data);
         _e_fm2_op_registry_needs_attention(e->ref, dlg);
      }
      break;

      case E_FM_OP_PROGRESS:  /*progress*/
      {
         int percent, seconds;
         off_t done, total;
         char *src = NULL;
         char *dst = NULL;
         char *p = e->data;
         E_Fm2_Op_Registry_Entry *ere;

         if (!e->data) return;

#define UP(value, type) (value) = *(type *)(void *)p; p += sizeof(type)
         UP(percent, int);
         UP(seconds, int);
         UP(done, off_t);
         UP(total, off_t);
#undef UP
         src = p;
         dst = p + strlen(src) + 1;
         // printf("%s:%s(%d) Progress from slave #%d:\n\t%d%% done,\n\t%d seconds left,\n\t%zd done,\n\t%zd total,\n\tsrc = %s,\n\tdst = %s.\n", __FILE__, __FUNCTION__, __LINE__, e->ref, percent, seconds, done, total, src, dst);

         ere = e_fm2_op_registry_entry_get(e->ref);
         if (!ere) return;
         ere->percent = percent;
         ere->done = done;
         ere->total = total;
         ere->eta = seconds;
         e_fm2_op_registry_entry_files_set(ere, src, dst);
         if (ere->percent == 100)
           {
              ere->status = E_FM2_OP_STATUS_SUCCESSFUL;
              ere->finished = 1;
           }
         e_fm2_op_registry_entry_changed(ere);
      }
      break;

      case E_FM_OP_QUIT:  /*finished*/
      {
         E_Fm2_Op_Registry_Entry *ere = e_fm2_op_registry_entry_get(e->ref);
         if (ere)
           {
              ere->finished = 1;
              ere->eta = 0;
              e_fm2_op_registry_entry_changed(ere);
           }
         e_fm2_op_registry_entry_del(e->ref);
      }
      break;

      default:
        break;
     }
}

E_API void
e_fm2_client_del(Ecore_Ipc_Event_Client_Del *e)
{
   Eina_List *l;
   E_Fm2_Client *cl;

   EINA_LIST_FOREACH(_e_fm2_client_list, l, cl)
     {
        if (cl->cl == e->client)
          {
             _e_fm2_client_list = eina_list_remove_list(_e_fm2_client_list, l);
             free(cl);
             break;
          }
     }
}

/* local subsystem functions */
static const char *
_e_fm2_dev_path_map(E_Fm2_Smart_Data *sd, const char *dev, const char *path)
{
   char buf[PATH_MAX] = "", *s, *ss;
   int len;

   /* map a device name to a mount point/path on the os (and append path) */

   /* FIXME: load mappings from config and use them first - maybe device
    * discovery should be done through config and not the below (except
    * maybe for home directory and root fs and other simple thngs */
   /* FIXME: also add other virtualized dirs like "backgrounds", "themes",
    * "favorites" */
#define CMP(x)        ((dev) && (e_util_glob_case_match(dev, x)))
#define PRT(args ...) snprintf(buf, sizeof(buf), ##args)

   if (dev)
     {
        if (dev[0] == '/')
          {
             if (dev[1] == '\0')
               {
                  if (eina_strlcpy(buf, path, sizeof(buf)) >=
                      (int)sizeof(buf))
                    return NULL;
               }
             else
               {
                  if (PRT("%s/%s", dev, path) >= (int)sizeof(buf))
                    return NULL;
               }
          }
        else if ((dev[0] == '~') && (dev[1] == '/') && (dev[2] == '\0'))
          {
             s = (char *)e_user_homedir_get();
             if (PRT("%s/%s", s, path) >= (int)sizeof(buf))
               return NULL;
          }
        else if (strcmp(dev, "favorites") == 0)
          {
             /* this is a virtual device - it's where your favorites list is
              * stored - a dir with
                .desktop files or symlinks (in fact anything
              * you like).
              */
             if (e_user_dir_concat_static(buf, "fileman/favorites") >= sizeof(buf))
               return NULL;
             if (sd && sd->config) sd->config->view.no_typebuf_set = EINA_TRUE;
             ecore_file_mkpath(buf);
          }
        else if (strcmp(dev, "desktop") == 0)
          {
             /* this is a virtual device - it's where your favorites list is
              * stored - a dir with
                .desktop files or symlinks (in fact anything
              * you like).
              */
             if ((!path) || (!path[0]) || (!strcmp(path, "/")))
               {
                  snprintf(buf, sizeof(buf), "%s", efreet_desktop_dir_get());
                  ecore_file_mkpath(buf);
               }
             else if (path[0] == '/')
               snprintf(buf, sizeof(buf), "%s/%s", efreet_desktop_dir_get(), path);
             else
               {
                  snprintf(buf, sizeof(buf), "%s-%s", efreet_desktop_dir_get(), path);
                  ecore_file_mkpath(buf);
               }
          }
        else if (strcmp(dev, "temp") == 0)
          PRT("/tmp");
        /* FIXME: replace all this removable, dvd and like with hal */
        else if (!strncmp(dev, "removable:", sizeof("removable:") - 1))
          {
             E_Volume *v;

             v = e_fm2_device_volume_find(dev + strlen("removable:"));
             if (v)
               {
                  if ((!v->mount_point) && (v->efm_mode == EFM_MODE_USING_HAL_MOUNT))
                    v->mount_point = e_fm2_device_volume_mountpoint_get(v);
                  else if (!v->mount_point)
                    return NULL;

                  if (PRT("%s/%s", v->mount_point, path) >= (int)sizeof(buf))
                    return NULL;
               }
          }
/*  else if (CMP("dvd") || CMP("dvd-*")) */
/*    { */
/*       /\* FIXME: find dvd mountpoint optionally for dvd no. X *\/ */
/*       /\* maybe make part of the device mappings config? *\/ */
/*    } */
/*  else if (CMP("cd") || CMP("cd-*") || CMP("cdrom") || CMP("cdrom-*") || */
/*     CMP("dvd") || CMP("dvd-*")) */
/*    { */
/*       /\* FIXME: find cdrom or dvd mountpoint optionally for cd/dvd no. X *\/ */
/*       /\* maybe make part of the device mappings config? *\/ */
/*    } */
     }
   /* FIXME: add code to find USB devices (multi-card readers or single,
    * usb thumb drives, other usb storage devices (usb hd's etc.)
    */
   /* maybe make part of the device mappings config? */
   /* FIXME: add code for finding nfs shares, smb shares etc. */
   /* maybe make part of the device mappings config? */

   if (buf[0] == '\0')
     {
        if (eina_strlcpy(buf, path, sizeof(buf)) >= sizeof(buf))
          return NULL;
     }

   /* strip out excess multiple slashes */
   s = buf;
   while (*s)
     {
        while ((s[0] == '/') && (s[1] == '/'))
          {
             ss = s;
             do
               {
                  ss[0] = ss[1];
                  ss++;
               }
             while (*ss);
          }
        s++;
     }
   /* strip out slashes at the end - unless its just "/" */
   len = s - buf;
   while ((len > 1) && (buf[len - 1] == '/'))
     {
        buf[len - 1] = 0;
        len--;
     }
   return eina_stringshare_add(buf);
}

static void
_e_fm2_file_add(Evas_Object *obj, const char *file, int unique, Eina_Stringshare *file_rel, int after, E_Fm2_Finfo *finf)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic, *ic2;
   Eina_List *l;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* if we only want unique icon names - if it's there - ignore */
   if (unique)
     {
        EINA_LIST_FOREACH(sd->icons, l, ic)
          {
             if (!strcmp(ic->info.file, file))
               {
                  sd->tmp.last_insert = NULL;
                  return;
               }
          }
        EINA_LIST_FOREACH(sd->queue, l, ic)
          {
             if (!strcmp(ic->info.file, file))
               {
                  sd->tmp.last_insert = NULL;
                  return;
               }
          }
     }
   /* create icon obj and append to unsorted list */
   ic = _e_fm2_icon_new(sd, file, finf);
   if (ic)
     {
        if (!file_rel)
          {
             if (ic->queued) abort();
             if (ic->inserted) abort();
             /* respekt da ordah! */
             if (sd->order_file)
               sd->queue = eina_list_append(sd->queue, ic);
             else
               {
                  /* insertion sort it here to spread the sort load into idle time */
                  sd->queue = eina_list_sorted_insert(sd->queue, _e_fm2_cb_icon_sort, ic);
               }
             ic->queued = EINA_TRUE;
          }
        else
          {
             if (ic->queued) abort();
             if (ic->inserted) abort();
             EINA_LIST_FOREACH(sd->icons, l, ic2)
               {
                  if (ic2->info.file == file_rel)
                    {
                       if (after)
                         sd->icons = eina_list_append_relative(sd->icons, ic, ic2);
                       else
                         sd->icons = eina_list_prepend_relative(sd->icons, ic, ic2);
                       ic->inserted = EINA_TRUE;
                       break;
                    }
               }
             if (ic->inserted)
               {
                  sd->icons = eina_list_append(sd->icons, ic);
                  ic->inserted = EINA_TRUE;
               }
             sd->icons_place = eina_list_append(sd->icons_place, ic);
          }
        sd->tmp.last_insert = NULL;
        sd->iconlist_changed = EINA_TRUE;
     }
}

static void
_e_fm2_file_del(Evas_Object *obj, const char *file)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;
   Eina_List *l;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!strcmp(ic->info.file, file))
          {
             if (!ic->inserted) abort();
             sd->icons = eina_list_remove_list(sd->icons, l);
             ic->inserted = EINA_FALSE;
             sd->icons_place = eina_list_remove(sd->icons_place, ic);
             if (ic->region)
               {
                  ic->region->list = eina_list_remove(ic->region->list, ic);
                  ic->region = NULL;
               }
             _e_fm2_icon_free(ic);
             return;
          }
     }
   EINA_LIST_FOREACH(sd->queue, l, ic)
     {
        if (!strcmp(ic->info.file, file))
          {
             INF("MATCH!");
             sd->queue = eina_list_remove_list(sd->queue, l);
             ic->queued = EINA_FALSE;
             _e_fm2_icon_free(ic);
             return;
          }
     }
}

static void
_e_fm_file_buffer_clear(void)
{
   const char *s;
   EINA_LIST_FREE(_e_fm_file_buffer, s)
     eina_stringshare_del(s);

   _e_fm_file_buffer_copying = 0;
}

static Eina_Bool
_e_fm2_buffer_fill(Evas_Object *obj)
{
   Eina_List *sel;
   char buf[PATH_MAX], *pfile;
   int bufused, buffree;
   const char *real_path;
   const E_Fm2_Icon_Info *ici;

   sel = e_fm2_selected_list_get(obj);
   if (!sel) return EINA_FALSE;

   real_path = e_fm2_real_path_get(obj);
   if (!real_path) return EINA_FALSE;

   bufused = eina_strlcpy(buf, real_path, sizeof(buf));
   if (bufused >= (int)sizeof(buf) - 2) return EINA_FALSE;

   if ((bufused > 0) && (buf[bufused - 1] != '/'))
     {
        buf[bufused] = '/';
        bufused++;
     }

   pfile = buf + bufused;
   buffree = sizeof(buf) - bufused;

   EINA_LIST_FREE(sel, ici)
     {
        if (!ici) continue;
        if ((int)eina_strlcpy(pfile, ici->file, buffree) >= buffree) continue;
        _e_fm_file_buffer = eina_list_append(_e_fm_file_buffer, _e_fm2_uri_escape(buf));
     }

   return EINA_TRUE;
}

static void
_e_fm2_file_cut(Evas_Object *obj)
{
   _e_fm_file_buffer_clear();
   _e_fm2_buffer_fill(obj);
}

static void
_e_fm2_file_copy(Evas_Object *obj)
{
   _e_fm_file_buffer_clear();
   _e_fm_file_buffer_copying = _e_fm2_buffer_fill(obj);
}

static void
_e_fm2_file_paste(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *paths;
   const char *filepath;
   size_t length = 0;
   size_t size = 0;
   char *args = NULL;
   Eina_Bool memerr = EINA_FALSE;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   /* Convert URI list to a list of real paths. */
   paths = e_fm2_uri_path_list_get(_e_fm_file_buffer);
   if (!paths) return;
   EINA_LIST_FREE(paths, filepath)
     {
        /* Get file's full path. */
        if (!filepath) continue;
        /* Check if file is protected. */
        if (e_filereg_file_protected(filepath))
          {
             eina_stringshare_del(filepath);
             continue;
          }
        /* Put filepath into a string of args.
         * If there are more files, put an additional space.
         */
        if (!memerr)
          {
             args = e_util_string_append_quoted(args, &size, &length, filepath);
             if (!args) memerr = EINA_TRUE;
             else
               {
                  args = e_util_string_append_char(args, &size, &length, ' ');
                  if (!args) memerr = EINA_TRUE;
               }
          }
        eina_stringshare_del(filepath);
     }
   if (memerr) return;

   /* Add destination to the arguments. */
   {
      E_Fm2_Icon *ic = NULL;

      if (eina_list_count(sd->selected_icons) == 1)
        {
           ic = eina_list_data_get(sd->selected_icons);
           if (ic->info.link || (!S_ISDIR(ic->info.statinfo.st_mode))) ic = NULL;
        }
      if (ic)
        {
           args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
           if (!args) return;
           args = e_util_string_append_char(args, &size, &length, '/');
           if (!args) return;
           args = e_util_string_append_quoted(args, &size, &length, ic->info.file);
           if (!args) return;
        }
      else
        {
           args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
           if (!args) return;
        }
   }

   /* Roll the operation! */
   if (_e_fm_file_buffer_copying)
     {
        if (sd->config->view.link_drop)
          e_fm2_client_file_symlink(sd->obj, args);
        else
          e_fm2_client_file_copy(sd->obj, args);
     }
   else
     {
        if (sd->config->view.link_drop)
          e_fm2_client_file_symlink(sd->obj, args);
        else
          e_fm2_client_file_move(sd->obj, args);
        _e_fm_file_buffer_clear();
     }

   free(args);
}

static void
_e_fm2_file_symlink(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *paths;
   const char *filepath;
   size_t length = 0;
   size_t size = 0;
   char *args = NULL;
   Eina_Bool memerr = EINA_FALSE;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   /* Convert URI list to a list of real paths. */
   paths = e_fm2_uri_path_list_get(_e_fm_file_buffer);
   EINA_LIST_FREE(paths, filepath)
     {
        /* Get file's full path. */
        if (!filepath)
          continue;

        /* Check if file is protected. */
        if (e_filereg_file_protected(filepath))
          {
             eina_stringshare_del(filepath);
             continue;
          }

        /* Put filepath into a string of args.
         * If there are more files, put an additional space.
         */
        if (!memerr)
          {
             args = e_util_string_append_quoted(args, &size, &length, filepath);
             if (!args) memerr = EINA_TRUE;
             else
               {
                  args = e_util_string_append_char(args, &size, &length, ' ');
                  if (!args) memerr = EINA_TRUE;
               }
          }

        eina_stringshare_del(filepath);
     }
   if (memerr) return;

   /* Add destination to the arguments. */
   args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
   if (!args) return;

   /* Roll the operation! */
   if (_e_fm_file_buffer_copying) e_fm2_client_file_symlink(sd->obj, args);

   free(args);
}

static void
_e_fm2_file_cut_menu(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   if (!sd) return;
   _e_fm2_file_cut(sd->obj);
}

static void
_e_fm2_file_copy_menu(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   if (!sd) return;
   _e_fm2_file_copy(sd->obj);
}

static void
_e_fm2_file_paste_menu(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   if (!sd) return;
   _e_fm2_file_paste(sd->obj);
}

static void
_e_fm2_file_symlink_menu(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   if (!sd) return;
   _e_fm2_file_symlink(sd->obj);
}

static void
_e_fm2_queue_process(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic, *ic2;
   Eina_List *l, **ll;
   int added = 0, i, p0, p1, n, v;
   double t;
   char buf[4096];

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->queue)
     {
        if (sd->resize_job) ecore_job_del(sd->resize_job);
        sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, obj);
        evas_object_smart_callback_call(sd->obj, "changed", NULL);
        sd->tmp.last_insert = NULL;
        return;
     }
//   double tt = ecore_time_get();
//   int queued = eina_list_count(sd->queue);
/* take unsorted and insert into the icon list - reprocess regions */
   t = ecore_time_get();
   if (!sd->tmp.last_insert)
     {
#if 1
        n = eina_list_count(sd->icons);
        E_FREE(sd->tmp.list_index);
        if (n > 0)
          sd->tmp.list_index = calloc(n, sizeof(Eina_List *));
        if (sd->tmp.list_index)
          {
             ll = sd->tmp.list_index;
             for (l = sd->icons; l; l = eina_list_next(l))
               {
                  *ll = l;
                  ll++;
               }
             /* binary search first queue */
             ic = eina_list_data_get(sd->queue);
             p0 = 0; p1 = n;
             i = (p0 + p1) / 2;
             ll = sd->tmp.list_index;
             if (ll[i])
               do /* avoid garbage deref */
                 {
                    ic2 = eina_list_data_get(ll[i]);
                    v = _e_fm2_cb_icon_sort(ic, ic2);
                    if (v < 0) /* ic should go before ic2 */
                      p1 = i;
                    else /* ic should go at or after ic2 */
                      p0 = i;
                    i = (p0 + p1) / 2;
                    l = ll[i];
                 }
               while ((p1 - p0) > 1);
          }
        else
#endif
        l = sd->icons;
     }
   else
     l = sd->tmp.last_insert;
   while (sd->queue)
     {
        ic = sd->queue->data;
        sd->queue = eina_list_remove_list(sd->queue, sd->queue);
        /* insertion sort - better than qsort for the way we are doing
         * things - incrimentally scan and sort as we go as we now know
         * that the queue files are in order, we speed up insertions to
         * a worst case of O(n) where n is the # of files in the list
         * so far
         */
        if (sd->order_file)
          {
             l = NULL;
          }
        else
          {
             EINA_LIST_FOREACH(l, l, ic2)
               {
                  if (_e_fm2_cb_icon_sort(ic, ic2) < 0)
                    {
                       if (!ic->queued) abort();
                       if (ic->inserted) abort();
                       ic->queued = EINA_FALSE;
                       ic->inserted = EINA_TRUE;
                       if (l == sd->icons)
                         sd->icons = eina_list_prepend(sd->icons, ic);
                       else
                         sd->icons = eina_list_prepend_relative_list(sd->icons,
                                                                     ic, l);
                       sd->tmp.last_insert = l;
                       break;
                    }
               }
          }
        if (!l)
          {
             if (!ic->queued) abort();
             if (ic->inserted) abort();
             ic->queued = EINA_FALSE;
             ic->inserted = EINA_TRUE;
             sd->icons = eina_list_append(sd->icons, ic);
             sd->tmp.last_insert = eina_list_last(sd->icons);
          }
        sd->icons_place = eina_list_append(sd->icons_place, ic);
        added++;
        /* if we spent more than 1/20th of a second inserting - give up
         * for now */
        if ((_e_fm2_toomany_get(sd)) && (!sd->toomany))
          {
             sd->toomany = EINA_TRUE;
             break;
          }
        if ((ecore_time_get() - t) > 0.01) break;
     }
//   printf("FM: SORT %1.3f (%i files) (%i queued, %i added) [%i iter]\n",
//	  ecore_time_get() - tt, eina_list_count(sd->icons), queued,
//	  added, sd->tmp.iter);
   sd->overlay_count = eina_list_count(sd->icons);
   snprintf(buf, sizeof(buf), P_("%u file", "%u files", sd->overlay_count), sd->overlay_count);
   edje_object_part_text_set(sd->overlay, "e.text.busy_label", buf);
   if (sd->resize_job) ecore_job_del(sd->resize_job);
   // this will handle a relayout if we have too many icons
   sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, obj);
   evas_object_smart_callback_call(sd->obj, "changed", NULL);
   sd->tmp.iter++;
}

static void
_e_fm2_queue_free(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* just free the icons in the queue  and the queue itself */
   EINA_LIST_FREE(sd->queue, ic)
     {
        if (!ic->queued) abort();
        if (ic->inserted) abort();
        ic->queued = EINA_FALSE;
        _e_fm2_icon_free(ic);
     }
}

static void
_e_fm2_regions_free(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Region *rg;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* free up all regions */
   EINA_LIST_FREE(sd->regions.list, rg)
     _e_fm2_region_free(rg);
}

static void
_e_fm2_regions_populate(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Region *rg;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* take the icon list and split into regions */
   rg = NULL;
   evas_event_freeze(evas_object_evas_get(obj));
   edje_freeze();
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!rg)
          {
             rg = _e_fm2_region_new(sd);
             sd->regions.list = eina_list_append(sd->regions.list, rg);
          }
        ic->region = rg;
        rg->list = eina_list_append(rg->list, ic);
        if (rg->w == 0)
          {
             rg->x = ic->x;
             rg->y = ic->y;
             rg->w = ic->w;
             rg->h = ic->h;
          }
        else
          {
             if (ic->x < rg->x)
               {
                  rg->w += rg->x - ic->x;
                  rg->x = ic->x;
               }
             if ((ic->x + ic->w) > (rg->x + rg->w))
               {
                  rg->w += (ic->x + ic->w) - (rg->x + rg->w);
               }
             if (ic->y < rg->y)
               {
                  rg->h += rg->y - ic->y;
                  rg->y = ic->y;
               }
             if ((ic->y + ic->h) > (rg->y + rg->h))
               {
                  rg->h += (ic->y + ic->h) - (rg->y + rg->h);
               }
          }
        if ((int)eina_list_count(rg->list) > sd->regions.member_max)
          rg = NULL;
     }
   _e_fm2_regions_eval(obj);
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if ((!ic->region->realized) && (ic->realized))
          _e_fm2_icon_unrealize(ic);
     }
   _e_fm2_obj_icons_place(sd);
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(obj));
}

static void
_e_fm2_icons_place_icons(E_Fm2_Smart_Data *sd)
{
   Eina_List *l;
   E_Fm2_Icon *ic;
   Evas_Coord x, y, rh;

   x = 0; y = 0;
   rh = 0;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if ((x > 0) && ((x + ic->w) > sd->w))
          {
             x = 0;
             y += rh;
             rh = 0;
          }
        ic->x = x;
        ic->y = y;
        x += ic->w;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
        if (ic->h > rh) rh = ic->h;
        if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
     }
}

static void
_e_fm2_icons_place_grid_icons(E_Fm2_Smart_Data *sd)
{
   Eina_List *l;
   E_Fm2_Icon *ic;
   Evas_Coord x, y, gw, gh;
   int cols = 1, col;

   gw = 0; gh = 0;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (ic->w > gw) gw = ic->w;
        if (ic->h > gh) gh = ic->h;
     }
   if (gw > 0) cols = sd->w / gw;
   if (cols < 1) cols = 1;
   x = 0; y = 0; col = 0;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        ic->x = x + ((gw - ic->w) / 2);
        ic->y = y + (gh - ic->h);
        x += gw;
        col++;
        if (col >= cols)
          {
             col = 0;
             x = 0;
             y += gh;
          }
        if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
     }
}

static int
_e_fm2_icons_icon_overlaps(E_Fm2_Icon *ic)
{
   Eina_List *l;
   E_Fm2_Icon *ic2;

   /* this is really slow... when we have a lot of icons */
   EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
     {
        if ((ic2 != ic) && (ic2->saved_pos))
          {
             if (E_INTERSECTS(ic2->x, ic2->y, ic2->w, ic2->h,
                              ic->x, ic->y, ic->w, ic->h))
               return 1;
          }
     }
   return 0;
}

static int
_e_fm2_icons_icon_row_ok(E_Fm2_Icon *ic)
{
   if (ic->x + ic->w > ic->sd->w) return 0;
   if (ic->x < 0) return 0;
   if (ic->y < 0) return 0;
   return 1;
}

static void
_e_fm2_icon_place_relative(E_Fm2_Icon *ic, E_Fm2_Icon *icr, int xrel, int yrel, int xa, int ya)
{
   ic->x = icr->x;
   ic->y = icr->y;

   if (xrel > 0) ic->x += icr->w;
   else if (xrel < 0)
     ic->x -= ic->w;
   else if (xa == 1)
     ic->x += (icr->w - ic->w) / 2;
   else if (xa == 2)
     ic->x += icr->w - ic->w;

   if (yrel > 0) ic->y += icr->h;
   else if (yrel < 0)
     ic->y -= ic->h;
   else if (ya == 1)
     ic->y += (icr->h - ic->h) / 2;
   else if (ya == 2)
     ic->y += icr->h - ic->h;
}

static void
_e_fm2_icons_place_icon(E_Fm2_Icon *ic)
{
   Eina_List *l;
   E_Fm2_Icon *ic2;

   ic->x = 0;
   ic->y = 0;
   ic->saved_pos = EINA_TRUE;
   /* ### BLAH ### */
//   if (!_e_fm2_icons_icon_overlaps(ic)) return;
/*
   _e_fm2_icon_place_relative(ic, ic2, 1, 0, 0, 2);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 0, -1, 0, 0);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 0, -1, 1, 0);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 1, 0, 0, 0);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 1, 0, 0, 1);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 0, 1, 0, 0);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
   _e_fm2_icon_place_relative(ic, ic2, 0, 1, 1, 0);
   if (_e_fm2_icons_icon_row_ok(ic) && !_e_fm2_icons_icon_overlaps(ic)) return;
 */
   if ((ic->sd->last_placed) && (ic->sd->toomany))
     {
        ic2 = ic->sd->last_placed;
        // ###_
        _e_fm2_icon_place_relative(ic, ic2, 1, 0, 0, 2);
        if (_e_fm2_icons_icon_row_ok(ic) &&
            !_e_fm2_icons_icon_overlaps(ic)) goto done;
        // _###
        _e_fm2_icon_place_relative(ic, ic2, -1, 0, 0, 2);
        if (_e_fm2_icons_icon_row_ok(ic) &&
            !_e_fm2_icons_icon_overlaps(ic)) goto done;
        // ###
        //  |
        _e_fm2_icon_place_relative(ic, ic2, 0, 1, 1, 0);
        if (_e_fm2_icons_icon_row_ok(ic) &&
            !_e_fm2_icons_icon_overlaps(ic)) goto done;
        //  |
        // ###
        _e_fm2_icon_place_relative(ic, ic2, 0, -1, 1, 0);
        if (_e_fm2_icons_icon_row_ok(ic) &&
            !_e_fm2_icons_icon_overlaps(ic)) goto done;
        // do this anyway - dont care.
        // ###
        //  |
        _e_fm2_icon_place_relative(ic, ic2, 0, 1, 1, 0);
        ic->x = 0;
        goto done;
     }
   EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
     {
        if ((ic2 != ic) && (ic2->saved_pos))
          {
             // ###_
             _e_fm2_icon_place_relative(ic, ic2, 1, 0, 0, 2);
             if (_e_fm2_icons_icon_row_ok(ic) &&
                 !_e_fm2_icons_icon_overlaps(ic)) goto done;
             // _###
             _e_fm2_icon_place_relative(ic, ic2, -1, 0, 0, 2);
             if (_e_fm2_icons_icon_row_ok(ic) &&
                 !_e_fm2_icons_icon_overlaps(ic)) goto done;
          }
     }

   EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
     {
        if ((ic2 != ic) && (ic2->saved_pos))
          {
             // ###
             //  |
             _e_fm2_icon_place_relative(ic, ic2, 0, 1, 1, 0);
             if (_e_fm2_icons_icon_row_ok(ic) &&
                 !_e_fm2_icons_icon_overlaps(ic)) goto done;
             //  |
             // ###
             _e_fm2_icon_place_relative(ic, ic2, 0, -1, 1, 0);
             if (_e_fm2_icons_icon_row_ok(ic) &&
                 !_e_fm2_icons_icon_overlaps(ic)) goto done;
          }
        if ((ic2 != ic) && (ic2->saved_pos))
          {
// TODO: if uncomment this, change EINA_LIST_FOREACH to EINA_LIST_FOREACH_SAFE!
//	     ic->sd->icons_place = eina_list_remove_list(ic->sd->icons_place, pl);
          }
     }
done:
   ic->sd->last_placed = ic;
   return;
}

static void
_e_fm2_icons_place_custom_icons(E_Fm2_Smart_Data *sd)
{
   Eina_List *l;
   E_Fm2_Icon *ic;

   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!ic->saved_pos)
          {
             /* FIXME: place using smart place fn */
             _e_fm2_icons_place_icon(ic);
          }

        if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
     }
}

static void
_e_fm2_icons_place_custom_grid_icons(E_Fm2_Smart_Data *sd)
{
   /* FIXME: not going to implement this at this stage */
   Eina_List *l;
   E_Fm2_Icon *ic;

   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!ic->saved_pos)
          {
             /* FIXME: place using grid fn */
          }

        if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
     }
}

static void
_e_fm2_icons_place_custom_smart_grid_icons(E_Fm2_Smart_Data *sd)
{
   /* FIXME: not going to implement this at this stage */
   Eina_List *l;
   E_Fm2_Icon *ic;

   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!ic->saved_pos)
          {
             /* FIXME: place using smart grid fn */
          }

        if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
     }
}

static void
_e_fm2_icons_place_list(E_Fm2_Smart_Data *sd)
{
   Eina_List *l;
   E_Fm2_Icon *ic;
   Evas_Coord x, y;
   int w, i;

   w = i = x = y = 0;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        ic->x = x;
        ic->y = y;
        sd->min.w = MAX(ic->min_w, sd->min.w);
        sd->min.h = MAX(ic->min_h, sd->min.h);
        if (sd->w > ic->min_w)
          ic->w = sd->w;
        else
          ic->w = ic->min_w;
        y += ic->h;
        ic->odd = (i & 0x01);
        if ((ic->w != sd->w) && ((ic->x + ic->w) > sd->max.w)) sd->max.w = ic->x + ic->w;
        else if (ic->min_w > sd->max.w)
          sd->max.w = ic->min_w;
        if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
        w = MAX(w, ic->min_w);
        w = MAX(w, sd->w);
        i++;
     }
   EINA_LIST_FOREACH(sd->icons, l, ic)
     ic->w = w;
}

static void
_e_fm2_icons_place(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* take the icon list and find a location for them */
   sd->max.w = 0;
   sd->max.h = 0;
   switch (_e_fm2_view_mode_get(sd))
     {
      case E_FM2_VIEW_MODE_ICONS:
        _e_fm2_icons_place_icons(sd);
//	sd->max.h += ICON_BOTTOM_SPACE;
        break;

      case E_FM2_VIEW_MODE_GRID_ICONS:
        _e_fm2_icons_place_grid_icons(sd);
//	sd->max.h += ICON_BOTTOM_SPACE;
        break;

      case E_FM2_VIEW_MODE_CUSTOM_ICONS:
        _e_fm2_icons_place_custom_icons(sd);
//	sd->max.h += ICON_BOTTOM_SPACE;
        break;

      case E_FM2_VIEW_MODE_CUSTOM_GRID_ICONS:
        /* FIXME: not going to implement this at this stage */
        _e_fm2_icons_place_custom_grid_icons(sd);
//	sd->max.h += ICON_BOTTOM_SPACE;
        break;

      case E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS:
        /* FIXME: not going to implement this at this stage */
        _e_fm2_icons_place_custom_smart_grid_icons(sd);
//	sd->max.h += ICON_BOTTOM_SPACE;
        break;

      case E_FM2_VIEW_MODE_LIST:
        _e_fm2_icons_place_list(sd);
        break;

      default:
        break;
     }
   /* tell our parent scrollview - if any, that we have changed */
   evas_object_smart_callback_call(sd->obj, "changed", NULL);
}

static void
_e_fm2_icons_free(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   _e_fm2_queue_free(obj);
   /* free all icons */
   EINA_LIST_FREE(sd->icons, ic)
     {
        if (ic->queued) abort();
        if (!ic->inserted) abort();
        ic->inserted = EINA_FALSE;
        _e_fm2_icon_free(ic);
     }
   sd->last_selected = NULL;
   sd->range_selected = NULL;
   eina_list_free(sd->icons_place);
   sd->icons_place = NULL;
   sd->tmp.last_insert = NULL;
   E_FREE(sd->tmp.list_index);
}

static void
_e_fm2_regions_eval(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Region *rg;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   EINA_LIST_FOREACH(sd->regions.list, l, rg)
     {
        if (_e_fm2_region_visible(rg))
          _e_fm2_region_realize(rg);
        else
          _e_fm2_region_unrealize(rg);
     }
}

static void
_e_fm2_config_free(E_Fm2_Config *cfg)
{
   if (cfg->icon.key_hint) eina_stringshare_del(cfg->icon.key_hint);
   if (cfg->theme.background) eina_stringshare_del(cfg->theme.background);
   if (cfg->theme.frame) eina_stringshare_del(cfg->theme.frame);
   if (cfg->theme.icons) eina_stringshare_del(cfg->theme.icons);
   free(cfg);
}

static Eina_List *
_e_fm2_file_fm2_find(const char *file)
{
   char *dir;
   Eina_List *l, *ret = NULL;
   Evas_Object *obj;

   dir = ecore_file_dir_get(file);
   if (!dir) return NULL;
   EINA_LIST_FOREACH(_e_fm2_list, l, obj)
     {
        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, obj))) continue;
        if (!strcmp(e_fm2_real_path_get(obj), dir))
          ret = eina_list_append(ret, obj);
     }
   free(dir);
   return ret;
}

static E_Fm2_Icon *
_e_fm2_icon_find(Evas_Object *obj, const char *file)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        if (!strcmp(ic->info.file, file)) return ic;
     }
   return NULL;
}

/* Escape illegal caracters within an uri and return an eina_stringshare */
static const char *
_e_fm2_uri_escape(const char *path)
{
   char dest[PATH_MAX * 3 + 7];
   const char *p;
   int i;

   if (!path) return NULL;
   memset(dest, 0, PATH_MAX * 3 + 7);

   snprintf(dest, 8, "file://");

   /* Most app doesn't handle the hostname in the uri so it's put to NULL */
   for (i = 7, p = 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);
}

/* Parse a single uri and return an E_Fm2_Uri struct.
 * If the parsing have failed it return NULL.
 * The E_Fm2_Uri may have hostname parameter and always a path.
 * If there's no hostname in the uri then the hostname parameter is NULL
 */
static E_Fm2_Uri *
_e_fm2_uri_parse(const char *val)
{
   E_Fm2_Uri *uri;
   const char *p;
   char hostname[PATH_MAX], path[PATH_MAX];
   int i = 0;

   /* if value is a raw path: /path/to/blah just return it */
   if (val[0] == '/')
     {
        uri = E_NEW(E_Fm2_Uri, 1);
        uri->hostname = NULL;
        uri->path = eina_stringshare_add(val);
        return uri;
     }
   /* The shortest possible path is file:///
    * anything smaller than that can't be a valid uri
    */
   if (strlen(val) <= 7 && strncmp(val, "file://", 7)) return NULL;
   memset(path, 0, PATH_MAX);

   /* An uri should be in a form file://<hostname>/<path> */
   p = val + 7;
   if (*p != '/')
     {
        for (i = 0; *p != '/' && *p != '\0' && i < _POSIX_HOST_NAME_MAX; p++, i++)
          hostname[i] = *p;
     }
   hostname[i] = '\0';

   /* See http://www.faqs.org/rfcs/rfc1738.html for the escaped chars */
   for (i = 0; (*p != '\0') && (i < (PATH_MAX - 1)); 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 = E_NEW(E_Fm2_Uri, 1);
   if (hostname[0]) uri->hostname = eina_stringshare_add(hostname);
   else uri->hostname = NULL;
   uri->path = eina_stringshare_add(path);

   return uri;
}

/* Takes an Eina_List of uri and return an Eina_List of real paths */
static Eina_List *
_e_fm2_uri_selected_icon_list_get(Eina_List *uri)
{
   Eina_List *icons = NULL;
   Eina_List *l;
   const char *path;

   EINA_LIST_FOREACH(uri, l, path)
     {
        Eina_List *fms;
        Evas_Object *fm;
        E_Fm2_Icon *ic;

        fms = _e_fm2_file_fm2_find(path);
        if (!fms) continue;
        ic = NULL;
        EINA_LIST_FREE(fms, fm)
          {
             ic = _e_fm2_icon_find(fm, ecore_file_file_get(path));
             if (!ic) continue;
             if (eina_list_data_find(ic->sd->selected_icons, ic)) break;
             ic = NULL;
          }
        if (ic) icons = eina_list_append(icons, ic);
     }
   return icons;
}

/**************************/

static E_Fm2_Icon *
_e_fm2_icon_new(E_Fm2_Smart_Data *sd, const char *file, E_Fm2_Finfo *finf)
{
   E_Fm2_Icon *ic;

   /* create icon */
   ic = E_NEW(E_Fm2_Icon, 1);
   ic->info.fm = sd->obj;
   ic->info.ic = ic;
   ic->info.file = eina_stringshare_add(file);
   ic->sd = sd;
   if (!_e_fm2_icon_fill(ic, finf))
     {
        eina_stringshare_del(ic->info.file);
        free(ic);
        return NULL;
     }
   return ic;
}

static void
_e_fm2_icon_unfill(E_Fm2_Icon *ic)
{
   eina_stringshare_del(ic->info.mime);
   eina_stringshare_del(ic->info.label);
   eina_stringshare_del(ic->info.comment);
   eina_stringshare_del(ic->info.generic);
   eina_stringshare_del(ic->info.icon);
   eina_stringshare_del(ic->info.link);
   eina_stringshare_del(ic->info.real_link);
   eina_stringshare_del(ic->info.category);
   ic->info.mime = NULL;
   ic->info.label = NULL;
   ic->info.comment = NULL;
   ic->info.generic = NULL;
   ic->info.icon = NULL;
   ic->info.link = NULL;
   ic->info.real_link = NULL;
   ic->info.mount = EINA_FALSE;
   ic->info.removable = EINA_FALSE;
   ic->info.removable_full = EINA_FALSE;
   ic->info.deleted = EINA_FALSE;
   ic->info.broken_link = EINA_FALSE;
}

static void
_e_fm2_icon_geom_adjust(E_Fm2_Icon *ic, int saved_x, int saved_y, int saved_w EINA_UNUSED, int saved_h EINA_UNUSED, int saved_res_w, int saved_res_h)
{
   int qx, qy, rx, ry, x, y;

   if (!((_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_CUSTOM_ICONS) &&
         (ic->sd->config->view.fit_custom_pos) &&
         (saved_res_w > 0) &&
         (saved_res_h > 0)))
     return;
   if (saved_res_w >= 3) qx = saved_x / (saved_res_w / 3);
   else qx = 0;
   rx = saved_x - ((saved_res_w / 2) * qx);
   x = ((ic->sd->w / 2) * qx) + rx;
   ic->x = x;

   if (saved_res_h >= 3) qy = saved_y / (saved_res_h / 3);
   else qy = 0;
   ry = saved_y - ((saved_res_h / 2) * qy);
   y = ((ic->sd->h / 2) * qy) + ry;
   ic->y = y;
}

static const char *
_mime_get(const char *path)
{
   const char *mime = efreet_mime_special_type_get(path);
   if (!mime) mime = efreet_mime_globs_type_get(path);
   if (!mime) mime = efreet_mime_fallback_type_get(path);
   return mime;
}

static int
_e_fm2_icon_fill(E_Fm2_Icon *ic, E_Fm2_Finfo *finf)
{
   Evas_Coord mw = 0, mh = 0;
   Evas_Object *obj, *obj2;
   char buf[PATH_MAX];
   const char *mime;
   E_Fm2_Custom_File *cf;

   if (!finf) return 0;
   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return 0;
   cf = e_fm2_custom_file_get(buf);
   memcpy(&(ic->info.statinfo), &(finf->st), sizeof(struct stat));
   if ((finf->lnk) && (finf->lnk[0]))
     ic->info.link = eina_stringshare_add(finf->lnk);
   else
     ic->info.link = NULL;
   if ((finf->rlnk) && (finf->rlnk[0]))
     ic->info.real_link = eina_stringshare_add(finf->rlnk);
   else
     ic->info.real_link = NULL;
   ic->info.broken_link = finf->broken_link;

   if ((!ic->info.link) && (S_ISDIR(ic->info.statinfo.st_mode)))
     {
        ic->info.mime = eina_stringshare_ref(_e_fm2_mime_inode_directory);
     }
   else if (ic->info.real_link)
     {
        mime = _mime_get(ic->info.real_link);
        if (!mime)
          /* XXX REMOVE/DEPRECATE ME LATER */
          mime = e_fm_mime_filename_get(ic->info.file);
        if (mime) ic->info.mime = eina_stringshare_add(mime);
     }

   if (!ic->info.mime)
     {
        mime = _mime_get(buf);
        if (!mime)
          /* XXX REMOVE/DEPRECATE ME LATER */
          mime = e_fm_mime_filename_get(ic->info.file);
        if (mime) ic->info.mime = eina_stringshare_add(mime);
     }

   if (_e_fm2_file_is_desktop(ic->info.file))
     _e_fm2_icon_desktop_load(ic);

   if (cf)
     {
        if (cf->icon.valid)
          {
             if (cf->icon.icon)
               {
                  eina_stringshare_replace(&ic->info.icon, cf->icon.icon);
               }
             ic->info.icon_type = cf->icon.type;
          }
        if (cf->geom.valid)
          {
             ic->saved_pos = EINA_TRUE;
             ic->x = cf->geom.x;
             ic->y = cf->geom.y;
             if (cf->geom.w > 0) ic->w = cf->geom.w;
             if (cf->geom.h > 0) ic->h = cf->geom.h;
             _e_fm2_icon_geom_adjust(ic, cf->geom.x, cf->geom.y, cf->geom.w, cf->geom.h, cf->geom.res_w, cf->geom.res_h);
          }
     }

   evas_event_freeze(evas_object_evas_get(ic->sd->obj));
   edje_freeze();

   switch (_e_fm2_view_mode_get(ic->sd))
     {
      case E_FM2_VIEW_MODE_ICONS:
      case E_FM2_VIEW_MODE_GRID_ICONS:
      case E_FM2_VIEW_MODE_CUSTOM_ICONS:
      case E_FM2_VIEW_MODE_CUSTOM_GRID_ICONS:
      case E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS:
        /* FIXME: need to define icon edjes. here goes:
         *
         * fileman/icon/fixed
         * fileman/icon/variable
         * fileman/list/fixed
         * fileman/list/variable
         * fileman/list_odd/fixed
         * fileman/list_odd/variable
         *
         */
        if ((!ic->sd->config->icon.fixed.w) || (!ic->sd->config->icon.fixed.h))
          {
             obj = ic->sd->tmp.obj;
             if (!obj)
               {
                  obj = edje_object_add(evas_object_evas_get(ic->sd->obj));
                  if ((ic->sd->config->icon.fixed.w) && (ic->sd->config->icon.fixed.h))
                    _e_fm2_theme_edje_object_set(ic->sd, obj,
                                                 "base/theme/fileman",
                                                 "icon/fixed");
                  else
                    _e_fm2_theme_edje_object_set(ic->sd, obj,
                                                 "base/theme/fileman",
                                                 "icon/variable");
                  ic->sd->tmp.obj = obj;
               }
             _e_fm2_icon_label_set(ic, obj);
             obj2 = ic->sd->tmp.obj2;
             if (!obj2)
               {
                  obj2 = evas_object_rectangle_add(evas_object_evas_get(ic->sd->obj));
                  ic->sd->tmp.obj2 = obj2;
               }
             /* FIXME: if icons are allowed to have their own size - use it */
             evas_object_size_hint_min_set(obj2, _e_fm2_icon_w_get(ic->sd), _e_fm2_icon_h_get(ic->sd));
             evas_object_size_hint_max_set(obj2, _e_fm2_icon_w_get(ic->sd), _e_fm2_icon_h_get(ic->sd));
             edje_object_part_swallow(obj, "e.swallow.icon", obj2);
             edje_object_size_min_calc(obj, &mw, &mh);
          }
        ic->w = mw;
        ic->h = mh;
        if (ic->sd->config->icon.fixed.w) ic->w = _e_fm2_icon_w_get(ic->sd);
        if (ic->sd->config->icon.fixed.h) ic->h = _e_fm2_icon_h_get(ic->sd);
        ic->min_w = mw;
        ic->min_h = mh;
        break;

      case E_FM2_VIEW_MODE_LIST:
        obj = ic->sd->tmp.obj;
        if (!obj)
          {
             obj = edje_object_add(evas_object_evas_get(ic->sd->obj));
// vairable sized list items are pretty usless - ignore.
//		  if (ic->sd->config->icon.fixed.w)
             _e_fm2_theme_edje_object_set(ic->sd, obj,
                                          "base/theme/fileman",
                                          "list/fixed");
//		  else
//		    _e_fm2_theme_edje_object_set(ic->sd, obj, "base/theme/fileman",
//					    "list/variable");
             ic->sd->tmp.obj = obj;
          }
        _e_fm2_icon_label_set(ic, obj);
        obj2 = ic->sd->tmp.obj2;
        if (!obj2)
          {
             obj2 = evas_object_rectangle_add(evas_object_evas_get(ic->sd->obj));
             ic->sd->tmp.obj2 = obj2;
          }
        evas_object_size_hint_min_set(obj2, ic->sd->config->icon.list.w, ic->sd->config->icon.list.h);
        evas_object_size_hint_max_set(obj2, ic->sd->config->icon.list.w, ic->sd->config->icon.list.h);
        edje_object_part_swallow(obj, "e.swallow.icon", obj2);
        edje_object_size_min_calc(obj, &mw, &mh);
        ic->w = MAX(mw, ic->sd->w);
        ic->h = mh;
        ic->min_w = mw;
        ic->min_h = mh;
        break;

      default:
        break;
     }
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(ic->sd->obj));
   return 1;
}

static void
_e_fm2_icon_free(E_Fm2_Icon *ic)
{
   if (ic->queued) abort();
   if (ic->inserted) abort();
   if (ic->eio)
     {
        eio_file_cancel(ic->eio);
        ic->eio = NULL;
     }
   /* free icon, object data etc. etc. */
   if (ic->sd->last_placed == ic)
     {
        ic->sd->last_placed = NULL;
     }
   if (ic->sd->drop_icon == ic)
     {
        /* FIXME: call hide call */
        ic->sd->drop_icon = NULL;
     }
   _e_fm2_icon_unrealize(ic);
   if (ic->menu)
     {
        e_menu_post_deactivate_callback_set(ic->menu, NULL, NULL);
        e_object_del(E_OBJECT(ic->menu));
        ic->menu = NULL;
     }
   if (ic->dialog)
     {
        e_object_del(E_OBJECT(ic->dialog));
        ic->dialog = NULL;
     }
   if (ic->entry_dialog)
     {
        e_object_del(E_OBJECT(ic->entry_dialog));
        ic->entry_dialog = NULL;
     }
   if (ic->entry_widget)
     _e_fm2_icon_entry_widget_del(ic);
   if (ic->prop_dialog)
     {
        e_object_del(E_OBJECT(ic->prop_dialog));
        ic->prop_dialog = NULL;
     }
   if (ic->mount)
     e_fm2_device_unmount(ic->mount);
   if (ic->mount_timer) ecore_timer_del(ic->mount_timer);
   if (ic->selected)
     ic->sd->selected_icons = eina_list_remove(ic->sd->selected_icons, ic);
   if (ic->drag.dnd_end_timer)
     ecore_timer_del(ic->drag.dnd_end_timer);
   eina_stringshare_del(ic->info.file);
   eina_stringshare_del(ic->info.mime);
   eina_stringshare_del(ic->info.label);
   eina_stringshare_del(ic->info.comment);
   eina_stringshare_del(ic->info.generic);
   eina_stringshare_del(ic->info.icon);
   eina_stringshare_del(ic->info.link);
   eina_stringshare_del(ic->info.real_link);
   eina_stringshare_del(ic->info.category);
   memset(ic, 0xff, sizeof(*ic));
   free(ic);
}

static void
_e_fm2_icon_label_click(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Fm2_Icon *ic = data;
   if (ic->entry_widget || ic->entry_dialog) return;
   if (!ic->selected) return;
   if (ecore_loop_time_get() - ic->selected_time < 0.2) return;

   if (ic->sd->config->view.no_click_rename) return;
   if (eina_list_count(ic->sd->selected_icons) != 1) return;
   if (eina_list_data_get(ic->sd->selected_icons) != ic) return;
   _e_fm2_file_rename(ic, NULL, NULL);
}

static void
_e_fm2_icon_realize(E_Fm2_Icon *ic)
{
   if (ic->realized) goto new_file;
   /* actually create evas objects etc. */
   ic->realized = EINA_TRUE;
   evas_event_freeze(evas_object_evas_get(ic->sd->obj));
   ic->obj = edje_object_add(evas_object_evas_get(ic->sd->obj));
   edje_object_freeze(ic->obj);
   evas_object_smart_member_add(ic->obj, ic->sd->obj);
   evas_object_stack_below(ic->obj, ic->sd->drop);
   if (_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_LIST)
     {
        const char *stacking;

//        if (ic->sd->config->icon.fixed.w)
//	  {
        if (ic->odd)
          _e_fm2_theme_edje_object_set(ic->sd, ic->obj,
                                       "base/theme/widgets",
                                       "list_odd/fixed");
        else
          _e_fm2_theme_edje_object_set(ic->sd, ic->obj,
                                       "base/theme/widgets",
                                       "list/fixed");
        stacking = edje_object_data_get(ic->obj, "stacking");
        if (stacking)
          {
             if (!strcmp(stacking, "below"))
               evas_object_stack_above(ic->obj, ic->sd->underlay);
             else if (!strcmp(stacking, "above"))
               evas_object_stack_below(ic->obj, ic->sd->drop);
          }

//	  }
//	else
//	  {
//	     if (ic->odd)
//	       _e_fm2_theme_edje_object_set(ic->sd, ic->obj, "base/theme/widgets",
//				       "list_odd/variable");
//	     else
//	       _e_fm2_theme_edje_object_set(ic->sd, ic->obj, "base/theme/widgets",
//				       "list/variable");
//	  }
     }
   else
     {
        if (ic->sd->config->icon.fixed.w)
          _e_fm2_theme_edje_object_set(ic->sd, ic->obj,
                                       "base/theme/fileman",
                                       "icon/fixed");
        else
          _e_fm2_theme_edje_object_set(ic->sd, ic->obj,
                                       "base/theme/fileman",
                                       "icon/variable");
     }
   _e_fm2_icon_label_set(ic, ic->obj);
   evas_object_clip_set(ic->obj, ic->sd->clip);
   evas_object_move(ic->obj,
                    ic->sd->x + ic->x - ic->sd->pos.x,
                    ic->sd->y + ic->y - ic->sd->pos.y);
   evas_object_resize(ic->obj, ic->w, ic->h);

   evas_object_event_callback_add(ic->obj, EVAS_CALLBACK_MOUSE_DOWN, _e_fm2_cb_icon_mouse_down, ic);
   evas_object_event_callback_add(ic->obj, EVAS_CALLBACK_MOUSE_UP, _e_fm2_cb_icon_mouse_up, ic);
   evas_object_event_callback_add(ic->obj, EVAS_CALLBACK_MOUSE_MOVE, _e_fm2_cb_icon_mouse_move, ic);
   evas_object_event_callback_add(ic->obj, EVAS_CALLBACK_MOUSE_IN, _e_fm2_cb_icon_mouse_in, ic);
   evas_object_event_callback_add(ic->obj, EVAS_CALLBACK_MOUSE_OUT, _e_fm2_cb_icon_mouse_out, ic);
   edje_object_signal_callback_add(ic->obj, "e,action,label,click", "e", _e_fm2_icon_label_click, ic);

   _e_fm2_icon_icon_set(ic);

   edje_object_thaw(ic->obj);
   evas_event_thaw(evas_object_evas_get(ic->sd->obj));
   evas_object_show(ic->obj);

   if (ic->selected)
     {
        const char *selectraise;

        /* FIXME: need new signal to INSTANTLY activate - no anim */
        /* FIXME: while listing dirs need to use icons in-place and not
         * unrealize and re-realize */
        edje_object_signal_emit(ic->obj, "e,state,selected", "e");
        selectraise = edje_object_data_get(ic->obj, "selectraise");
        if ((selectraise) && (!strcmp(selectraise, "on")))
          evas_object_stack_below(ic->obj, ic->sd->drop);
     }

   if (ic->info.removable)
     _e_fm2_icon_removable_update(ic);
new_file:
   if (ic->sd->new_file.thread && (!ic->sd->new_file.filename))
     {
        /* we got the file through the scanner :/ */
        const char *file = ecore_thread_global_data_find("efm_pending_filename");

        if (!e_util_strcmp(ic->info.file, file))
          {
             _e_fm2_file_rename(ic, NULL, NULL);
             ic->sd->new_file.done = 1;
          }
        if (file)
          ecore_thread_global_data_del("efm_pending_filename");
     }
   if (ic->sd->new_file.filename)
     {
        if (ic->info.file == ic->sd->new_file.filename)
          {
             _e_fm2_file_rename(ic, NULL, NULL);
             eina_stringshare_replace(&ic->sd->new_file.filename, NULL);
          }
     }
}

static void
_e_fm2_icon_unrealize(E_Fm2_Icon *ic)
{
   if (!ic->realized) return;
   /* delete evas objects */
   ic->realized = EINA_FALSE;
   evas_object_event_callback_del_full(ic->obj, EVAS_CALLBACK_MOUSE_DOWN, _e_fm2_cb_icon_mouse_down, ic);
   evas_object_event_callback_del_full(ic->obj, EVAS_CALLBACK_MOUSE_UP, _e_fm2_cb_icon_mouse_up, ic);
   evas_object_event_callback_del_full(ic->obj, EVAS_CALLBACK_MOUSE_MOVE, _e_fm2_cb_icon_mouse_move, ic);
   evas_object_event_callback_del_full(ic->obj, EVAS_CALLBACK_MOUSE_IN, _e_fm2_cb_icon_mouse_in, ic);
   evas_object_event_callback_del_full(ic->obj, EVAS_CALLBACK_MOUSE_OUT, _e_fm2_cb_icon_mouse_out, ic);
   evas_object_del(ic->obj);
   ic->obj = NULL;
   evas_object_del(ic->obj_icon);
   ic->obj_icon = NULL;
}

static Eina_Bool
_e_fm2_icon_visible(const E_Fm2_Icon *ic)
{
   /* return if the icon is visible */
   if (
     ((ic->x - ic->sd->pos.x) < (ic->sd->w + OVERCLIP)) &&
     ((ic->x + ic->w - ic->sd->pos.x) > (-OVERCLIP)) &&
     ((ic->y - ic->sd->pos.y) < (ic->sd->h + OVERCLIP)) &&
     ((ic->y + ic->h - ic->sd->pos.y) > (-OVERCLIP))
     )
     return 1;
   return 0;
}

static void
_e_fm2_icon_label_set(E_Fm2_Icon *ic, Evas_Object *obj)
{
   char buf[4096], *p;
   int len;
   const char *lbl, *type;

   if (ic->info.label)
     lbl = ic->info.label;
   else if ((ic->sd->config->icon.extension.show) || ((!ic->info.link) && (S_ISDIR(ic->info.statinfo.st_mode))))
     lbl = ic->info.file;
   else
     {
        /* remove extension. handle double extensions like .tar.gz too
         * also be fuzzy - up to 4 chars of extn is ok - eg .html but 5 or
         * more is considered part of the name
         */
        eina_strlcpy(buf, ic->info.file, sizeof(buf));

        len = strlen(buf);
        p = strrchr(buf, '.');
        if ((p) && ((len - (p - buf)) < 6))
          {
             *p = 0;

             len = strlen(buf);
             p = strrchr(buf, '.');
             if ((p) && ((len - (p - buf)) < 6)) *p = 0;
          }
        lbl = buf;
     }
   type = evas_object_type_get(edje_object_part_object_get(obj, "e.text.label"));
   if (!e_util_strcmp(type, "textblock"))
     {
        p = evas_textblock_text_utf8_to_markup(NULL, lbl);
        edje_object_part_text_set(obj, "e.text.label", p);
        free(p);
     }
   else
     edje_object_part_text_set(obj, "e.text.label", lbl);
}

static Evas_Object *
_e_fm2_icon_icon_direct_set(E_Fm2_Icon *ic, Evas_Object *o, Evas_Smart_Cb gen_func, void *data, int force_gen)
{
   Evas_Object *oic;

   oic = e_fm2_icon_get(evas_object_evas_get(o), ic,
                        gen_func, data, force_gen, NULL);
   if (oic)
     {
        edje_object_part_swallow(o, "e.swallow.icon", oic);
        evas_object_show(oic);
     }
   return oic;
}

static void
_e_fm2_icon_icon_set(E_Fm2_Icon *ic)
{
   if (!ic->realized) return;
   ic->obj_icon = _e_fm2_icon_icon_direct_set(ic, ic->obj,
                                              _e_fm2_cb_icon_thumb_gen,
                                              ic, 0);
}

static void
_e_fm2_icon_thumb(const E_Fm2_Icon *ic, Evas_Object *oic, int force)
{
   if ((force) ||
       ((_e_fm2_icon_visible(ic)) &&
        (!ic->sd->queue) &&
        (!ic->sd->sort_idler) &&
        (!ic->sd->listing)))
     e_thumb_icon_begin(oic);
}

static void
_e_fm2_icon_select(E_Fm2_Icon *ic)
{
   E_Fm2_Icon *prev;
   if (ic->selected) return;
   prev = eina_list_last_data_get(ic->sd->selected_icons);
   if (prev) prev->last_selected = EINA_FALSE;
   ic->selected = EINA_TRUE;
   ic->sd->range_selected = NULL;
   ic->sd->last_selected = ic;
   ic->sd->selected_icons = eina_list_append(ic->sd->selected_icons, ic);
   ic->last_selected = EINA_TRUE;
   ic->selected_time = ecore_loop_time_get();
   if (ic->realized)
     {
        const char *selectraise;

        if (ic->sd->iop_icon)
          _e_fm2_icon_entry_widget_accept(ic->sd->iop_icon);

        edje_object_signal_emit(ic->obj, "e,state,selected", "e");
        evas_object_stack_below(ic->obj, ic->sd->drop);
        selectraise = edje_object_data_get(ic->obj, "selectraise");
        if ((selectraise) && (!strcmp(selectraise, "on")))
          evas_object_stack_below(ic->obj, ic->sd->drop);
     }
}

static void
_e_fm2_icon_deselect(E_Fm2_Icon *ic)
{
   if (!ic->selected) return;
   ic->selected = EINA_FALSE;
   ic->last_selected = EINA_FALSE;
   ic->sd->range_selected = NULL;
   if (ic->sd->last_selected == ic) ic->sd->last_selected = NULL;
   ic->sd->selected_icons = eina_list_remove(ic->sd->selected_icons, ic);
   ic->selected_time = 0.0;
   if (ic->realized)
     {
        const char *stacking, *selectraise;

        if (ic->entry_widget)
          _e_fm2_icon_entry_widget_del(ic);

        edje_object_signal_emit(ic->obj, "e,state,unselected", "e");
        stacking = edje_object_data_get(ic->obj, "stacking");
        selectraise = edje_object_data_get(ic->obj, "selectraise");
        if ((selectraise) && (!strcmp(selectraise, "on")))
          {
             if ((stacking) && (!strcmp(stacking, "below")))
               evas_object_stack_above(ic->obj, ic->sd->underlay);
          }
     }
}

static const char *
_e_fm2_icon_desktop_url_eval(const char *val)
{
   const char *s;
   char *path, *p;

   if (strlen(val) < 6) return NULL;
   if (strncmp(val, "file:", 5)) return NULL;
   path = (char *)val + 5;
   p = e_util_shell_env_path_eval(path);
   if (!p) return NULL;
   path = p;
   while (*path == '/')
     path++;
   path--;
   s = eina_stringshare_add(path);
   free(p);
   return s;
}

E_API const char *
e_fm2_desktop_url_eval(const char *val)
{
   return _e_fm2_icon_desktop_url_eval(val);
}

static void
_e_fm2_cb_eio_stat(void *data, Eio_File *handler EINA_UNUSED, const Eina_Stat *st)
{
   E_Fm2_Icon *ic = data;
   ic->eio = NULL;
#define FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(member) \
  ic->info.statinfo.st_##member = st->member

   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(dev);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(ino);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(mode);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(nlink);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(uid);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(gid);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(rdev);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(size);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(blksize);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(blocks);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(atime);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(mtime);
   FUCK_EINA_STAT_WHY_IS_IT_NOT_THE_SAME_AS_STAT(ctime);
}

static void
_e_fm2_cb_eio_err(void *data, Eio_File *handler EINA_UNUSED, int error EINA_UNUSED)
{
   E_Fm2_Icon *ic = data;
   ic->eio = NULL;
}

static int
_e_fm2_icon_desktop_load(E_Fm2_Icon *ic)
{
   char buf[PATH_MAX];
   Efreet_Desktop *desktop;

   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return 0;

   desktop = efreet_desktop_new(buf);
//   printf("efreet_desktop_new(%s) = %p\n", buf, desktop);
   if (!desktop) goto error;
//   if (desktop->type != EFREET_DESKTOP_TYPE_LINK) goto error;

   ic->info.removable = EINA_FALSE;
   ic->info.removable_full = EINA_FALSE;
   ic->info.label = eina_stringshare_add(desktop->name);
   ic->info.generic = eina_stringshare_add(desktop->generic_name);
   ic->info.comment = eina_stringshare_add(desktop->comment);
   ic->info.icon = eina_stringshare_add(desktop->icon);
   if (desktop->url)
     ic->info.link = _e_fm2_icon_desktop_url_eval(desktop->url);
   if (ic->info.link)
     {
        if (!ic->eio)
          ic->eio = eio_file_direct_stat(ic->info.link, _e_fm2_cb_eio_stat, _e_fm2_cb_eio_err, ic);
     }
   if (desktop->x)
     {
        const char *type;

        type = eina_hash_find(desktop->x, "X-Enlightenment-Type");
        if (type)
          {
             if (!strcmp(type, "Mount")) ic->info.mount = EINA_TRUE;
             else if (!strcmp(type, "Removable"))
               {
                  ic->info.removable = EINA_TRUE;
                  if ((!e_fm2_device_storage_find(ic->info.link)) &&
                      (!e_fm2_device_volume_find(ic->info.link)))
                    {
                       /* delete .desktop for non existing device */
                       if (ecore_file_remove(buf))
                         _e_fm2_live_file_del(ic->sd->obj, ic->info.file);
                       else /* ignore */
                         _e_fm2_file_del(ic->sd->obj, ic->info.file);

                       efreet_desktop_free(desktop);
                       goto error;
                    }
               }
             type = eina_hash_find(desktop->x, "X-Enlightenment-Removable-State");
             if (type)
               {
                  if (!strcmp(type, "Full"))
                    ic->info.removable_full = EINA_TRUE;
               }
          }
     }
   /* FIXME: get category */
   ic->info.category = NULL;
   efreet_desktop_free(desktop);

   return 1;
error:
   eina_stringshare_del(ic->info.label);
   eina_stringshare_del(ic->info.comment);
   eina_stringshare_del(ic->info.generic);
   eina_stringshare_del(ic->info.icon);
   eina_stringshare_del(ic->info.link);
   eina_stringshare_del(ic->info.category);
   ic->info.label = NULL;
   ic->info.comment = NULL;
   ic->info.generic = NULL;
   ic->info.icon = NULL;
   ic->info.link = NULL;
   ic->info.category = NULL;
   //Hack
   if (!strncmp(ic->info.file, "|storage_", 9)) ecore_file_unlink(buf);
   return 0;
}

/**************************/
static E_Fm2_Region *
_e_fm2_region_new(E_Fm2_Smart_Data *sd)
{
   E_Fm2_Region *rg;

   rg = E_NEW(E_Fm2_Region, 1);
   rg->sd = sd;
   return rg;
}

static void
_e_fm2_region_free(E_Fm2_Region *rg)
{
   E_Fm2_Icon *ic;

   EINA_LIST_FREE(rg->list, ic)
     ic->region = NULL;
   free(rg);
}

static void
_e_fm2_region_realize(E_Fm2_Region *rg)
{
   const Eina_List *l;
   E_Fm2_Icon *ic;

   if (rg->realized) return;
   /* actually create evas objects etc. */
   rg->realized = 1;
   edje_freeze();
   EINA_LIST_FOREACH(rg->list, l, ic)
     _e_fm2_icon_realize(ic);
   EINA_LIST_FOREACH(rg->list, l, ic)
     {
        if (ic->selected)
          evas_object_stack_below(ic->obj, ic->sd->drop);
     }
   edje_thaw();
}

static void
_e_fm2_region_unrealize(E_Fm2_Region *rg)
{
   Eina_List *l;
   E_Fm2_Icon *ic;

   if (!rg->realized) return;
   /* delete evas objects */
   rg->realized = 0;
   edje_freeze();
   EINA_LIST_FOREACH(rg->list, l, ic)
     _e_fm2_icon_unrealize(ic);
   edje_thaw();
}

static int
_e_fm2_region_visible(E_Fm2_Region *rg)
{
   /* return if the icon is visible */
   if (
     ((rg->x - rg->sd->pos.x) < (rg->sd->w + OVERCLIP)) &&
     ((rg->x + rg->w - rg->sd->pos.x) > (-OVERCLIP)) &&
     ((rg->y - rg->sd->pos.y) < (rg->sd->h + OVERCLIP)) &&
     ((rg->y + rg->h - rg->sd->pos.y) > (-OVERCLIP))
     )
     return 1;
   return 0;
}

static void
_e_fm2_icon_make_visible(E_Fm2_Icon *ic)
{
   if (_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_LIST)
     {
        if (
          ((ic->y - ic->sd->pos.y) >= 0) &&
          ((ic->y + ic->h - ic->sd->pos.y) <= (ic->sd->h))
          )
          return;
        if ((ic->y - ic->sd->pos.y) < 0)
          e_fm2_pan_set(ic->sd->obj, ic->sd->pos.x, ic->y);
        else
          e_fm2_pan_set(ic->sd->obj, ic->sd->pos.x, ic->y - ic->sd->h + ic->h);
     }
   else
     {
        Evas_Coord x, y;

        if (
          ((ic->y - ic->sd->pos.y) >= 0) &&
          ((ic->y + ic->h /* + ICON_BOTTOM_SPACE*/ - ic->sd->pos.y) <= (ic->sd->h)) &&
          ((ic->x - ic->sd->pos.x) >= 0) &&
          ((ic->x + ic->w - ic->sd->pos.x) <= (ic->sd->w))
          )
          return;
        x = ic->sd->pos.x;
        if ((ic->x - ic->sd->pos.x) < 0)
          x = ic->x;
        else if ((ic->x + ic->w - ic->sd->pos.x) > (ic->sd->w))
          x = ic->x + ic->w - ic->sd->w;
        y = ic->sd->pos.y;
        if ((ic->y - ic->sd->pos.y) < 0)
          y = ic->y;
        else if ((ic->y + ic->h /* + ICON_BOTTOM_SPACE*/ - ic->sd->pos.y) > (ic->sd->h))
          y = ic->y + ic->h /* + ICON_BOTTOM_SPACE*/ - ic->sd->h;
        e_fm2_pan_set(ic->sd->obj, x, y);
     }
   evas_object_smart_callback_call(ic->sd->obj, "pan_changed", NULL);
}

static void
_e_fm2_icon_desel_any(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l, *ll;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   EINA_LIST_FOREACH_SAFE(sd->selected_icons, l, ll, ic)
     _e_fm2_icon_deselect(ic);
}

static E_Fm2_Icon *
_e_fm2_icon_first_selected_find(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return eina_list_data_get(sd->selected_icons);
}

static void
_e_fm2_icon_sel_first(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->icons) return;
   if ((!add) || sd->config->selection.single)
     _e_fm2_icon_desel_any(obj);
   ic = eina_list_data_get(sd->icons);
   _e_fm2_icon_select(ic);
   evas_object_smart_callback_call(sd->obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic);
}

static void
_e_fm2_icon_sel_last(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->icons) return;
   if ((!add) || sd->config->selection.single)
     _e_fm2_icon_desel_any(obj);
   ic = eina_list_last_data_get(sd->icons);
   _e_fm2_icon_select(ic);
   evas_object_smart_callback_call(sd->obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic);
}

static void
_e_fm2_icon_sel_any(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;
   Eina_List *l;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->icons) return;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     if (!ic->selected) _e_fm2_icon_select(ic);
}

static E_Fm2_Icon *
_e_fm2_icon_next_find(Evas_Object *obj, int next, int (*match_func)(E_Fm2_Icon *ic, void *data), void *data)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Icon *ic, *ic_next;
   char view_mode;
   int x = 0, y = 0, custom = 0;
   int dist = 0, min = 65535;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   if (!sd->icons) return NULL;

   l = eina_list_data_find_list(sd->icons, (sd->range_selected) ?
                                     sd->range_selected :
                                     sd->last_selected);

   if (!next) return eina_list_data_get(l);

   if (!l) return (next == 1) ? eina_list_data_get(sd->icons) :
                                eina_list_last_data_get(sd->icons);

   view_mode = _e_fm2_view_mode_get(sd);
   if ((view_mode == E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS) ||
       (view_mode == E_FM2_VIEW_MODE_CUSTOM_GRID_ICONS) ||
       (view_mode == E_FM2_VIEW_MODE_CUSTOM_ICONS))
     custom = 1;

   ic_next = NULL;

   if (custom || match_func)
     {
        ic = eina_list_data_get(l);
        x = ic->x;
        y = ic->y;

        EINA_LIST_FOREACH(sd->icons, l, ic)
          {
             if (match_func && !match_func(ic, data)) continue;

             int dx = (ic->x - x);
             int dy = (ic->y - y);
             int sgnx = (dx) ? dx / abs(dx) : 0;

             if ((next == sgnx) && (abs(dx) >= abs(dy)))
               {
                  dist = abs(dy) + abs(dx);
                  if (dist < min)
                    {
                       min = dist;
                       ic_next = ic;
                    }
               }
          }
     }
   else
     {
        ic_next = eina_list_data_get((next == 1) ? eina_list_next(l) :
                                                   eina_list_prev(l));
     }

   return ic_next;
}

static void
_e_fm2_icon_sel_prev(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Icon *ic_prev;

   ic_prev = _e_fm2_icon_next_find(obj, -1, NULL, NULL);

   if (!ic_prev) return;

   if ((!add) || ic_prev->sd->config->selection.single)
     {
        _e_fm2_icon_desel_any(obj);
        _e_fm2_icon_select(ic_prev);
     }
   else
     {
        _e_fm2_icon_range_select(ic_prev);
     }

   evas_object_smart_callback_call(obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic_prev);
}

static void
_e_fm2_icon_sel_next(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Icon *ic_next;

   ic_next = _e_fm2_icon_next_find(obj, 1, NULL, NULL);

   if (!ic_next) return;

   if ((!add) || ic_next->sd->config->selection.single)
     {
        _e_fm2_icon_desel_any(obj);
        _e_fm2_icon_select(ic_next);
     }
   else
     {
        _e_fm2_icon_range_select(ic_next);
     }
   evas_object_smart_callback_call(obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic_next);
}

static void
_e_fm2_icon_sel_down(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Icon *ic, *ic2, *ic_down;
   int dist, min = 65535;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->icons) return;

   ic = (sd->range_selected) ? sd->range_selected : sd->last_selected;

   if (ic)
     {
       ic_down = ic;
       EINA_LIST_FOREACH(sd->icons, l, ic2)
         {
            int dx = (ic2->x - ic->x);
            int dy = (ic2->y - ic->y);

            if ((dy > 0) && (abs(dy) > abs(dx)))
              {
                 dist = abs(dx)+abs(dy);
                 if (dist < min)
                   {
                      min = dist;
                      ic_down = ic2;
                   }
              }
         }
     }
   else
     {
        ic_down = eina_list_data_get(sd->icons);
     }

   if ((!add) || ic_down->sd->config->selection.single)
     {
        _e_fm2_icon_desel_any(obj);
        _e_fm2_icon_select(ic_down);
     }
   else
     {
        _e_fm2_icon_range_select(ic_down);
     }

   evas_object_smart_callback_call(sd->obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic_down);
}

static void
_e_fm2_icon_sel_up(Evas_Object *obj, Eina_Bool add)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;
   E_Fm2_Icon *ic, *ic2, *ic_down;
   int dist, min = 65535;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->icons) return;

   ic = (sd->range_selected) ? sd->range_selected : sd->last_selected;

   if (ic)
     {
        ic_down = ic;
        EINA_LIST_FOREACH(sd->icons, l, ic2)
          {
             int dx = (ic2->x - ic->x);
             int dy = (ic2->y - ic->y);

             if ((dy < 0) && (abs(dy) > abs(dx)))
               {
                  dist = abs(dx) + abs(dy);

                  if (dist < min)
                    {
                       min = dist;
                       ic_down = ic2;
                    }
               }
           }
     }
   else
     {
        ic_down = eina_list_last_data_get(sd->icons);
     }

   if ((!add) || ic_down->sd->config->selection.single)
     {
        _e_fm2_icon_desel_any(obj);
        _e_fm2_icon_select(ic_down);
     }
   else
     {
        _e_fm2_icon_range_select(ic_down);
     }

   evas_object_smart_callback_call(sd->obj, "selection_change", NULL);
   _e_fm2_icon_make_visible(ic_down);
}

static void
_e_fm2_icon_range_select(E_Fm2_Icon *ic)
{
   const Eina_List *l;
   E_Fm2_Icon *ic2;
   E_Fm2_Icon *last;

   char view_mode = _e_fm2_view_mode_get(ic->sd);

   if (!ic->sd->range_selected)
     {
        last = ic->sd->last_selected;
        ic->sd->range_select_anchor = last;
     }
   else
     {
        last = ic->sd->range_select_anchor;
     }

   _e_fm2_icon_desel_any(ic->sd->obj);

   if ((!last) || (last == ic))
     {
        _e_fm2_icon_select(ic);
        ic->sd->range_select_anchor = ic;
     }
   else if ((view_mode == E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS) ||
            (view_mode == E_FM2_VIEW_MODE_CUSTOM_GRID_ICONS) ||
            (view_mode == E_FM2_VIEW_MODE_CUSTOM_ICONS))
     {
        int topx = (ic->x <= last->x) ? ic->x : last->x;
        int boundx = (ic->x < last->x) ? (last->x + last->w) : (ic->x + ic->w);
        int topy = (ic->y <= last->y) ? ic->y : last->y;
        int boundy = (ic->y < last->y) ? (last->y + last->h) : (ic->y + ic->h);

        EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
          {
             if ((ic2->x >= topx) && (ic2->x < boundx) &&
                 (ic2->y >= topy) && (ic2->y < boundy))
               _e_fm2_icon_select(ic2);
          }
     }
   else
     {
        int trig = 2;
        EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
          {
             if ((ic2 == last) || (ic2 == ic)) trig--;
             if ((trig < 2)) _e_fm2_icon_select(ic2);
             if (!trig) break;
          }
     }

   ic->sd->range_selected = ic;
}

/* FIXME: prototype */
static void
_e_fm2_typebuf_show(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->typebuf.disabled) return;
   E_FREE(sd->typebuf.buf);
   sd->typebuf.buf = strdup("");
   edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", sd->typebuf.buf);
   edje_object_signal_emit(sd->overlay, "e,state,typebuf,start", "e");
   sd->typebuf_visible = EINA_TRUE;
}

static void
_e_fm2_typebuf_hide(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->typebuf_visible) return;
   if (sd->typebuf.setting) return;
   E_FREE(sd->typebuf.buf);
   edje_object_signal_emit(sd->overlay, "e,state,typebuf,stop", "e");
   eina_stringshare_replace(&sd->typebuf.start, NULL);
   sd->typebuf_visible = EINA_FALSE;
   sd->typebuf.wildcard = 0;
   if (sd->typebuf.timer) ecore_timer_del(sd->typebuf.timer);
   sd->typebuf.timer = NULL;
}

#if 0
static void
_e_fm2_typebuf_history_prev(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* FIXME: do */
}

static void
_e_fm2_typebuf_history_next(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   /* FIXME: do */
}

#endif

static int
_e_fm2_inplace_open(const E_Fm2_Icon *ic)
{
   char buf[PATH_MAX];

   if (((!S_ISDIR(ic->info.statinfo.st_mode)) ||
        (ic->info.link && (!S_ISDIR(ic->info.statinfo.st_mode))) ||
        (!ic->sd->config->view.open_dirs_in_place) ||
        (ic->sd->config->view.no_subdir_jump)))
     return 0;

   if (!_e_fm2_icon_path(ic, buf, sizeof(buf)))
     return -1;

   e_fm2_path_set(ic->sd->obj,
                  ic->info.real_link ? "/" : ic->sd->dev,
                  buf);

   return 1;
}

static void
_e_fm2_typebuf_run(Evas_Object *obj)
{
   E_Fm2_Icon *ic;

   _e_fm2_typebuf_hide(obj);
   ic = _e_fm2_icon_first_selected_find(obj);
   if (ic)
     {
        if (_e_fm2_inplace_open(ic) == 0)
          evas_object_smart_callback_call(ic->sd->obj, "selected", NULL);
     }
}

static int
_e_fm2_typebuf_match_func(E_Fm2_Icon *ic, void *data)
{
   char *s, *tb = data;
   s = strrchr(tb, '/');
   if (s) tb = s + 1;
   return ((ic->info.label) &&
           (e_util_glob_case_match(ic->info.label, tb))) ||
          ((ic->info.file) &&
           (e_util_glob_case_match(ic->info.file, tb)));
}

static Eina_Bool
_e_fm_typebuf_timer_cb(void *data)
{
   E_Fm2_Smart_Data *sd = data;

   if (!sd) return ECORE_CALLBACK_CANCEL;
   sd->typebuf.timer = NULL;
   if (!sd->typebuf_visible) return ECORE_CALLBACK_CANCEL;

   _e_fm2_typebuf_hide(sd->obj);
   return ECORE_CALLBACK_CANCEL;
}

static E_Fm2_Icon *
_e_fm2_typebuf_match(Evas_Object *obj, int next)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic, *ic_match = NULL;
   Eina_List *l, *sel = NULL;
   char *tb;
   int tblen, x;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   if (sd->typebuf.disabled) return NULL;
   if (!sd->typebuf.buf) return NULL;
   if (!sd->icons) return NULL;

   tblen = strlen(sd->typebuf.buf);
   if (sd->typebuf.buf[tblen - 1] != '*')
     {
        tb = alloca(tblen + 2);
        memcpy(tb, sd->typebuf.buf, tblen);
        tb[tblen] = '*';
        tb[tblen + 1] = '\0';
     }
   else
     {
        size_t blen = strlen(sd->typebuf.buf);
        tb = memcpy(alloca(blen + 1), sd->typebuf.buf, blen + 1);
     }

   if (!next)
     {
        EINA_LIST_FOREACH(sd->icons, l, ic)
          {
             if (_e_fm2_typebuf_match_func(ic, tb))
               {
                  sel = eina_list_append(sel, ic);
                  if (!sd->typebuf.wildcard) break;
               }
          }
        if (eina_list_count(sel) == 1)
          ic_match = eina_list_data_get(sel);
     }
   else
     {
        ic_match = _e_fm2_icon_next_find(obj, next, &_e_fm2_typebuf_match_func, tb);
     }

   for (x = 0; x < 2; x++)
     {
        switch (x)
          {
           case 0:
             if (!sd->selected_icons) continue;
             if (eina_list_count(sel) != eina_list_count(sd->selected_icons)) continue;
             EINA_LIST_FOREACH(sd->selected_icons, l, ic)
               if (!eina_list_data_find(sel, ic))
                 {
                    x++;
                    break;
                 }
             if (!x)
               {
                  /* selections are identical, don't change */
                  _e_fm2_icon_make_visible(eina_list_data_get(sel));
                  sel = eina_list_free(sel);
                  x++;
                  break;
               }
             EINA_FALLTHROUGH;
             /* no break */

           case 1:
             _e_fm2_icon_desel_any(obj);
             if (sel)
               {
                  if (!ic_match) ic_match = eina_list_data_get(sel);
                  _e_fm2_icon_make_visible(eina_list_data_get(sel));
                  EINA_LIST_FREE(sel, ic)
                    _e_fm2_icon_select(ic);
               }
             evas_object_smart_callback_call(obj, "selection_change", NULL);
          }
     } while (0)
     ;

   if (sd->typebuf.timer) ecore_timer_loop_reset(sd->typebuf.timer);
   else sd->typebuf.timer = ecore_timer_loop_add(3.5, _e_fm_typebuf_timer_cb, sd);
   return ic_match;
}

static void
_e_fm2_typebuf_complete(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->typebuf.disabled) return;
   if ((!sd->typebuf.buf) || (!sd->typebuf.buf[0])) return;
   ic = _e_fm2_typebuf_match(obj, 0);
   if (!ic) return;
   if ((sd->typebuf.buf[0] == '/') || (!memcmp(sd->typebuf.buf, "~/", 2)))
     {
        char *buf, *s;
        size_t size;

        s = strrchr(sd->typebuf.buf, '/');
        if (!s) return;
        s++;
        s[0] = 0;
        size = s - sd->typebuf.buf + strlen(ic->info.file) + 1;
        buf = malloc(size);
        if (!buf) return;
        snprintf(buf, size, "%s%s", sd->typebuf.buf, ic->info.file);
        free(sd->typebuf.buf);
        sd->typebuf.buf = buf;
        edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", sd->typebuf.buf);
        evas_object_smart_callback_call(sd->obj, "typebuf_changed", sd->typebuf.buf);
     }
   else
     {
        free(sd->typebuf.buf);
        sd->typebuf.buf = strdup(ic->info.file);
        if (!sd->typebuf.buf)
          {
             edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", "");
             return;
          }
        eina_stringshare_replace(&sd->typebuf.start, sd->realpath);
        edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", sd->typebuf.buf);
        evas_object_smart_callback_call(sd->obj, "typebuf_changed", sd->typebuf.buf);
     }
}

static void
_e_fm2_typebuf_char_append(Evas_Object *obj, const char *ch)
{
   E_Fm2_Smart_Data *sd;
   char *ts;
   size_t len;

   if ((!ch) || (!ch[0])) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->typebuf.disabled) return;
   if (!sd->typebuf.buf) return;
   len = strlen(sd->typebuf.buf) + strlen(ch);
   ts = malloc(len + 1);
   if (!ts) return;
   strcpy(ts, sd->typebuf.buf);
   strcat(ts, ch);
   free(sd->typebuf.buf);
   sd->typebuf.buf = ts;
   if (ch[0] == '*')
     sd->typebuf.wildcard++;
   _e_fm2_typebuf_match(obj, 0);
   if ((!sd->config->view.no_typebuf_set) && (ch[0] == '/'))
     {
        if (sd->typebuf.buf[0] == '/')
          {
             if (ecore_file_is_dir(sd->typebuf.buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "/", sd->typebuf.buf);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
        else if (sd->typebuf.buf[0] != '~')
          {
             char buf[PATH_MAX];

             if (!sd->typebuf.start) sd->typebuf.start = eina_stringshare_ref(sd->realpath);
             snprintf(buf, sizeof(buf), "%s/%s", sd->typebuf.start, sd->typebuf.buf);
             if (ecore_file_is_dir(buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "/", buf);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
        else if (!memcmp(sd->typebuf.buf, "~/", 2))
          {
             char buf[PATH_MAX];

             snprintf(buf, sizeof(buf), "%s/%s", getenv("HOME"), sd->typebuf.buf + 2);
             if (ecore_file_is_dir(buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "~/", sd->typebuf.buf + 1);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
     }
   edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", sd->typebuf.buf);
   evas_object_smart_callback_call(sd->obj, "typebuf_changed", sd->typebuf.buf);
}

static void
_e_fm2_typebuf_char_backspace(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   int len, p, dec;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->typebuf.disabled) return;
   if (!sd->typebuf.buf) return;
   len = strlen(sd->typebuf.buf);
   if (len == 0)
     {
        _e_fm2_typebuf_hide(obj);
        return;
     }
   p = evas_string_char_prev_get(sd->typebuf.buf, len, &dec);
   if (p < 0) return;
   sd->typebuf.buf[p] = 0;
   len--;
   if (dec == '*')
     sd->typebuf.wildcard--;
   if (len) _e_fm2_typebuf_match(obj, 0);
   if ((!sd->config->view.no_typebuf_set) && (dec == '/'))
     {
        if ((len > 1) || (sd->typebuf.buf[0] == '/'))
          {
             if (ecore_file_is_dir(sd->typebuf.buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "/", sd->typebuf.buf);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
        else if ((len > 1) || (sd->typebuf.buf[0] != '~'))
          {
             char buf[PATH_MAX];

             if (!sd->typebuf.start) sd->typebuf.start = eina_stringshare_ref(sd->realpath);
             snprintf(buf, sizeof(buf), "%s/%s", sd->typebuf.start, sd->typebuf.buf);
             if (ecore_file_is_dir(buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "/", buf);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
        else if (!memcmp(sd->typebuf.buf, "~/", 2))
          {
             char buf[PATH_MAX];

             snprintf(buf, sizeof(buf), "%s/%s", getenv("HOME"), sd->typebuf.buf + 2);
             if (ecore_file_is_dir(buf))
               {
                  sd->typebuf.setting = EINA_TRUE;
                  e_fm2_path_set(obj, "~/", sd->typebuf.buf + 1);
                  sd->typebuf.setting = EINA_FALSE;
               }
          }
     }
   edje_object_part_text_set(sd->overlay, "e.text.typebuf_label", sd->typebuf.buf);
   evas_object_smart_callback_call(sd->obj, "typebuf_changed", sd->typebuf.buf);
}

/**************************/

/* FIXME: prototype + reposition + implement */
static void
_e_fm2_dnd_drop_configure(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->drop_icon) return;
   if (sd->drop_after == -1)
     {
        evas_object_move(sd->drop_in,
                         sd->x + sd->drop_icon->x - sd->pos.x,
                         sd->y + sd->drop_icon->y - sd->pos.y);
        evas_object_resize(sd->drop_in, sd->drop_icon->w, sd->drop_icon->h);
     }
   else if (sd->drop_after)
     {
        evas_object_move(sd->drop,
                         sd->x + sd->drop_icon->x - sd->pos.x,
                         sd->y + sd->drop_icon->y - sd->pos.y + sd->drop_icon->h - 1);
        evas_object_resize(sd->drop, sd->drop_icon->w, 2);
     }
   else
     {
        evas_object_move(sd->drop,
                         sd->x + sd->drop_icon->x - sd->pos.x,
                         sd->y + sd->drop_icon->y - sd->pos.y - 1);
        evas_object_resize(sd->drop, sd->drop_icon->w, 2);
     }
}

/* FIXME: prototype + reposition + implement */
static void
_e_fm2_dnd_drop_all_show(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->drop_show)
     {
        edje_object_signal_emit(sd->drop, "e,state,unselected", "e");
        sd->drop_show = EINA_FALSE;
     }
   if (sd->drop_in_show)
     {
        edje_object_signal_emit(sd->drop_in, "e,state,unselected", "e");
        sd->drop_in_show = EINA_FALSE;
     }
   if (!sd->drop_all)
     {
        edje_object_signal_emit(sd->overlay, "e,state,drop,start", "e");
        sd->drop_all = EINA_TRUE;
     }
   sd->drop_icon = NULL;
   sd->drop_after = EINA_FALSE;
}

/* FIXME: prototype + reposition + implement */
static void
_e_fm2_dnd_drop_all_hide(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->drop_all)
     {
        edje_object_signal_emit(sd->overlay, "e,state,drop,stop", "e");
        sd->drop_all = EINA_FALSE;
     }
}

/* FIXME: prototype + reposition + implement */
static void
_e_fm2_dnd_drop_show(E_Fm2_Icon *ic, int after)
{
   int emit = 0;

   if ((ic->sd->drop_icon == ic) &&
       (ic->sd->drop_after == after)) return;
   if (((!ic->sd->drop_icon)) ||
       ((after < 0) && (ic->sd->drop_after >= 0)) ||
       ((after >= 0) && (ic->sd->drop_after < 0)))
     emit = 1;
   evas_object_smart_callback_call(ic->sd->obj, "dnd_changed", &ic->info);
   ic->sd->drop_icon = ic;
   ic->sd->drop_after = after;
   if (emit)
     {
        if (ic->sd->drop_after != -1)
          {
             edje_object_signal_emit(ic->sd->drop_in, "e,state,unselected", "e");
             edje_object_signal_emit(ic->sd->drop, "e,state,selected", "e");
             ic->sd->drop_in_show = EINA_FALSE;
             ic->sd->drop_show = EINA_TRUE;
          }
        else
          {
             edje_object_signal_emit(ic->sd->drop, "e,state,unselected", "e");
             edje_object_signal_emit(ic->sd->drop_in, "e,state,selected", "e");
             ic->sd->drop_in_show = EINA_TRUE;
             ic->sd->drop_show = EINA_FALSE;
          }
     }
   _e_fm2_dnd_drop_all_hide(ic->sd->obj);
   _e_fm2_dnd_drop_configure(ic->sd->obj);
}

/* FIXME: prototype + reposition + implement */
static void
_e_fm2_dnd_drop_hide(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->drop_show)
     {
        edje_object_signal_emit(sd->drop, "e,state,unselected", "e");
        sd->drop_show = EINA_FALSE;
     }
   if (sd->drop_in_show)
     {
        edje_object_signal_emit(sd->drop_in, "e,state,unselected", "e");
        sd->drop_in_show = EINA_FALSE;
     }
   sd->drop_icon = NULL;
   sd->drop_after = EINA_FALSE;
}

/* FIXME: prototype + reposition + implement */
static Eina_Bool
_e_fm2_dnd_type_implemented(const char *type)
{
   const char ***t;

   for (t = _e_fm2_dnd_types; *t; t++)
     {
        if (type == **t) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
_e_fm2_dnd_finish(Evas_Object *obj, int refresh)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;
   const Eina_List *l;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (!sd->drag) return;
   sd->drag = EINA_FALSE;
   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        ic->drag.src = EINA_FALSE;
        if (ic->drag.hidden) continue;
        if (ic->obj) evas_object_show(ic->obj);
        if (ic->obj_icon) evas_object_show(ic->obj_icon);
     }
   if (refresh) e_fm2_refresh(obj);
}

static Eina_Bool
_e_fm2_cb_dnd_scroller(E_Fm2_Smart_Data *sd)
{
   int cx = 0, cy = 0;
   int x = 0, y = 0, w = 0, h = 0;
   int mx = 0, my = 0;
   double px = 0.0;

#undef EFM_MAX_PIXEL_DRAG
#define EFM_MAX_PIXEL_DRAG 25

   px = EFM_MAX_PIXEL_DRAG * ecore_animator_frametime_get();
   x = y = 500;

   if (sd->w < 100)
     w = sd->w / 5;
   else if (sd->w < 500)
     w = sd->w / 8;
   else
     w = sd->w / 10;

   if (sd->h < 100)
     h = sd->h / 5;
   else if (sd->h < 500)
     h = sd->h / 8;
   else
     h = sd->h / 10;

   if (sd->dnd_current.x < w)
     {
        /* left scroll */
        x = lround((-px) * (double)(w - sd->dnd_current.x));
     }
   else if (sd->dnd_current.x > sd->w - w)
     {
        /* right scroll */
        x = lround((px) * (double)(sd->dnd_current.x - (sd->w - w)));
     }

   if (sd->dnd_current.y < h)
     {
        /* up scroll */
        y = lround((-px) * (double)(h - sd->dnd_current.y));
     }
   else if (sd->dnd_current.y > sd->h - h)
     {
        /* down scroll */
        y = lround((px) * (double)(sd->dnd_current.y - (sd->h - h)));
     }

   if ((x == 500) && (y == 500)) return EINA_TRUE;

   e_fm2_pan_get(sd->obj, &cx, &cy);
   e_fm2_pan_max_get(sd->obj, &mx, &my);
   x = MIN(cx + x, mx);
   y = MIN(cy + y, my);
   x = MAX(0, x);
   y = MAX(0, y);
   e_fm2_pan_set(sd->obj, x, y);
   if ((sd->drop_icon) &&
       (!E_INSIDE(sd->dnd_current.x, sd->dnd_current.y,
                  sd->drop_icon->x - sd->drop_icon->sd->pos.x,
                  sd->drop_icon->y - sd->drop_icon->sd->pos.y,
                  sd->drop_icon->w, sd->drop_icon->h)))
     _e_fm2_dnd_drop_hide(sd->obj);
/*
 * FIXME: this is slow and doesn't do much, need a better way...
   if (!sd->drop_icon)
     _e_fm2_cb_dnd_move(sd, NULL, NULL);
 */
#undef EFM_MAX_PIXEL_DRAG
   return EINA_TRUE;
}

static void
_e_fm2_cb_dnd_enter(void *data, const char *type, void *event)
{
   E_Event_Dnd_Enter *ev;
   E_Fm2_Smart_Data *sd = data;

   if (!_e_fm2_dnd_type_implemented(type)) return;
   ev = (E_Event_Dnd_Enter *)event;
   e_drop_handler_action_set(ev->action);
   if (!sd->dnd_scroller)
     sd->dnd_scroller = ecore_animator_add((Ecore_Task_Cb)_e_fm2_cb_dnd_scroller, sd);
   evas_object_smart_callback_call(sd->obj, "dnd_enter", NULL);
}

static Eina_Bool
_e_fm2_cb_dnd_move_helper(E_Fm2_Smart_Data *sd, E_Fm2_Icon *ic, int dx, int dy)
{
   if (!E_INSIDE(dx, dy, ic->x - ic->sd->pos.x, ic->y - ic->sd->pos.y, ic->w, ic->h)) return EINA_FALSE;
   if (ic->drag.dnd) return EINA_FALSE;
   if (!S_ISDIR(ic->info.statinfo.st_mode))
     {
        if (ic->sd->drop_icon)
          _e_fm2_dnd_drop_hide(sd->obj);
        _e_fm2_dnd_drop_all_show(sd->obj);
        return EINA_TRUE;
     }
   /* if list view */
   if (_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_LIST)
     {
        /* if there is a .order file - we can re-order files */
        if (ic->sd->order_file)
          {
             /* if dir: */
             if (!ic->sd->config->view.no_subdir_drop)
               {
                  /* if bottom 25% or top 25% then insert between prev or next */
                  /* if in middle 50% then put in dir */
                  if (dy <= (ic->y - ic->sd->pos.y + (ic->h / 4)))
                    _e_fm2_dnd_drop_show(ic, 0);
                  else if (dy > (ic->y - ic->sd->pos.y + ((ic->h * 3) / 4)))
                    _e_fm2_dnd_drop_show(ic, 1);
                  else
                    _e_fm2_dnd_drop_show(ic, -1);
               }
             else
               {
                  /* if top 50% or bottom 50% then insert between prev or next */
                  if (dy <= (ic->y - ic->sd->pos.y + (ic->h / 2)))
                    _e_fm2_dnd_drop_show(ic, 0);
                  else
                    _e_fm2_dnd_drop_show(ic, 1);
               }
          }
        /* if we are over subdirs or files */
        else
          {
             /*
              * if it's over a dir - hilight as it will be dropped info
              * FIXME: should there be a separate highlighting function for files?
              * */
             if (!ic->sd->config->view.no_subdir_drop)
               _e_fm2_dnd_drop_show(ic, -1);
          }
     }
   else
     {
        /* if it's over a dir - hilight as it will be dropped in */
        if (!ic->sd->config->view.no_subdir_drop)
          _e_fm2_dnd_drop_show(ic, -1);
     }
   return EINA_TRUE;
}

static void
_e_fm2_cb_dnd_move(void *data, const char *type, void *event)
{
   E_Fm2_Smart_Data *sd;
   E_Event_Dnd_Move *ev;
   E_Fm2_Icon *ic;
   Eina_List *l;
   int dx, dy;

   sd = data;
   /* also called from the scroll animator with only the first param */
   if (type && (!_e_fm2_dnd_type_implemented(type))) return;
   ev = (E_Event_Dnd_Move *)event;
   if (ev)
     {
        dx = ev->x - sd->x;
        dy = ev->y - sd->y;
        sd->dnd_current.x = dx, sd->dnd_current.y = dy;
        e_drop_handler_action_set(ev->action);
     }
   else
     dx = sd->dnd_current.x, dy = sd->dnd_current.y;

   EINA_LIST_FOREACH(sd->icons, l, ic)
     if (_e_fm2_cb_dnd_move_helper(sd, ic, dx, dy)) return;
   /* FIXME: not over icon - is it within the fm view? if so drop there */
   if (E_INSIDE(dx, dy, 0, 0, sd->w, sd->h))
     {
#if 0 //this is broken since we don't allow custom list sorting
      /* if listview - it is now after last file */
        if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST)
          {
             /* if there is a .order file - we can re-order files */
             if (sd->order_file)
               {
                  ic = eina_list_last_data_get(sd->icons);
                  if (ic)
                    {
                       if (!ic->drag.dnd)
                         _e_fm2_dnd_drop_show(ic, 1);
                       else
                         _e_fm2_dnd_drop_all_show(sd->obj);
                    }
                  else
                    _e_fm2_dnd_drop_all_show(sd->obj);
               }
             else
               _e_fm2_dnd_drop_all_show(sd->obj);
          }
        else
#endif
        _e_fm2_dnd_drop_all_show(sd->obj);
        return;
     }
   /* outside fm view */
   _e_fm2_dnd_drop_hide(sd->obj);
}

static void
_e_fm2_cb_dnd_leave(void *data, const char *type, void *event EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (!_e_fm2_dnd_type_implemented(type)) return;
   _e_fm2_dnd_drop_hide(sd->obj);
   _e_fm2_dnd_drop_all_hide(sd->obj);
   if (sd->dnd_scroller) ecore_animator_del(sd->dnd_scroller);
   sd->dnd_scroller = NULL;
   sd->dnd_current.x = sd->dnd_current.y = 0;
   evas_object_smart_callback_call(sd->obj, "dnd_leave", NULL);
   /* NOTE: DO NOT PERFORM ANY OPERATIONS USING sd AT THIS POINT!
    * things use this callback to delete the efm object
    */
}

static void
_e_fm_file_reorder(const char *file, const char *dst, const char *relative, int after)
{
   unsigned int length = strlen(file) + 1 + strlen(dst) + 1 + strlen(relative) + 1 + sizeof(after);
   char *data, *p;

   data = alloca(length);
   if (!data) return;

   p = data;

#define P(s) memcpy(p, s, strlen(s) + 1); p += strlen(s) + 1
   P(file);
   P(dst);
   P(relative);
#undef P

   memcpy(p, &after, sizeof(int));

   _e_fm_client_send_new(E_FM_OP_REORDER, data, length);
}

static void
_e_fm_icon_save_position(const char *file, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   E_Fm2_Custom_File *cf, new;

   if (!file) return;

   cf = e_fm2_custom_file_get(file);
   if (!cf)
     {
        memset(&new, 0, sizeof(E_Fm2_Custom_File));
        cf = &new;
     }

   cf->geom.x = x;
   cf->geom.y = y;
   cf->geom.res_w = w;
   cf->geom.res_h = h;

   cf->geom.valid = 1;
   e_fm2_custom_file_set(file, cf);
   e_fm2_custom_file_flush();
}

static Eina_Bool
_e_fm_drop_menu_queue(Evas_Object *e_fm, void *args, int op)
{
   E_Volume *vol;
   E_Fm2_Device_Mount_Op *mop = args;

   vol = evas_object_data_del(e_fm, "dnd_queue");
   if (!vol) return EINA_FALSE;
   if (!vol->mounted)
     {
        /* this should get picked up by the post-mount callback */
#ifndef HAVE_WAYLAND_ONLY
        switch (op)
          {
           case 0: //copy
             mop->action = ECORE_X_ATOM_XDND_ACTION_COPY;
             break;

           case 1: //move
             mop->action = ECORE_X_ATOM_XDND_ACTION_MOVE;
             break;

           case 2: //link
             mop->action = ECORE_X_ATOM_XDND_ACTION_LINK;
             break;
          }
#endif
        return EINA_TRUE;
     }
   switch (op)
     {
      case 0: //copy
        e_fm2_client_file_copy(e_fm, mop->args);
        break;

      case 1: //move
        e_fm2_client_file_move(e_fm, mop->args);
        break;

      case 2: //link
        e_fm2_client_file_symlink(e_fm, mop->args);
        break;
     }
   vol->mount_ops = eina_inlist_remove(vol->mount_ops, EINA_INLIST_GET(mop));
   free(mop->args);
   free(mop);
   return EINA_TRUE;
}

static void
_e_fm_drop_menu_copy_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   char *args;

   args = evas_object_data_del(data, "drop_menu_data");
   if (_e_fm_drop_menu_queue(data, args, 0)) return;
   e_fm2_client_file_copy(data, args);
   free(args);
}

static void
_e_fm_drop_menu_move_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   char *args;

   args = evas_object_data_del(data, "drop_menu_data");
   if (_e_fm_drop_menu_queue(data, args, 1)) return;
   e_fm2_client_file_move(data, args);
   free(args);
}

static void
_e_fm_drop_menu_symlink_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   char *args;

   args = evas_object_data_del(data, "drop_menu_data");
   if (_e_fm_drop_menu_queue(data, args, 2)) return;
   e_fm2_client_file_symlink(data, args);
   free(args);
}

static void
_e_fm_drop_menu_abort_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   free(evas_object_data_del(data, "drop_menu_data"));
}

static void
_e_fm_drop_menu_free(void *data)
{
   Evas_Object *e_fm;

   e_fm = e_object_data_get(data);
   if (!e_fm) return;
   evas_object_data_del(e_fm, "drop_menu_data");
}

static void
_e_fm2_cb_dnd_selection_notify_post_mount_fail(E_Volume *vol)
{
   E_Fm2_Device_Mount_Op *mop;

   while (vol->mount_ops)
     {
        E_Fm2_Icon *ic;

        mop = (E_Fm2_Device_Mount_Op *)vol->mount_ops;
        ic = mop->ic;
        vol->mount_ops = eina_inlist_remove(vol->mount_ops, EINA_INLIST_GET(mop));
        /* FIXME: this can be made more clear */
        e_util_dialog_show(_("Error"), _("The recent DND operation requested for '%s' has failed."), vol->label ? : vol->udi);
        evas_object_data_del(ic->sd->obj, "dnd_queue");
        free(mop->args);
        ic->sd->mount_ops = eina_list_remove(ic->sd->mount_ops, mop);
        free(mop);
     }
}

static void
_e_fm2_cb_dnd_selection_notify_post_mount(E_Volume *vol)
{
   E_Fm2_Device_Mount_Op *mop = NULL;
   const char *mp;
   Eina_Inlist *l;

   mp = e_fm2_device_volume_mountpoint_get(vol);
   EINA_INLIST_FOREACH_SAFE(vol->mount_ops, l, mop)
     {
        E_Fm2_Icon *ic = mop->ic;

        if (mp)
          {
             mop->args = e_util_string_append_quoted(mop->args, &mop->size, &mop->length, mp);
#ifndef HAVE_WAYLAND_ONLY
             if (mop->action == ECORE_X_ATOM_XDND_ACTION_ASK)
               continue;
             else if (mop->action == ECORE_X_ATOM_XDND_ACTION_MOVE)
               e_fm2_client_file_move(ic->sd->obj, mop->args);
             else if (mop->action == ECORE_X_ATOM_XDND_ACTION_COPY)
               e_fm2_client_file_copy(ic->sd->obj, mop->args);
             else if (mop->action == ECORE_X_ATOM_XDND_ACTION_LINK)
               e_fm2_client_file_symlink(ic->sd->obj, mop->args);
#endif
          }
        else
          e_util_dialog_show(_("Error"), _("The recent DND operation requested for '%s' has failed."), vol->label ? : vol->udi);
        free(mop->args);
        vol->mount_ops = eina_inlist_remove(vol->mount_ops, EINA_INLIST_GET(mop));
        ic->sd->mount_ops = eina_list_remove(ic->sd->mount_ops, mop);
        free(mop);
     }
   eina_stringshare_del(mp);
}

static void
_e_fm2_cb_dnd_selection_notify_post_umount(E_Volume *vol)
{
   E_Fm2_Device_Mount_Op *mop;

   EINA_INLIST_FOREACH(vol->mount_ops, mop)
     {
        E_Fm2_Icon *ic = mop->ic;

        if (!ic) continue;
        if (ic->mount_timer) ecore_timer_del(ic->mount_timer);
        ic->mount_timer = NULL;
        ic->mount = NULL;
        ic->sd->mount_ops = eina_list_remove(ic->sd->mount_ops, mop);
     }
}

static Eina_Bool
_e_fm2_cb_dnd_selection_notify_post_mount_timer(E_Fm2_Icon *ic)
{
   e_fm2_device_unmount(ic->mount);
   ic->mount = NULL;
   ic->mount_timer = NULL;
   return EINA_FALSE;
}

static void
_e_fm2_cb_dnd_selection_notify(void *data, const char *type, void *event)
{
   E_Fm2_Smart_Data *sd = data;
   E_Event_Dnd_Drop *ev = event;
   E_Fm2_Icon *ic = NULL;
   Eina_List *fsel, *l, *ll, *il, *isel = NULL;
   char buf[PATH_MAX];
   const char *fp;
   Evas_Coord ox, oy;
   Evas_Object *obj;
   int adjust_icons = 0;
   char dirpath[PATH_MAX];
   char *args = NULL;
   size_t size = 0;
   size_t length = 0;
   Eina_Bool lnk = EINA_FALSE, memerr = EINA_FALSE, mnt = EINA_FALSE;
   E_Fm2_Device_Mount_Op *mop = NULL;

   if (!_e_fm2_dnd_type_implemented(type)) return;
   if (type == _e_fm2_mime_xmozurl)
     {
        const char **name, *s;
        Efreet_Desktop *desktop;
        E_Dnd_X_Moz_Url *moz = ev->data;
        unsigned int x = 0;

        EINA_INARRAY_FOREACH(moz->link_names, name)
          {
             int p;

             s = *name;
             for (p = 0; p < 7; p++)
               {
                  Eina_Bool done = EINA_FALSE;

                  if (!s[p]) break;
                  if ((s[p] == ':') && (s[p + 1] == '/'))
                    {
                       s = ecore_file_file_get(s);
                       done = EINA_TRUE;
                    }
                  if (done) break;
               }
             if (!s[0]) s = ecore_file_file_get(*name);
             if (!s)
               {
                  s = *(char**)eina_inarray_nth(moz->links, x);
                  s = ecore_file_file_get(s);
               }
             /* FIXME: should this filename be sanitized somehow? */
             if (sd->drop_icon && sd->drop_after == -1)
               {
                  //into drop_icon
                  if (S_ISDIR(sd->drop_icon->info.statinfo.st_mode))
                    {
                       if (sd->drop_icon->info.link)
                         snprintf(dirpath, sizeof(dirpath), "%s/Link to %s.desktop", sd->drop_icon->info.link, s);
                       else
                         snprintf(dirpath, sizeof(dirpath), "%s/%s/Link to %s.desktop", sd->realpath, sd->drop_icon->info.file, s);
                    }
                  else
                    snprintf(buf, sizeof(buf), "%s/Link to %s.desktop", sd->realpath, s);
               }
             else
               snprintf(buf, sizeof(buf), "%s/Link to %s.desktop", sd->realpath, s);
             if (ecore_file_exists(buf))
               {
                  e_util_dialog_show(_("Error"), _("A link to the requested URL already exists!"));
                  continue;
               }
             desktop = efreet_desktop_empty_new(buf);
             desktop->type = EFREET_DESKTOP_TYPE_LINK;
             snprintf(buf, sizeof(buf), "Link to %s", *name);
             desktop->name = strdup(buf);
             desktop->icon = strdup("text-html");
             desktop->url = strdup(*(char**)eina_inarray_nth(moz->links, x));
             efreet_desktop_save(desktop);
             efreet_desktop_free(desktop);
             x++;
          }
        return;
     }
   fsel = e_fm2_uri_path_list_get(ev->data);
   fp = eina_list_data_get(fsel);
   if (fp && sd->realpath && ((sd->drop_all) || (!sd->drop_icon)))
     {
        const char *f;

        /* if we're dropping into a link_drop view, we need to stop here so we
         * don't accidentally replace an original file with a link!
         */
        f = ecore_file_file_get(fp);
        if ((((f - fp - 1 == 0) && (!strcmp(sd->realpath, "/"))) ||
             ((f - fp - 1 > 0) && (!strncmp(sd->realpath, fp, f - fp - 1)))) &&
            ((size_t)(f - fp - 1) == strlen(sd->realpath)))
          {
#ifndef HAVE_WAYLAND_ONLY
             if ((e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_MOVE) || (sd->config->view.link_drop))
               {
                  lnk = EINA_TRUE;
                  if (_e_fm2_view_mode_get(sd) != E_FM2_VIEW_MODE_CUSTOM_ICONS)
                    goto end;
                  memerr = EINA_TRUE; // prevent actual file move op
               }
#endif
          }
     }
   if (!fsel)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_COPY)
          {
             /* most likely someone is trying to dnd some text to us */
             _e_fm2_new_file(sd, NULL, NULL);
             EINA_LIST_FOREACH(ev->data, l, fp)
               isel = eina_list_append(isel, strdup(fp));
             ecore_thread_global_data_add("efm_text_uri_list", isel, (Eina_Free_Cb)e_util_string_list_free, 0);
          }
#endif
        /* no files, abort! */
        return;
     }

   isel = _e_fm2_uri_selected_icon_list_get(fsel);
   ox = 0; oy = 0;
   EINA_LIST_FOREACH(isel, l, ic)
     {
        if (!ic) continue;
        if (ic->drag.src)
          {
             ox = ic->x;
             oy = ic->y;
             break;
          }
     }

   /* note - logic.
    * if drop file prefix path matches extra_file_source then it can be
    * and indirect link - dont MOVE the file just add filename to list.
    * if not literally move the file in. if move can't work - try a copy.
    * on a literal move find any fm views for the dir of the dropped file
    * and refresh those, as well as refresh current target fm dir
    */
   if (sd->drop_all) /* drop arbitrarily into the dir */
     {
        Evas_Coord x, y;

        /* move file into this fm dir */
        for (ll = fsel, il = isel; ll; ll = eina_list_next(ll), il = eina_list_next(il))
          {
             ic = eina_list_data_get(il);
             fp = eina_list_data_get(ll);
             if (!fp) continue;

             if ((ic) && (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_CUSTOM_ICONS))
               {
                  /* dnd doesn't tell me all the co-ords of the icons being dragged so i can't place them accurately.
                   * need to fix this. ev->data probably needs to become more compelx than a list of url's
                   */

                  x = ev->x + (ic->x - ox) - ic->drag.x + sd->pos.x - sd->x;
                  y = ev->y + (ic->y - oy) - ic->drag.y + sd->pos.y - sd->y;

                  if (x < 0) x = 0;
                  if (y < 0) y = 0;

                  if (sd->config->view.fit_custom_pos)
                    {
                       if ((x + ic->w) > sd->w) x = (sd->w - ic->w);
                       if ((y + ic->h) > sd->h) y = (sd->h - ic->h);
                    }

                  if (ic->sd == sd)
                    {
                       ic->x = x;
                       ic->y = y;
                       ic->saved_pos = EINA_TRUE;
                       adjust_icons = 1;
                    }

                  snprintf(buf, sizeof(buf), "%s/%s",
                           sd->realpath, ecore_file_file_get(fp));
                  _e_fm_icon_save_position(buf, x, y, sd->w, sd->h);
               }

             if (!memerr)
               {
                  args = e_util_string_append_quoted(args, &size, &length, fp);
                  if (!args) memerr = EINA_TRUE;
                  else
                    {
                       args = e_util_string_append_char(args, &size, &length, ' ');
                       if (!args) memerr = EINA_TRUE;
                       else if (ic) ic->drag.hidden = EINA_TRUE;
                    }
               }
             eina_stringshare_del(fp);
          }
        if (adjust_icons)
          {
             sd->max.w = 0;
             sd->max.h = 0;
             EINA_LIST_FOREACH(sd->icons, l, ic)
               {
                  if ((ic->x + ic->w) > sd->max.w) sd->max.w = ic->x + ic->w;
                  if ((ic->y + ic->h) > sd->max.h) sd->max.h = ic->y + ic->h;
               }
             _e_fm2_obj_icons_place(sd);
             evas_object_smart_callback_call(sd->obj, "changed", NULL);
          }

        if (!memerr)
          args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
     }
   else if (sd->drop_icon) /* into or before/after an icon */
     {
        if (sd->drop_after == -1) /* put into subdir/file in icon */
          {
             /* move file into dir that this icon is for */
             for (ll = fsel, il = isel; ll && il; ll = eina_list_next(ll), il = eina_list_next(il))
               {
                  fp = eina_list_data_get(ll);
                  if (!fp) continue;
                  if (!memerr)
                    {
                       args = e_util_string_append_quoted(args, &size, &length, fp);
                       if (!args) memerr = EINA_TRUE;
                       else
                         {
                            args = e_util_string_append_char(args, &size, &length, ' ');
                            if (!args) memerr = EINA_TRUE;
                            else ic->drag.hidden = EINA_TRUE;
                         }
                    }
                  eina_stringshare_del(fp);
               }

             if (!memerr)
               {
                  while (sd->drop_icon->info.removable)
                    {
                       /* we're dropping onto a device
                        * cross your fingers and hope for good luck
                        */
                       E_Volume *vol;
                       const char *mp;

                       vol = e_fm2_device_volume_find_fast(sd->drop_icon->info.link);
                       if (!vol) break;
                       if (vol->mounted)
                         {
                            mp = e_fm2_device_volume_mountpoint_get(vol);
                            if (mp)
                              {
                                 /* luuuuuuckkyyyyyyyyy */
                                 args = e_util_string_append_quoted(args, &size, &length, mp);
                                 if (!args) memerr = EINA_TRUE;
                                 eina_stringshare_del(mp);
                                 mnt = EINA_TRUE;
                                 break;
                              }
                         }
                       else if (!sd->drop_icon->mount)
                         sd->drop_icon->mount = e_fm2_device_mount(vol, (Ecore_Cb)_e_fm2_cb_dnd_selection_notify_post_mount,
                                                                   (Ecore_Cb)_e_fm2_cb_dnd_selection_notify_post_mount_fail, (Ecore_Cb)_e_fm2_cb_dnd_selection_notify_post_umount,
                                                                   NULL, vol);

                       if (sd->drop_icon->mount_timer) ecore_timer_loop_reset(sd->drop_icon->mount_timer);
                       else sd->drop_icon->mount_timer = ecore_timer_loop_add(15., (Ecore_Task_Cb)_e_fm2_cb_dnd_selection_notify_post_mount_timer, sd->drop_icon);
#ifndef HAVE_WAYLAND_ONLY
                       if ((e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_ASK) ||
                           ((sd->config->view.link_drop) || (!sd->drop_icon)))
                         /* this here's some buuuullshit */
                         evas_object_data_set(sd->obj, "dnd_queue", vol);
                       else if (e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_MOVE)
                         /* set copy if we're over a device */
                         e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_COPY);
#endif
                       mop = e_fm2_device_mount_op_add(sd->drop_icon->mount, args, size, length);
                       mop->ic = sd->drop_icon;
                       mop->mnt = sd->drop_icon->mount;
                       sd->mount_ops = eina_list_append(sd->mount_ops, mop);
                       /*
                        * set lnk here to prevent deleting the show timer
                        */
                       mnt = lnk = EINA_TRUE;
                       break;
                    }
                  if (!mnt)
                    {
                       if (S_ISDIR(sd->drop_icon->info.statinfo.st_mode))
                         {
                            if (sd->drop_icon->info.link)
                              snprintf(dirpath, sizeof(dirpath), "%s", sd->drop_icon->info.link);
                            else
                              snprintf(dirpath, sizeof(dirpath), "%s/%s", sd->realpath, sd->drop_icon->info.file);
                         }
                       else
                         snprintf(dirpath, sizeof(dirpath), "%s", sd->realpath);

                       args = e_util_string_append_quoted(args, &size, &length, dirpath);
                       if (!args) memerr = EINA_TRUE;
                    }
               }
          }
        else if (sd->realpath)
          {
             if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST && sd->order_file) /* list */
               {
                  for (ll = fsel, il = isel; ll && il; ll = eina_list_next(ll), il = eina_list_next(il))
                    {
                       fp = eina_list_data_get(ll);
                       if (!fp) continue;
                       if (!memerr)
                         {
                            snprintf(buf, sizeof(buf), "%s/%s", sd->realpath, ecore_file_file_get(fp));
                            args = e_util_string_append_quoted(args, &size, &length, fp);
                            if (!args) memerr = EINA_TRUE;
                            else
                              {
                                 args = e_util_string_append_char(args, &size, &length, ' ');
                                 if (!args) memerr = EINA_TRUE;
                                 else ic->drag.hidden = EINA_TRUE;
                              }
                         }

                       _e_fm_file_reorder(ecore_file_file_get(fp), sd->realpath, sd->drop_icon->info.file, sd->drop_after);

                       eina_stringshare_del(fp);
                    }

                  if (!memerr)
                    {
                       args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
                       if (!args) memerr = EINA_TRUE;
                    }
               }
             else
               {
                  for (ll = fsel, il = isel; ll && il; ll = eina_list_next(ll), il = eina_list_next(il))
                    {
                       fp = eina_list_data_get(ll);
                       if (!fp) continue;

                       if (!memerr)
                         {
                            args = e_util_string_append_quoted(args, &size, &length, fp);
                            if (!args) memerr = EINA_TRUE;
                            else
                              {
                                 args = e_util_string_append_char(args, &size, &length, ' ');
                                 if (!args) memerr = EINA_TRUE;
                                 else ic->drag.hidden = EINA_TRUE;
                              }
                         }
                       eina_stringshare_del(fp);
                    }
                  if (!memerr)
                    {
                       args = e_util_string_append_quoted(args, &size, &length, sd->realpath);
                       if (!args) memerr = EINA_TRUE;
                    }
               }
          }
     }

   if (args)
     {
        Eina_Bool do_lnk = EINA_FALSE, do_move = EINA_FALSE, do_copy = EINA_FALSE;

#ifndef HAVE_WAYLAND_ONLY
        if (e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_COPY)
          {
             lnk = EINA_TRUE;
             if (sd->config->view.link_drop && (!sd->drop_icon))
               do_lnk = EINA_TRUE;
             else
               do_copy = EINA_TRUE;
          }
        else if (e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_MOVE)
          {
             if (sd->config->view.link_drop)
               lnk = do_lnk = EINA_TRUE;
             else
               do_move = EINA_TRUE;
          }
        else if (e_drop_handler_action_get() == ECORE_X_ATOM_XDND_ACTION_ASK)
          {
             if (sd->config->view.link_drop && (!sd->drop_icon))
               do_lnk = lnk = EINA_TRUE;
          }
        if (mnt && mop)
          mop->action = (do_lnk * ECORE_X_ATOM_XDND_ACTION_LINK) +
            (do_copy * ECORE_X_ATOM_XDND_ACTION_COPY) + (do_move * ECORE_X_ATOM_XDND_ACTION_MOVE) +
            (((!do_copy) && (!do_move) && (!do_lnk)) * ECORE_X_ATOM_XDND_ACTION_ASK);
        else if (do_lnk)
          e_fm2_client_file_symlink(sd->obj, args);
        else if (do_copy)
          e_fm2_client_file_copy(sd->obj, args);
        else if (do_move)
          e_fm2_client_file_move(sd->obj, args);
#endif
        if ((!do_lnk) && (!do_copy) && (!do_move))
          {
             e_fm2_drop_menu(sd->obj, args);
             if (mnt && mop)
               evas_object_data_set(sd->obj, "drop_menu_data", mop);
             E_LIST_FOREACH(isel, _e_fm2_cb_drag_finished_show);
          }
        if (((!mnt) || (!mop)) && (do_lnk || do_copy || do_move))
          free(args);
     }
end:
   _e_fm2_dnd_drop_hide(sd->obj);
   _e_fm2_dnd_drop_all_hide(sd->obj);
   _e_fm2_list_walking++;
   EINA_LIST_FOREACH(_e_fm2_list, l, obj)
     {
        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, obj))) continue;
        _e_fm2_dnd_finish(obj, 0);
     }
   _e_fm2_list_walking--;
   if (_e_fm2_list_walking == 0)
     {
        EINA_LIST_FREE(_e_fm2_list_remove, obj)
          {
             _e_fm2_list = eina_list_remove(_e_fm2_list, obj);
          }
     }
   eina_list_free(fsel);
   EINA_LIST_FREE(isel, ic)
     if (ic->drag.dnd_end_timer && (!lnk))
       {
          ecore_timer_del(ic->drag.dnd_end_timer);
          ic->drag.dnd_end_timer = NULL;
       }
}

static void
_e_fm2_mouse_2_handler(E_Fm2_Icon *ic, void *evas_event)
{
   int multi_sel = 1;
   Eina_List *l;
   E_Fm2_Icon *ic2;

   if (!evas_event) return;

   if (ic->sd->config->selection.single)
     multi_sel = 0;

   EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
     ic2->last_selected = 0;
   if (ic->selected)
     _e_fm2_icon_deselect(ic);
   else
     {
        if (!multi_sel)
          {
             ic2 = eina_list_data_get(ic->sd->selected_icons);
             if (ic2) _e_fm2_icon_deselect(ic2);
          }
        _e_fm2_icon_select(ic);
        _e_fm2_icon_make_visible(ic);
        ic->last_selected = EINA_TRUE;
     }
   evas_object_smart_callback_call(ic->sd->obj, "selection_change", NULL);
}

/* FIXME: prototype */
static int
_e_fm2_mouse_1_handler(E_Fm2_Icon *ic, int up, void *evas_event)
{
   Evas_Event_Mouse_Down *ed = NULL;
   Evas_Event_Mouse_Up *eu = NULL;
   Evas_Modifier *modifiers;
   int multi_sel = 0, range_sel = 0, sel_change = 0;
   static unsigned int down_timestamp = 0;

   if (!evas_event) return 0;

   if (!up)
     {
        ed = evas_event;
        modifiers = ed->modifiers;
     }
   else
     {
        eu = evas_event;
        modifiers = eu->modifiers;
     }
   if (ed && ic->sd->config->view.single_click_delay)
     down_timestamp = ed->timestamp;

   if (!ic->sd->config->selection.windows_modifiers)
     {
        if (evas_key_modifier_is_set(modifiers, "Shift"))
          range_sel = 1;
        else if (evas_key_modifier_is_set(modifiers, "Control"))
          multi_sel = 1;
     }
   else
     {
        if (evas_key_modifier_is_set(modifiers, "Control"))
          range_sel = 1;
        else if (evas_key_modifier_is_set(modifiers, "Shift"))
          multi_sel = 1;
     }
   if (ic->sd->config->selection.single)
     {
        multi_sel = 0;
        range_sel = 0;
     }

   /*
    * On mouse up, check if we want to do in place open
    */
   if ((eu) &&
       (!multi_sel) &&
       (!range_sel) &&
       (ic->sd->config->view.single_click) &&
       ((eu->timestamp - down_timestamp) > ic->sd->config->view.single_click_delay))
     {
        if (_e_fm2_inplace_open(ic) == 1) return 1;
     }

   if (range_sel)
     {
        if (!up) _e_fm2_icon_range_select(ic);
     }
   else if ((!multi_sel) && ((up) || ((!up) && (!ic->selected))))
     {
        const Eina_List *l;
        E_Fm2_Icon *ic2;
        /* desel others */
        EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
          {
             if (ic2 != ic)
               {
                  if (ic2->selected)
                    {
                       _e_fm2_icon_deselect(ic2);
                       sel_change = 1;
                    }
               }
          }
     }
   else
     {
        if (!up)
          {
             const Eina_List *l;
             E_Fm2_Icon *ic2;
             EINA_LIST_FOREACH(ic->sd->icons, l, ic2)
               ic2->last_selected = 0;
          }
     }
   if ((multi_sel) && (ic->selected))
     {
        if ((up) && (!ic->drag.dnd) && (!ic->down_sel))
          {
             sel_change = 1;
             _e_fm2_icon_deselect(ic);
          }
     }
   else
     {
        if (!up)
          {
             if (!ic->selected) sel_change = EINA_TRUE;
             _e_fm2_icon_select(ic);
             _e_fm2_icon_make_visible(ic);
             ic->down_sel = EINA_TRUE;
             ic->last_selected = EINA_TRUE;
          }
     }
   if (sel_change)
     evas_object_smart_callback_call(ic->sd->obj, "selection_change", NULL);
   if (ic->sd->config->view.single_click && (!range_sel) && (!multi_sel))
     {
        if (eu && (eu->timestamp - down_timestamp) > ic->sd->config->view.single_click_delay)
          {
             int icon_pos_x = ic->x + ic->sd->x - ic->sd->pos.x;
             int icon_pos_y = ic->y + ic->sd->y - ic->sd->pos.y;

             if (eu->output.x >= icon_pos_x && eu->output.x <= (icon_pos_x + ic->w) &&
                 eu->output.y >= icon_pos_y && eu->output.y <= (icon_pos_y + ic->h))
               evas_object_smart_callback_call(ic->sd->obj, "selected", NULL);
          }
     }
   return 0;
}

static void
_e_fm2_cb_icon_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Fm2_Icon *ic;

   ic = data;
   ev = event_info;

   if (ic->entry_widget)
     return;

   _e_fm2_typebuf_hide(ic->sd->obj);
   if ((ev->button == 1) && (ev->flags & EVAS_BUTTON_DOUBLE_CLICK))
     {
        /* if its a directory && open dirs in-place is set then change the dir
         * to be the dir + file */
        if (ic->sd->config->view.single_click) return;
        if (_e_fm2_inplace_open(ic) == 0)
          evas_object_smart_callback_call(ic->sd->obj, "selected", NULL);
        /* if its in file selector mode then signal that a selection has
         * taken place and dont do anything more */

        /* do the below per selected file */
        /* if its a directory and open dirs in-place is not set, then
         * signal owner that a new dir should be opened */
        /* if its a normal file - do what the mime type says to do with
         * that file type */
     }
   else if (ev->button < 3)
     {
        if (ev->button == 1)
          {
             if ((ic->sd->eobj || ic->sd->win))
               {
                  ic->drag.x = ev->output.x - ic->x - ic->sd->x + ic->sd->pos.x;
                  ic->drag.y = ev->output.y - ic->y - ic->sd->y + ic->sd->pos.y;
                  ic->drag.start = EINA_TRUE;
                  ic->drag.dnd = EINA_FALSE;
                  ic->drag.src = EINA_TRUE;
               }

             _e_fm2_mouse_1_handler(ic, 0, ev);
          }
        else
          _e_fm2_mouse_2_handler(ic, ev);
     }
   else if (ev->button == 3)
     {
        if (!ic->selected)
          {
             if (_e_fm2_mouse_1_handler(ic, 0, ev)) return;
          }
        _e_fm2_icon_menu(ic, ic->sd->obj, ev->timestamp);
     }
}

static void
_e_fm2_cb_icon_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Up *ev = event_info;
   E_Fm2_Icon *ic = data;

   if (ic->sd->selecting) return;
   edje_object_message_signal_process(ic->obj);
   edje_object_message_signal_process(ic->obj);

   _e_fm2_typebuf_hide(ic->sd->obj);
   if ((ev->button == 1) && (!ic->drag.dnd))
     {
        if (!(ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD))
          {
             if (!ic->entry_widget)
               {
                  if (_e_fm2_mouse_1_handler(ic, 1, ev)) return;
               }
          }
        ic->drag.start = EINA_FALSE;
        ic->drag.dnd = EINA_FALSE;
        ic->drag.src = EINA_FALSE;
        ic->down_sel = EINA_FALSE;
     }
}

static Eina_Bool
_e_fm2_cb_drag_finished_show(E_Fm2_Icon *ic)
{
   ic->drag.dnd = ic->drag.src = EINA_FALSE;
   if (ic->obj) evas_object_show(ic->obj);
   if (ic->obj_icon) evas_object_show(ic->obj_icon);
   ic->drag.dnd_end_timer = NULL;
   return EINA_FALSE;
}

static void
_e_fm2_cb_drag_finished(E_Drag *drag, int dropped EINA_UNUSED)
{
   E_Fm2_Uri *uri;
   const char *p;
   char buf[PATH_MAX * 3 + 7];
   Eina_List *fms;
   Evas_Object *fm;
   int i;

   memset(buf, 0, sizeof(buf));
   for (p = drag->data, i = 0; p && *p != '\0'; p++, i++)
     {
        if (*p == '\r')
          {
             p++;
             i = -1;
             uri = _e_fm2_uri_parse(buf);
             memset(buf, 0, sizeof(buf));
             if (!uri) continue;

             fms = _e_fm2_file_fm2_find(uri->path);
             if (fms)
               {
                  const char *file;
                  E_Fm2_Icon *ic;

                  file = ecore_file_file_get(uri->path);
                  if (file)
                    {
                       EINA_LIST_FREE(fms, fm)
                         {
                            ic = _e_fm2_icon_find(fm, file);
                            if (ic && ic->drag.dnd)
                              {
                                 ic->drag.dnd = EINA_FALSE;
                                 if (ic->sd->dnd_scroller) ecore_animator_del(ic->sd->dnd_scroller);
                                 ic->sd->dnd_scroller = NULL;
                                 if (ic->drag.dnd_end_timer) ecore_timer_loop_reset(ic->drag.dnd_end_timer);
                                 else ic->drag.dnd_end_timer = ecore_timer_loop_add(0.2, (Ecore_Task_Cb)_e_fm2_cb_drag_finished_show, ic);
                                 /* NOTE:
                                  * do not touch ic after this callback; it's possible that it may have been deleted
                                  */
                                 evas_object_smart_callback_call(ic->sd->obj, "dnd_end", &ic->info);
                                 break;
                              }
                         }
                       eina_list_free(fms);
                    }
               }

             if (uri->hostname) eina_stringshare_del(uri->hostname);
             eina_stringshare_del(uri->path);
             E_FREE(uri);
          }
        else
          buf[i] = *p;
     }
   free(drag->data);
}

static void
_e_fm_drag_key_down_cb(E_Drag *drag, Ecore_Event_Key *e)
{
#ifndef HAVE_WAYLAND_ONLY
   if (!strncmp(e->keyname, "Alt", 3))
     {
        ecore_x_dnd_source_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
        e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
        edje_object_signal_emit(drag->object, "e,state,ask", "e");
     }
   else if (!strncmp(e->key, "Shift", 5))
     {
        if (e->modifiers == ECORE_EVENT_MODIFIER_CTRL)
          {
             ecore_x_dnd_source_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
             e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
             edje_object_signal_emit(drag->object, "e,state,ask", "e");
          }
        else
          {
             ecore_x_dnd_source_action_set(ECORE_X_ATOM_XDND_ACTION_MOVE);
             e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_MOVE);
             edje_object_signal_emit(drag->object, "e,state,move", "e");
          }
     }
   else if (!strncmp(e->key, "Control", 7))
     {
        if (e->modifiers == ECORE_EVENT_MODIFIER_SHIFT)
          {
             ecore_x_dnd_source_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
             e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_ASK);
             edje_object_signal_emit(drag->object, "e,state,ask", "e");
          }
        else
          {
             ecore_x_dnd_source_action_set(ECORE_X_ATOM_XDND_ACTION_COPY);
             e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_COPY);
             edje_object_signal_emit(drag->object, "e,state,copy", "e");
          }
     }
#endif
}

static void
_e_fm_drag_key_up_cb(E_Drag *drag, Ecore_Event_Key *e)
{
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Atom act = ECORE_X_ATOM_XDND_ACTION_MOVE;
   /* Default action would be move. ;) */

   if (!strncmp(e->key, "Alt", 3))
     act = ECORE_X_ATOM_XDND_ACTION_MOVE;
   else if (!strncmp(e->key, "Shift", 5))
     act = ECORE_X_ATOM_XDND_ACTION_MOVE;
   else if (!strncmp(e->key, "Control", 7))
     act = ECORE_X_ATOM_XDND_ACTION_MOVE;

   ecore_x_dnd_source_action_set(act);
   e_drop_handler_action_set(act);
#endif

   edje_object_signal_emit(drag->object, "e,state,move", "e");
}

static void
_e_fm2_cb_icon_mouse_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_In *ev;
   E_Fm2_Icon *ic;

   ic = data;
   ev = event_info;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (e_comp_util_mouse_grabbed()) return;
   evas_object_smart_callback_call(ic->sd->obj, "icon_mouse_in", &ic->info);
}

static void
_e_fm2_cb_icon_mouse_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Out *ev;
   E_Fm2_Icon *ic;

   ic = data;
   ev = event_info;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   evas_object_smart_callback_call(ic->sd->obj, "icon_mouse_out", &ic->info);
}

static void
_e_fm2_cb_icon_mouse_move(void *data, Evas *e, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Fm2_Icon *ic;
   E_Fm2_Icon_Info *ici;

   ic = data;
   ev = event_info;

   if (ic->entry_widget) return;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (ic->sd->selecting)
     {
        /* happens when clicking precisely between icons */
        ic->drag.x = ic->drag.y = 0;
        ic->drag.start = ic->drag.dnd = ic->drag.src = EINA_FALSE;
        return;
     }
   if ((ic->drag.start) && (ic->sd->eobj || ic->sd->win))
     {
        int dx, dy;

        dx = ev->cur.output.x - (ic->drag.x + ic->x + ic->sd->x - ic->sd->pos.x);
        dy = ev->cur.output.y - (ic->drag.y + ic->y + ic->sd->y - ic->sd->pos.y);
        if (((dx * dx) + (dy * dy)) >
            (e_config->drag_resist * e_config->drag_resist))
          {
             E_Drag *d;
             Evas_Object *o = NULL, *o2 = NULL, *layout = NULL;
             const char *drag_types[] = { "text/uri-list" }, *real_path;
             char buf[PATH_MAX + 8], *p, *sel = NULL;
             Eina_Binbuf *sbuf;
             Eina_List *sl, *icons = NULL;
             size_t sel_length = 0, p_offset, p_length;
             int wx = 0, wy = 0;

             ic->sd->drag = EINA_TRUE;
             ic->drag.start = EINA_FALSE;
             real_path = e_fm2_real_path_get(ic->sd->obj);
             p_offset = eina_strlcpy(buf, real_path, sizeof(buf));
             if ((p_offset < 1) || (p_offset >= (int)sizeof(buf) - 2)) return;
             if (buf[p_offset - 1] != '/')
               {
                  buf[p_offset] = '/';
                  p_offset++;
               }
             p = buf + p_offset;
             p_length = sizeof(buf) - p_offset - 1;

             sl = e_fm2_selected_list_get(ic->sd->obj);
             if (eina_list_count(sl) > 1)
               {
                  layout = e_layout_add(e_comp->evas);
                  e_layout_freeze(layout);
                  e_layout_virtual_size_set(layout, ic->sd->w, ic->sd->h);
               }
             sbuf = eina_binbuf_new();
             EINA_LIST_FREE(sl, ici)
               {
                  const char *s;
                  size_t s_len;

                  if (eina_strlcpy(p, ici->file, p_length) >= p_length)
                    continue;
                  s = _e_fm2_uri_escape(buf);
                  if (!s) continue;
                  s_len = strlen(s);
                  eina_binbuf_append_length(sbuf, (void*)s, s_len);
                  eina_binbuf_append_length(sbuf, (void*)"\r\n", 2);
                  eina_stringshare_del(s);

                  ici->ic->drag.dnd = EINA_TRUE;
                  if (ici->ic->obj) evas_object_hide(ici->ic->obj);
                  if (ici->ic->obj_icon) evas_object_hide(ici->ic->obj_icon);

                  o = edje_object_add(e_comp->evas);
                  if (_e_fm2_view_mode_get(ici->ic->sd) == E_FM2_VIEW_MODE_LIST)
                    {
                       if (ici->ic->sd->config->icon.fixed.w)
                         {
                            if (ici->ic->odd)
                              _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                           "base/theme/widgets",
                                                           "list_odd/fixed");
                            else
                              _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                           "base/theme/widgets",
                                                           "list/fixed");
                         }
                       else
                         {
                            if (ici->ic->odd)
                              _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                           "base/theme/widgets",
                                                           "list_odd/variable");
                            else
                              _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                           "base/theme/widgets",
                                                           "list/variable");
                         }
                    }
                  else
                    {
                       if (ici->ic->sd->config->icon.fixed.w)
                         _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                      "base/theme/fileman",
                                                      "icon/fixed");
                       else
                         _e_fm2_theme_edje_object_set(ici->ic->sd, o,
                                                      "base/theme/fileman",
                                                      "icon/variable");
                    }
                  _e_fm2_icon_label_set(ici->ic, o);
                  o2 = _e_fm2_icon_icon_direct_set(ici->ic, o,
                                                   _e_fm2_cb_icon_thumb_dnd_gen, o,
                                                   1);
                  edje_object_signal_emit(o, "e,state,selected", "e");
                  if (isedje(o2))
                    edje_object_signal_emit(o2, "e,state,selected", "e");
                  edje_object_signal_emit(o, "e,state,move", "e");
                  if (layout)
                    {
                       e_layout_pack(layout, o);
                       e_layout_child_move(o, ici->ic->x, ici->ic->y);
                       e_layout_child_resize(o, ici->ic->w, ici->ic->h);
                       evas_object_show(o);
                       icons = eina_list_append(icons, o);
                    }
                  icons = eina_list_append(icons, o2);
               }
             eina_binbuf_append_char(sbuf, 0);
             sel_length = eina_binbuf_length_get(sbuf) - 1;
             sel = (char*)eina_binbuf_string_steal(sbuf);
             eina_binbuf_free(sbuf);

             d = e_drag_new(0, 0, drag_types, 1,
                            sel, sel_length, NULL, _e_fm2_cb_drag_finished);
             d->button_mask = evas_pointer_button_down_mask_get(e);
             if (ic->sd->win)
               evas_object_geometry_get(ic->sd->win, &wx, &wy, NULL, NULL);
             if (layout)
               d->x = ic->sd->x - ic->sd->pos.x, d->y = ic->sd->y - ic->sd->pos.y;
             else
               d->x = ic->x + ic->sd->x - ic->sd->pos.x, d->y = ic->y + ic->sd->y - ic->sd->pos.y;
             d->x += wx, d->y += wy;
#ifndef HAVE_WAYLAND_ONLY
             e_drop_handler_action_set(ECORE_X_ATOM_XDND_ACTION_MOVE);
#endif
             e_drag_object_set(d, layout ?: o);
             if (layout)
               {
                  e_layout_thaw(layout);
                  e_drag_resize(d, ic->sd->w, ic->sd->h);
                  EINA_LIST_FREE(icons, o)
                    e_comp_object_util_del_list_append(d->comp_object, o);
               }
             else
               {
                  e_drag_resize(d, ic->w, ic->h);
                  e_comp_object_util_del_list_append(d->comp_object, o2);
               }
             evas_object_smart_callback_call(ic->sd->obj, "dnd_begin", &ic->info);

             e_drag_key_down_cb_set(d, _e_fm_drag_key_down_cb);
             e_drag_key_up_cb_set(d, _e_fm_drag_key_up_cb);

             e_drag_xdnd_start(d,
                               ic->drag.x + wx + ic->x + ic->sd->x - ic->sd->pos.x,
                               ic->drag.y + wy + ic->y + ic->sd->y - ic->sd->pos.y);
          }
     }
}

static void
_e_fm2_cb_icon_thumb_dnd_gen(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Evas_Object *o;
   Evas_Coord w = 0, h = 0;
   int have_alpha;

   o = data;
   e_icon_size_get(obj, &w, &h);
   if (w + h == 0) return;
   have_alpha = e_icon_alpha_get(obj);
//   if (_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_LIST)
   {
      evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_BOTH, w, h);
   }
   edje_object_part_swallow(o, "e.swallow.icon", obj);
   if (have_alpha)
     edje_object_signal_emit(o, "e,action,thumb,gen,alpha", "e");
   else
     edje_object_signal_emit(o, "e,action,thumb,gen", "e");
}

static void
_e_fm2_cb_icon_thumb_gen(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Fm2_Icon *ic;
   const char *file;

   ic = data;

   if (e_icon_file_get(obj, &file, NULL))
     {
        int w = 0, h = 0;

        if (!ic->realized)
          return;

        e_icon_size_get(obj, &w, &h);
        if (w && h)
          {
             Eina_Bool have_alpha;
             have_alpha = e_icon_alpha_get(obj);
     //	if (_e_fm2_view_mode_get(ic->sd) == E_FM2_VIEW_MODE_LIST)
             {
                evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_BOTH, w, h);
             }
             edje_object_part_swallow(ic->obj, "e.swallow.icon", obj);
             if (have_alpha)
               edje_object_signal_emit(ic->obj, "e,action,thumb,gen,alpha", "e");
             else
               edje_object_signal_emit(ic->obj, "e,action,thumb,gen", "e");
             return;
          }
     }
   ic->thumb_failed = EINA_TRUE;
   evas_object_del(obj);

   if (ic->realized)
     _e_fm2_icon_icon_set(ic);
}

#if 0
static void
_e_fm2_cb_focus_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (sd->iop_icon && sd->iop_icon->entry_widget)
     e_widget_focus_set(sd->iop_icon->entry_widget, 1);
}
#endif

static void
_e_fm2_cb_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Key_Down *ev = event_info;
   E_Fm2_Smart_Data *sd = data;
   E_Fm2_Icon *ic;

   if (e_comp_util_kbd_grabbed()) return;
   if (sd->iop_icon) return;

   if (evas_key_modifier_is_set(ev->modifiers, "Control"))
     {
        if (!strcmp(ev->key, "x"))
          {
             _e_fm2_file_cut(obj);
             return;
          }
        else if (!strcmp(ev->key, "c"))
          {
             _e_fm2_file_copy(obj);
             return;
          }
        else if (!strcmp(ev->key, "v"))
          {
             _e_fm2_file_paste(obj);
             return;
          }
        else if (!strcmp(ev->key, "h"))
          {
             if (sd->show_hidden_files)
               sd->show_hidden_files = EINA_FALSE;
             else
               sd->show_hidden_files = EINA_TRUE;
             sd->inherited_dir_props = EINA_FALSE;
             _e_fm2_refresh(data, NULL, NULL);
             return;
          }
        else if (!strcmp(ev->key, "1"))
          {
             if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_GRID_ICONS)
               return;
             sd->view_mode = E_FM2_VIEW_MODE_GRID_ICONS;
             sd->inherited_dir_props = EINA_FALSE;
             _e_fm2_refresh(sd, NULL, NULL);
             return;
          }
        else if (!strcmp(ev->key, "2"))
          {
             if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST)
               return;
             sd->view_mode = E_FM2_VIEW_MODE_LIST;
             sd->inherited_dir_props = EINA_FALSE;
             _e_fm2_refresh(sd, NULL, NULL);
             return;
          }
        else if (!strcmp(ev->key, "3"))
          {
             if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_CUSTOM_ICONS)
               return;
             sd->view_mode = E_FM2_VIEW_MODE_CUSTOM_ICONS;
             sd->inherited_dir_props = EINA_FALSE;
             _e_fm2_refresh(sd, NULL, NULL);
             return;
          }
     }

   if (!strcmp(ev->key, "Left"))
     {
        /* FIXME: icon mode, typebuf extras */
        /* list mode: scroll left n pix
         * icon mode: prev icon
         * typebuf mode: cursor left
         */
        _e_fm2_icon_sel_prev(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
     }
   else if (!strcmp(ev->key, "Right"))
     {
        /* FIXME: icon mode, typebuf extras */
        /* list mode: scroll right n pix
         * icon mode: next icon
         * typebuf mode: cursor right
         */
        _e_fm2_icon_sel_next(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
     }
   else if (!strcmp(ev->key, "Up"))
     {
        if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST)
          _e_fm2_icon_sel_prev(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
        else
          _e_fm2_icon_sel_up(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
     }
   else if (!strcmp(ev->key, "Down"))
     {
        if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST)
          _e_fm2_icon_sel_next(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
        else
          _e_fm2_icon_sel_down(obj, evas_key_modifier_is_set(ev->modifiers, "Shift"));
     }
   else if (!strcmp(ev->key, "Home"))
     {
        /* FIXME: typebuf extras */
        /* go to first icon
         * typebuf mode: cursor to start
         */
        _e_fm2_icon_sel_first(obj, EINA_FALSE);
     }
   else if (!strcmp(ev->key, "End"))
     {
        /* FIXME: typebuf extras */
        /* go to last icon
         * typebuf mode: cursor to end
         */
        _e_fm2_icon_sel_last(obj, EINA_FALSE);
     }
   else if (!strcmp(ev->key, "Prior"))
     {
        /* up h * n pixels */
        e_fm2_pan_set(obj, sd->pos.x, sd->pos.y - sd->h);
        evas_object_smart_callback_call(sd->obj, "pan_changed", NULL);
     }
   else if (!strcmp(ev->key, "Next"))
     {
        /* down h * n pixels */
        e_fm2_pan_set(obj, sd->pos.x, sd->pos.y + sd->h);
        evas_object_smart_callback_call(sd->obj, "pan_changed", NULL);
     }
   else if (!strcmp(ev->key, "Escape"))
     {
        /* typebuf mode: end typebuf mode */
        if (sd->typebuf_visible)
          _e_fm2_typebuf_hide(obj);
        else if (sd->dev && (!strcmp(sd->dev, "desktop")))
          return;
        else
          {
             ic = _e_fm2_icon_first_selected_find(obj);
             if (ic)
               _e_fm2_icon_desel_any(obj);
             else
               {
                  if (e_fm2_has_parent_get(obj))
                    e_fm2_parent_go(obj);
               }
          }
     }
   else if ((!strcmp(ev->key, "Return")) || (!strcmp(ev->key, "KP_Enter")))
     {
        /* if selected - select callback.
         * typebuf mode: if nothing selected - run cmd
         */
        if (sd->typebuf_visible)
          _e_fm2_typebuf_run(obj);
        else
          {
             ic = _e_fm2_icon_first_selected_find(obj);
             if (ic)
               {
                  if (_e_fm2_inplace_open(ic) == 0)
                    evas_object_smart_callback_call(ic->sd->obj, "selected", NULL);
               }
          }
     }
   else if (!strcmp(ev->key, "F5"))
     e_fm2_refresh(obj);
   else if (!strcmp(ev->key, "F2"))
     {
        if (eina_list_count(sd->selected_icons) == 1)
          _e_fm2_file_rename(eina_list_data_get(sd->selected_icons), NULL, NULL);
     }
   else if (!strcmp(ev->key, "Insert"))
     {
        /* dunno what to do with this yet */
     }
   else if (!strcmp(ev->key, "Tab"))
     {
        /* typebuf mode: tab complete */
        if (sd->typebuf_visible)
          _e_fm2_typebuf_complete(obj);
     }
   else if (!strcmp(ev->key, "BackSpace"))
     {
        /* typebuf mode: backspace */
        if (sd->typebuf_visible)
          _e_fm2_typebuf_char_backspace(obj);
        else if (!sd->config->view.no_typebuf_set)
          {
             /* only allow this action when typebuf navigation is allowed in config */
             if (e_fm2_has_parent_get(obj))
               e_fm2_parent_go(obj);
          }
     }
   else if (!strcmp(ev->key, "Delete"))
     _e_fm2_file_delete(obj);
   else if (!evas_key_modifier_is_set(ev->modifiers, "Control") &&
            !evas_key_modifier_is_set(ev->modifiers, "Alt"))
     {
        if (ev->string)
          {
             if (!sd->typebuf_visible) _e_fm2_typebuf_show(obj);
             _e_fm2_typebuf_char_append(obj, ev->string);
          }
     }
}

static void
_e_fm2_cb_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Fm2_Smart_Data *sd;

   sd = data;
   ev = event_info;
   _e_fm2_typebuf_hide(sd->obj);
   if (sd->iop_icon)
     _e_fm2_icon_entry_widget_del(sd->iop_icon);
   if (ev->button == 1)
     {
        Eina_List *l;
        int multi_sel = 0, range_sel = 0, sel_change = 0;

        if (!sd->config->selection.windows_modifiers)
          {
             if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               range_sel = 1;
             else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               multi_sel = 1;
          }
        else
          {
             if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               range_sel = 1;
             else if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               multi_sel = 1;
          }
        if (sd->config->selection.single)
          {
             multi_sel = 0;
             range_sel = 0;
          }
        if ((!multi_sel) && (!range_sel))
          {
             E_Fm2_Icon *ic;
             EINA_LIST_FOREACH(sd->icons, l, ic)
               {
                  if (ic->selected)
                    {
                       _e_fm2_icon_deselect(ic);
                       sel_change = 1;
                    }
               }
          }
        if (sel_change)
          evas_object_smart_callback_call(sd->obj, "selection_change", NULL);

        if (!(ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD))
          {
             if (!sd->config->selection.single)
               {
                  sd->selrect.ox = ev->canvas.x;
                  sd->selrect.oy = ev->canvas.y;
                  sd->selecting = EINA_TRUE;
               }
          }
        if (ev->flags & EVAS_BUTTON_DOUBLE_CLICK)
          evas_object_smart_callback_call(sd->obj, "double_clicked", NULL);
     }
   else if (ev->button == 3)
     {
        _e_fm2_menu(sd->obj, ev->timestamp);
     }
}

static void
_e_fm2_cb_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   _e_fm2_typebuf_hide(sd->obj);
   sd->selecting = EINA_FALSE;
   sd->selrect.ox = 0;
   sd->selrect.oy = 0;
   evas_object_hide(sd->sel_rect);
}

static void
_e_fm2_cb_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Fm2_Smart_Data *sd;
   E_Fm2_Icon *ic;
   Eina_List *l = NULL;
   int x, y, w, h;
   int sel_change = 0;

   sd = data;
   ev = event_info;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD)
     {
        if (sd->selecting)
          {
             sd->selecting = EINA_FALSE;
             sd->selrect.ox = 0;
             sd->selrect.oy = 0;
             evas_object_hide(sd->sel_rect);
          }
        return;
     }
   if (!sd->selecting) return;

   if (ev->cur.canvas.x < sd->selrect.ox)
     {
        sd->selrect.x = ev->cur.canvas.x;
        sd->selrect.w = (sd->selrect.ox - sd->selrect.x);
     }
   else
     {
        sd->selrect.x = MIN(sd->selrect.ox, ev->cur.canvas.x);
        sd->selrect.w = abs(sd->selrect.x - ev->cur.canvas.x);
     }
   if (ev->cur.canvas.y < sd->selrect.oy)
     {
        sd->selrect.y = ev->cur.canvas.y;
        sd->selrect.h = (sd->selrect.oy - sd->selrect.y);
     }
   else
     {
        sd->selrect.y = MIN(sd->selrect.oy, ev->cur.canvas.y);
        sd->selrect.h = abs(sd->selrect.y - ev->cur.canvas.y);
     }
   _e_fm2_sel_rect_update(sd);

   evas_object_geometry_get(sd->sel_rect, &x, &y, &w, &h);

/*
 * Leave commented for now. Start of scrolling the sel_rect
 *
   int nx, ny, nw, nh;

   nx = sd->pos.x;
   if ((x - sd->pos.x) < 0)
     nx = x;
   else if ((x + w - sd->pos.x) > (sd->w))
     nx = x + w - sd->w;
   ny = sd->pos.y;
   if ((y - sd->pos.y) < 0)
     ny = y;
   else if ((y + h - sd->pos.y) > (sd->h))
     ny = y + h - sd->h;
   e_fm2_pan_set(sd->obj, nx, ny);
   evas_object_smart_callback_call(sd->obj, "pan_changed", NULL);
 */

   EINA_LIST_FOREACH(sd->icons, l, ic)
     {
        int ix, iy, iw, ih;
        int ix_t, iy_t, iw_t, ih_t;

        if (!ic) continue;
        evas_object_geometry_get(ic->obj_icon, &ix, &iy, &iw, &ih);
        evas_object_geometry_get(edje_object_part_object_get(ic->obj,
                                                             "e.text.label"),
                                 &ix_t, &iy_t, &iw_t, &ih_t);
        if (E_INTERSECTS(x, y, w, h, ix, iy, iw, ih) ||
            E_INTERSECTS(x, y, w, h, ix_t, iy_t, iw_t, ih_t))
          {
             if (!ic->selected)
               {
                  _e_fm2_icon_select(ic);
                  sel_change = 1;
               }
          }
        else
          {
             if (ic->selected)
               {
                  _e_fm2_icon_deselect(ic);
                  sel_change = 1;
               }
          }
     }
   if (sel_change)
     evas_object_smart_callback_call(sd->obj, "selection_change", NULL);
}

static void
_e_fm2_sel_rect_update(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   evas_object_move(sd->sel_rect, sd->selrect.x, sd->selrect.y);
   evas_object_resize(sd->sel_rect, sd->selrect.w, sd->selrect.h);
   evas_object_show(sd->sel_rect);
}

static void
_e_fm2_cb_scroll_job(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;
   sd->scroll_job = NULL;
   evas_event_freeze(evas_object_evas_get(sd->obj));
   edje_freeze();
   _e_fm2_regions_eval(sd->obj);
   _e_fm2_obj_icons_place(sd);
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(sd->obj));
   _e_fm2_dir_save_props(sd);
}

static void
_e_fm2_cb_resize_job(void *data)
{
   E_Fm2_Smart_Data *sd;
   Eina_List *l;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;
   sd->resize_job = NULL;
   evas_event_freeze(evas_object_evas_get(sd->obj));
   edje_freeze();
   switch (_e_fm2_view_mode_get(sd))
     {
      case E_FM2_VIEW_MODE_ICONS:
        _e_fm2_regions_free(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      case E_FM2_VIEW_MODE_GRID_ICONS:
        _e_fm2_regions_free(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      case E_FM2_VIEW_MODE_CUSTOM_ICONS:
        if (sd->config->view.fit_custom_pos)
          {
             E_Fm2_Icon *ic;
             EINA_LIST_FOREACH(sd->icons, l, ic)
               {
                  ic->region = NULL;
                  _e_fm2_icon_geom_adjust(ic, ic->x, ic->y, ic->w, ic->h, sd->pw, sd->ph);
               }
          }
        _e_fm2_regions_free(sd->obj);
//	_e_fm2_regions_eval(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      case E_FM2_VIEW_MODE_CUSTOM_GRID_ICONS:
        /* FIXME: not going to implement this at this stage */
        _e_fm2_regions_free(sd->obj);
//	_e_fm2_regions_eval(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      case E_FM2_VIEW_MODE_CUSTOM_SMART_GRID_ICONS:
        /* FIXME: not going to implement this at this stage */
        _e_fm2_regions_free(sd->obj);
//	_e_fm2_regions_eval(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      case E_FM2_VIEW_MODE_LIST:
        if (sd->iconlist_changed)
          {
             E_Fm2_Icon *ic;
             EINA_LIST_FOREACH(sd->icons, l, ic)
               {
                  ic->region = NULL;
//		  _e_fm2_icon_unrealize(ic);
               }
          }
        _e_fm2_regions_free(sd->obj);
        _e_fm2_icons_place(sd->obj);
        _e_fm2_regions_populate(sd->obj);
        break;

      default:
        break;
     }
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(sd->obj));
   sd->iconlist_changed = EINA_FALSE;
   sd->pw = sd->w;
   sd->ph = sd->h;

   if ((sd->max.w > 0) && (sd->max.h > 0) && (sd->w > 0) && (sd->h > 0) && (sd->view_flags & E_FM2_VIEW_SAVE_DIR_CUSTOM))
     {
        E_Fm2_Custom_File *cf = e_fm2_custom_file_get(sd->realpath);
        if ((cf) && (cf->dir))
          {
             sd->pos.x = cf->dir->pos.x * (sd->max.w - sd->w);
             sd->pos.y = cf->dir->pos.y * (sd->max.h - sd->h);
             evas_object_smart_callback_call(sd->obj, "pan_changed", NULL);
          }
     }
}

static int
_e_fm2_cb_icon_sort(const void *data1, const void *data2)
{
   const E_Fm2_Icon *ic1, *ic2;
   char *l1, *l2;

   ic1 = data1;
   ic2 = data2;
   l1 = (char *)ic1->info.file;
   if (ic1->info.label) l1 = (char *)ic1->info.label;
   l2 = (char *)ic2->info.file;
   if (ic2->info.label) l2 = (char *)ic2->info.label;
   if (ic1->sd->config->list.sort.dirs.first)
     {
        if ((S_ISDIR(ic1->info.statinfo.st_mode)) !=
            (S_ISDIR(ic2->info.statinfo.st_mode)))
          {
             if (S_ISDIR(ic1->info.statinfo.st_mode)) return -1;
             return 1;
          }
     }
   else if (ic1->sd->config->list.sort.dirs.last)
     {
        if ((S_ISDIR(ic1->info.statinfo.st_mode)) !=
            (S_ISDIR(ic2->info.statinfo.st_mode)))
          {
             if (S_ISDIR(ic1->info.statinfo.st_mode)) return 1;
             return -1;
          }
     }
   if (ic1->sd->config->list.sort.mtime)
     {
        if (ic1->info.statinfo.st_mtime > ic2->info.statinfo.st_mtime)
          return -1;
        if (ic1->info.statinfo.st_mtime < ic2->info.statinfo.st_mtime)
          return 1;
     }
   if (ic1->sd->config->list.sort.extension)
     {
        int cmp;
        const char *f1, *f2;
        f1 = ecore_file_file_get(l1);
        f1 = strrchr(f1, '.');
        f2 = ecore_file_file_get(l2);
        f2 = strrchr(f2, '.');
        if (f1 && f2)
          {
             cmp = strcasecmp(f1, f2);
             if (cmp) return cmp;
          }
        else if (f1)
          return 1;
        else if (f2)
          return -1;
     }
   if (ic1->sd->config->list.sort.size)
     {
        if (ic1->info.link)
          {
             if (!ic2->info.link) return 1;
          }
        else
          {
             if (ic2->info.link) return -1;
             if (ic1->info.statinfo.st_size > ic2->info.statinfo.st_size)
               return -1;
             else if (ic1->info.statinfo.st_size < ic2->info.statinfo.st_size)
               return 1;
          }
     }
   if (ic1->sd->config->list.sort.no_case)
     return strcasecmp(l1, l2);
   return strcmp(l1, l2);
}

static Eina_Bool
_e_fm2_cb_scan_timer(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return ECORE_CALLBACK_CANCEL;
   _e_fm2_queue_process(data);
   sd->scan_timer = NULL;
   if (!sd->listing)
     {
        _e_fm2_client_monitor_list_end(data);
        return ECORE_CALLBACK_CANCEL;
     }
   if (sd->busy_count > 0)
     sd->scan_timer = ecore_timer_loop_add(0.2, _e_fm2_cb_scan_timer, sd->obj);
   else
     {
        if (!sd->sort_idler)
          sd->sort_idler = ecore_idler_add(_e_fm2_cb_sort_idler, data);
     }
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_fm2_cb_sort_idler(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return ECORE_CALLBACK_CANCEL;
   _e_fm2_queue_process(data);
   if (!sd->listing)
     {
        sd->sort_idler = NULL;
        _e_fm2_client_monitor_list_end(data);
        return ECORE_CALLBACK_CANCEL;
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_fm2_cb_theme(void *data, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_fm2_refresh(data);
   return ECORE_CALLBACK_RENEW;
}

/**************************/
static void
_e_fm2_obj_icons_place(E_Fm2_Smart_Data *sd)
{
   const Eina_List *l;
   E_Fm2_Region *rg;

   evas_event_freeze(evas_object_evas_get(sd->obj));
   edje_freeze();
   EINA_LIST_FOREACH(sd->regions.list, l, rg)
     {
        if (rg->realized)
          {
             const Eina_List *ll;
             E_Fm2_Icon *ic;

             EINA_LIST_FOREACH(rg->list, ll, ic)
               {
                  const char *th[] =
                  {
                   "list/fixed",
                   "list_odd/fixed",
                  };
                  Evas_Object *prev;

                  if (!ic->realized) continue;
                  if (!_e_fm2_icon_visible(ic))
                    {
                       e_thumb_icon_end(ic->obj_icon);
                    }
                  evas_object_move(ic->obj,
                                   sd->x + ic->x - sd->pos.x,
                                   sd->y + ic->y - sd->pos.y);
                  evas_object_resize(ic->obj, ic->w, ic->h);
                  _e_fm2_icon_thumb(ic, ic->obj_icon, 0);
                  if (_e_fm2_view_mode_get(ic->sd) != E_FM2_VIEW_MODE_LIST) continue;
                  /* FIXME: this is probably something that should be unnecessary,
                   * but currently we add icons in semi-randomly and it messes up ordering.
                   * current process is:
                   * 1 receive file info from slave
                   * 2 create icon struct
                   * 3 either directly add icon or queue it (usually queue)
                   * 4 process queue on timer, determining even/odd for all icons at this time
                   * 4.5 icons are realized, theme is set here <-- where bug occurs
                   * 5 goto 1
                   * 6 if icon is inserted into current viewport during 4.5, even/odd for surrounding items
                   *   will always be wrong (ticket #1579)
                   */

                   prev = edje_object_part_swallow_get(ic->obj, "e.swallow.icon");
                   _e_fm2_theme_edje_object_set(ic->sd, ic->obj,
                                                "base/theme/widgets",
                                                th[ic->odd]);
                   edje_object_part_swallow(ic->obj, "e.swallow.icon", prev);
                   _e_fm2_icon_label_set(ic, ic->obj);
                   if (ic->selected)
                     edje_object_signal_emit(ic->obj, "e,state,selected", "e");
               }
          }
     }
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(sd->obj));
}

/**************************/

static void
_e_fm2_smart_add(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = E_NEW(E_Fm2_Smart_Data, 1);
   if (!sd) return;

   sd->view_mode = -1; /* unset */
   sd->icon_size = -1; /* unset */

   sd->obj = obj;
   sd->clip = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(sd->clip, obj);
   evas_object_color_set(sd->clip, 255, 255, 255, 255);

   sd->underlay = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_clip_set(sd->underlay, sd->clip);
   evas_object_smart_member_add(sd->underlay, obj);
   evas_object_color_set(sd->underlay, 0, 0, 0, 0);
   evas_object_show(sd->underlay);

   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN, _e_fm2_cb_key_down, sd);
   //evas_object_event_callback_add(obj, EVAS_CALLBACK_FOCUS_IN, _e_fm2_cb_focus_in, sd);
   evas_object_event_callback_add(sd->underlay, EVAS_CALLBACK_MOUSE_DOWN, _e_fm2_cb_mouse_down, sd);
   evas_object_event_callback_add(sd->underlay, EVAS_CALLBACK_MOUSE_UP, _e_fm2_cb_mouse_up, sd);
   evas_object_event_callback_add(sd->underlay, EVAS_CALLBACK_MOUSE_MOVE, _e_fm2_cb_mouse_move, sd);

   sd->drop = edje_object_add(evas_object_evas_get(obj));
   evas_object_clip_set(sd->drop, sd->clip);
   _e_fm2_theme_edje_object_set(sd, sd->drop,
                                "base/theme/fileman",
                                "list/drop_between");
   evas_object_smart_member_add(sd->drop, obj);
   evas_object_show(sd->drop);

   sd->drop_in = edje_object_add(evas_object_evas_get(obj));
   evas_object_clip_set(sd->drop_in, sd->clip);
   _e_fm2_theme_edje_object_set(sd, sd->drop_in,
                                "base/theme/fileman",
                                "list/drop_in");
   evas_object_smart_member_add(sd->drop_in, obj);
   evas_object_show(sd->drop_in);

   sd->overlay = edje_object_add(evas_object_evas_get(obj));
   evas_object_clip_set(sd->overlay, sd->clip);
   _e_fm2_theme_edje_object_set(sd, sd->overlay,
                                "base/theme/fileman",
                                "overlay");
   evas_object_smart_member_add(sd->overlay, obj);
   evas_object_show(sd->overlay);

   sd->sel_rect = edje_object_add(evas_object_evas_get(obj));
   evas_object_clip_set(sd->sel_rect, sd->clip);
   _e_fm2_theme_edje_object_set(sd, sd->sel_rect, "base/theme/fileman",
                                "rubberband");
   evas_object_smart_member_add(sd->sel_rect, obj);

   evas_object_smart_data_set(obj, sd);
   evas_object_move(obj, 0, 0);
   evas_object_resize(obj, 0, 0);

   E_LIST_HANDLER_APPEND(sd->handlers, E_EVENT_CONFIG_ICON_THEME, _e_fm2_cb_theme, sd->obj);
   E_LIST_HANDLER_APPEND(sd->handlers, EFREET_EVENT_ICON_CACHE_UPDATE, _e_fm2_icon_cache_update, sd);

   _e_fm2_list = eina_list_append(_e_fm2_list, sd->obj);
}

static void
_e_fm2_smart_del(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   E_FREE_LIST(sd->handlers, ecore_event_handler_del);

   _e_fm2_client_monitor_list_end(obj);
   if (sd->realpath) _e_fm2_client_monitor_del(sd->id, sd->realpath);
   _e_fm2_live_process_end(obj);
   _e_fm2_queue_free(obj);
   _e_fm2_regions_free(obj);
   _e_fm2_icons_free(obj);
   if (sd->selected_icons) eina_list_free(sd->selected_icons);
   if (sd->menu)
     {
        e_menu_post_deactivate_callback_set(sd->menu, NULL, NULL);
        e_object_del(E_OBJECT(sd->menu));
        sd->menu = NULL;
     }
   if (sd->entry_dialog)
     {
        e_object_del(E_OBJECT(sd->entry_dialog));
        sd->entry_dialog = NULL;
     }
   if (sd->image_dialog)
     {
        e_object_del(E_OBJECT(sd->image_dialog));
        sd->image_dialog = NULL;
     }
   E_FREE_LIST(sd->rename_dialogs, e_object_del);
   if (sd->scroll_job) ecore_job_del(sd->scroll_job);
   if (sd->resize_job) ecore_job_del(sd->resize_job);
   if (sd->refresh_job) ecore_job_del(sd->refresh_job);
   eina_stringshare_del(sd->custom_theme);
   eina_stringshare_del(sd->custom_theme_content);
   sd->custom_theme = sd->custom_theme_content = NULL;
   eina_stringshare_del(sd->dev);
   eina_stringshare_del(sd->path);
   eina_stringshare_del(sd->realpath);
   eina_stringshare_del(sd->new_file.filename);
   sd->dev = sd->path = sd->realpath = NULL;
   if (sd->mount)
     {
        e_fm2_device_unmount(sd->mount);
        sd->mount = NULL;
     }
   if (sd->config) _e_fm2_config_free(sd->config);

   E_FREE(sd->typebuf.buf);
   if (sd->typebuf.timer) ecore_timer_del(sd->typebuf.timer);
   sd->typebuf.timer = NULL;
   eina_list_free(sd->mount_ops);

   evas_object_del(sd->underlay);
   evas_object_del(sd->overlay);
   evas_object_del(sd->drop);
   evas_object_del(sd->drop_in);
   evas_object_del(sd->sel_rect);
   evas_object_del(sd->clip);
   if (sd->drop_handler) e_drop_handler_del(sd->drop_handler);
   if (_e_fm2_list_walking == 0)
     _e_fm2_list = eina_list_remove(_e_fm2_list, sd->obj);
   else
     _e_fm2_list_remove = eina_list_append(_e_fm2_list_remove, sd->obj);
   if (sd->desktop) efreet_desktop_free(sd->desktop);
   sd->desktop = NULL;
   if (sd->dnd_scroller) ecore_animator_del(sd->dnd_scroller);
   sd->dnd_scroller = NULL;
   free(sd);
   e_fm2_custom_file_flush();
}

static void
_e_fm2_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((sd->x == x) && (sd->y == y)) return;
   sd->x = x;
   sd->y = y;
   evas_object_move(sd->underlay, sd->x, sd->y);
   if (!sd->overlay_clip)
     evas_object_move(sd->overlay, sd->x, sd->y);
   _e_fm2_dnd_drop_configure(sd->obj);
   evas_object_move(sd->clip, sd->x - OVERCLIP, sd->y - OVERCLIP);
   _e_fm2_obj_icons_place(sd);
   if (sd->drop_handler)
     e_drop_handler_geometry_set(sd->drop_handler, sd->x, sd->y, sd->w, sd->h);
}

static void
_e_fm2_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Fm2_Smart_Data *sd;
   Eina_Bool wch = EINA_FALSE;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((sd->w == w) && (sd->h == h)) return;
   if (w != sd->w) wch = EINA_TRUE;
   sd->w = w;
   sd->h = h;
   evas_object_resize(sd->underlay, sd->w, sd->h);
   if (!sd->overlay_clip)
     evas_object_resize(sd->overlay, sd->w, sd->h);
   _e_fm2_dnd_drop_configure(sd->obj);
   evas_object_resize(sd->clip, sd->w + (OVERCLIP * 2), sd->h + (OVERCLIP * 2));

   /* for automatic layout - do this - NB; we could put this on a timer delay */
   if ((_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_LIST) ||
       (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_GRID_ICONS))
     {
        if (wch)
          {
             if (sd->resize_job) ecore_job_del(sd->resize_job);
             sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, obj);
          }
        else
          {
             if (sd->scroll_job) ecore_job_del(sd->scroll_job);
             sd->scroll_job = ecore_job_add(_e_fm2_cb_scroll_job, obj);
          }
     }
   else if (_e_fm2_view_mode_get(sd) == E_FM2_VIEW_MODE_CUSTOM_ICONS)
     {
        if (sd->config->view.fit_custom_pos)
          {
             if (sd->resize_job) ecore_job_del(sd->resize_job);
             sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, obj);
          }
        else
          {
             if (sd->scroll_job) ecore_job_del(sd->scroll_job);
             sd->scroll_job = ecore_job_add(_e_fm2_cb_scroll_job, obj);
          }
     }
   if (sd->drop_handler)
     e_drop_handler_geometry_set(sd->drop_handler, sd->x, sd->y, sd->w, sd->h);
}

static void
_e_fm2_smart_show(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_show(sd->clip);
}

static void
_e_fm2_smart_hide(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_hide(sd->clip);
}

static void
_e_fm2_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_color_set(sd->clip, r, g, b, a);
}

static void
_e_fm2_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_set(sd->clip, clip);
}

static void
_e_fm2_smart_clip_unset(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_unset(sd->clip);
}

static void
_e_fm2_overlay_clip_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   int w, h;

   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   evas_object_resize(sd->overlay, w, h);
}

static void
_e_fm2_overlay_clip_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   int x, y;

   evas_object_geometry_get(obj, &x, &y, NULL, NULL);
   evas_object_move(sd->overlay, x, y);
}

static void
_e_fm2_view_menu_sorting_change_case(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.no_case = !mi->toggle;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_change_size(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.size = mi->toggle;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_change_mtime(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.mtime = mi->toggle;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_change_extension(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.extension = mi->toggle;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_change_dirs_first(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.dirs.first = mi->toggle;
   if (mi->toggle && sd->config->list.sort.dirs.last)
     sd->config->list.sort.dirs.last = 0;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_change_dirs_last(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->list.sort.dirs.last = mi->toggle;
   if (mi->toggle && sd->config->list.sort.dirs.first)
     sd->config->list.sort.dirs.first = 0;
   _e_fm2_refresh(sd, NULL, NULL);
}

static void
_e_fm2_view_menu_sorting_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   E_Menu *subm;

   subm = e_menu_new();
   e_menu_item_submenu_set(mi, subm);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Case Sensitive"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, !sd->config->list.sort.no_case);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_case, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Sort By Extension"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->config->list.sort.extension);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_extension, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Sort By Modification Time"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->config->list.sort.mtime);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_mtime, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Sort By Size"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->config->list.sort.size);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_size, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Directories First"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->config->list.sort.dirs.first);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_dirs_first, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Directories Last"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->config->list.sort.dirs.last);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_sorting_change_dirs_last, sd);
}

static void
_e_fm2_menu(Evas_Object *obj, unsigned int timestamp)
{
   E_Fm2_Smart_Data *sd;
   E_Menu *mn, *sub;
   E_Menu_Item *mi;
   E_Zone *zone;
   int x, y;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   mn = e_menu_new();
   e_object_data_set(E_OBJECT(mn), obj);
   e_menu_category_set(mn, "e/fileman/action");

   if (sd->icon_menu.replace.func)
     sd->icon_menu.replace.func(sd->icon_menu.replace.data, sd->obj, mn, NULL);
   else
     {
        if (sd->icon_menu.start.func)
          sd->icon_menu.start.func(sd->icon_menu.start.data, sd->obj, mn, NULL);
        if (!(sd->icon_menu.flags & E_FM2_MENU_NO_VIEW_MENU))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("View Mode"));
             e_util_menu_item_theme_icon_set(mi, "preferences-look");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));
             e_object_data_set(E_OBJECT(sub), sd);
             e_menu_pre_activate_callback_set(sub, _e_fm2_view_menu_pre, sd);

             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Sorting"));
             e_menu_item_submenu_pre_callback_set(mi, _e_fm2_view_menu_sorting_pre, sd);
          }
        if (!(sd->icon_menu.flags &
              (E_FM2_MENU_NO_SHOW_HIDDEN | E_FM2_MENU_NO_REMEMBER_ORDERING | E_FM2_MENU_NO_ACTIVATE_CHANGE)))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Options"));
             e_util_menu_item_theme_icon_set(mi, "preferences-system");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));
             e_object_data_set(E_OBJECT(sub), sd);
             e_menu_pre_activate_callback_set(sub, _e_fm2_options_menu_pre, sd);
          }
        if (!(sd->icon_menu.flags & E_FM2_MENU_NO_REFRESH))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Refresh View"));
             e_util_menu_item_theme_icon_set(mi, "view-refresh");
             e_menu_item_callback_set(mi, _e_fm2_refresh, sd);
          }

        if (!(sd->icon_menu.flags & E_FM2_MENU_NO_NEW))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_separator_set(mi, 1);

             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("New..."));
             e_util_menu_item_theme_icon_set(mi, "add");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));
             e_object_data_set(E_OBJECT(sub), sd);
             e_menu_pre_activate_callback_set(sub, _e_fm2_add_menu_pre, sd);
          }

        if (sd->realpath)
          {
             const Eina_List *ll;
             /* see if we have any mime handlers registered for this file */
             ll = e_fm2_mime_handler_mime_handlers_get("inode/directory");
             if (ll)
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_separator_set(mi, 1);

                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Actions..."));
                  e_util_menu_item_theme_icon_set(mi, "preferences-plugin");
                  sub = e_menu_new();
                  e_menu_item_submenu_set(mi, sub);
                  _e_fm2_context_menu_append(sd, sd->realpath, ll, sub, NULL);
               }
          }

        if (((!(sd->icon_menu.flags & E_FM2_MENU_NO_PASTE)) ||
             (!(sd->icon_menu.flags & E_FM2_MENU_NO_SYMLINK))) &&
            (eina_list_count(_e_fm_file_buffer) > 0) &&
            ecore_file_can_write(sd->realpath))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_separator_set(mi, 1);

             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_PASTE))
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Paste"));
                  e_util_menu_item_theme_icon_set(mi, "edit-paste");
                  e_menu_item_callback_set(mi, _e_fm2_file_paste_menu, sd);
               }

             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_SYMLINK))
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Link"));
                  e_util_menu_item_theme_icon_set(mi, "emblem-symbolic-link");
                  e_menu_item_callback_set(mi, _e_fm2_file_symlink_menu, sd);
               }
          }

        if (sd->icon_menu.end.func)
          sd->icon_menu.end.func(sd->icon_menu.end.data, sd->obj, mn, NULL);
     }

   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   zone = e_zone_current_get();
   if (!zone)
     {
        e_object_del(E_OBJECT(mn));
        return;
     }
   sd->menu = mn;
   e_menu_post_deactivate_callback_set(mn, _e_fm2_menu_post_cb, sd);
   e_menu_activate_mouse(mn, zone,
                         x, y, 1, 1,
                         E_MENU_POP_DIRECTION_DOWN, timestamp);
}

static void
_e_fm2_menu_post_cb(void *data, E_Menu *m EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   sd->menu = NULL;
}

static void
_e_fm2_icon_menu(E_Fm2_Icon *ic, Evas_Object *obj, unsigned int timestamp)
{
   E_Fm2_Smart_Data *sd;
   E_Menu *mn, *sub;
   E_Menu_Item *mi;
   E_Zone *zone;
   Eina_List *sel;
   Eina_List *l = NULL;
   int x, y, can_w, can_w2, protect;
   char buf[PATH_MAX], *ext;
   Eina_Bool writable;

   sd = ic->sd;
   if (ic->menu) return;

   mn = e_menu_new();
   e_object_data_set(E_OBJECT(mn), obj);
   e_menu_category_set(mn, "e/fileman/action");

   if (sd->icon_menu.replace.func)
     sd->icon_menu.replace.func(sd->icon_menu.replace.data, sd->obj, mn, &ic->info);
   else
     {
        writable = ecore_file_can_write(sd->realpath);

        if (sd->icon_menu.start.func)
          sd->icon_menu.start.func(sd->icon_menu.start.data, sd->obj, mn, &ic->info);
        if (!(sd->icon_menu.flags & E_FM2_MENU_NO_VIEW_MENU))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("View Mode"));
             e_util_menu_item_theme_icon_set(mi, "preferences-look");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_data_set(E_OBJECT(sub), sd);
             e_object_unref(E_OBJECT(sub));
             e_menu_pre_activate_callback_set(sub, _e_fm2_icon_view_menu_pre, sd);

             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Sorting"));
             e_menu_item_submenu_pre_callback_set(mi, _e_fm2_view_menu_sorting_pre, sd);
          }
        if (!(sd->icon_menu.flags &
              (E_FM2_MENU_NO_SHOW_HIDDEN | E_FM2_MENU_NO_REMEMBER_ORDERING | E_FM2_MENU_NO_ACTIVATE_CHANGE)))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Options"));
             e_util_menu_item_theme_icon_set(mi, "preferences-system");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));
             e_object_data_set(E_OBJECT(sub), sd);
             e_menu_pre_activate_callback_set(sub, _e_fm2_options_menu_pre, sd);
          }
        if (!(sd->icon_menu.flags & E_FM2_MENU_NO_REFRESH))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Refresh View"));
             e_util_menu_item_theme_icon_set(mi, "view-refresh");
             e_menu_item_callback_set(mi, _e_fm2_refresh, sd);
          }

        /* FIXME: stat the dir itself - move to e_fm_main */
        if (writable && !(sd->icon_menu.flags & E_FM2_MENU_NO_NEW))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_separator_set(mi, 1);

             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("New..."));
             e_util_menu_item_theme_icon_set(mi, "add");
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));
             e_object_data_set(E_OBJECT(sub), sd);
             e_menu_pre_activate_callback_set(sub, _e_fm2_add_menu_pre, sd);
          }
        {
           const Eina_List *ll = NULL;
           E_Menu *subm = NULL;
           if (ic->info.mime)
             {
                /* see if we have any mime handlers registered for this file */
                ll = e_fm2_mime_handler_mime_handlers_get(ic->info.mime);
             }
           if (ll || sd->realpath)
             {
                mi = e_menu_item_new(mn);
                e_menu_item_separator_set(mi, 1);

                mi = e_menu_item_new(mn);
                e_menu_item_label_set(mi, _("Actions..."));
                e_util_menu_item_theme_icon_set(mi, "preferences-plugin");
                subm = e_menu_new();
                e_menu_item_submenu_set(mi, subm);
                _e_fm2_icon_realpath(ic, buf, sizeof(buf));
             }
           if (ll)
             _e_fm2_context_menu_append(sd, buf, ll, subm, ic);
           if (sd->realpath && (ic->info.mime != _e_fm2_mime_inode_directory))
             {
                ll = e_fm2_mime_handler_mime_handlers_get("inode/directory");
                _e_fm2_context_menu_append(sd, sd->realpath, ll, subm, ic);
             }

           /* see if we have any glob handlers registered for this file */
           ext = strrchr(ic->info.file, '.');
           if (ext)
             {
                snprintf(buf, sizeof(buf), "*%s", ext);
                l = e_fm2_mime_handler_glob_handlers_get(buf);
                if (l)
                  {
                     if (subm)
                       {
                          if (subm->items)
                            {
                               mi = e_menu_item_new(mn);
                               e_menu_item_separator_set(mi, 1);
                            }
                       }
                     else
                       {
                          mi = e_menu_item_new(mn);
                          e_menu_item_separator_set(mi, 1);

                          mi = e_menu_item_new(mn);
                          e_menu_item_label_set(mi, _("Actions..."));
                          e_util_menu_item_theme_icon_set(mi, "preferences-plugin");
                          subm = e_menu_new();
                          e_menu_item_submenu_set(mi, subm);
                       }
                     _e_fm2_icon_realpath(ic, buf, sizeof(buf));
                     _e_fm2_context_menu_append(sd, buf, l, subm, ic);
                     eina_list_free(l);
                  }
             }
        }
        if (!ic->info.removable)
          {
             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_CUT))
               {
                  if (writable)
                    {
                       mi = e_menu_item_new(mn);
                       e_menu_item_separator_set(mi, 1);

                       mi = e_menu_item_new(mn);
                       e_menu_item_label_set(mi, _("Cut"));
                       e_util_menu_item_theme_icon_set(mi, "edit-cut");
                       e_menu_item_callback_set(mi, _e_fm2_file_cut_menu, sd);
                    }
               }
             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_COPY))
               {
                  if (!writable)
                    {
                       mi = e_menu_item_new(mn);
                       e_menu_item_separator_set(mi, 1);
                    }

                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Copy"));
                  e_util_menu_item_theme_icon_set(mi, "edit-copy");
                  e_menu_item_callback_set(mi, _e_fm2_file_copy_menu, sd);
               }

             if (((!(sd->icon_menu.flags & E_FM2_MENU_NO_PASTE)) ||
                  (!(sd->icon_menu.flags & E_FM2_MENU_NO_SYMLINK))) &&
                 (eina_list_count(_e_fm_file_buffer) > 0) &&
                 writable)
               {
                  if (!(sd->icon_menu.flags & E_FM2_MENU_NO_PASTE))
                    {
                       mi = e_menu_item_new(mn);
                       e_menu_item_label_set(mi, _("Paste"));
                       e_util_menu_item_theme_icon_set(mi, "edit-paste");
                       e_menu_item_callback_set(mi, _e_fm2_file_paste_menu, sd);
                    }

                  if (!(sd->icon_menu.flags & E_FM2_MENU_NO_SYMLINK))
                    {
                       mi = e_menu_item_new(mn);
                       e_menu_item_label_set(mi, _("Link"));
                       e_util_menu_item_theme_icon_set(mi, "emblem-symbolic-link");
                       e_menu_item_callback_set(mi, _e_fm2_file_symlink_menu, sd);
                    }
               }
          }

        can_w2 = 1;
        if (ic->sd->order_file)
          {
             snprintf(buf, sizeof(buf), "%s/.order", sd->realpath);
             /* FIXME: stat the .order itself - move to e_fm_main */
//	     can_w2 = ecore_file_can_write(buf);
          }
        if (ic->info.link)
          {
             can_w = 1;
/*	     struct stat st;

             if (_e_fm2_icon_realpath(ic, buf, sizeof(buf)) &&
                 (lstat(buf, &st) == 0))
               {
                  if (st.st_uid == getuid())
                    {
                       if (st.st_mode & S_IWUSR) can_w = 1;
                    }
                  else if (st.st_gid == getgid())
                    {
                       if (st.st_mode & S_IWGRP) can_w = 1;
                    }
                  else
                    {
                       if (st.st_mode & S_IWOTH) can_w = 1;
                    }
               }
 */       }
        else
          can_w = 1;

        sel = e_fm2_selected_list_get(ic->sd->obj);
        if ((!sel) || eina_list_count(sel) == 1)
          {
             _e_fm2_icon_realpath(ic, buf, sizeof(buf));
             protect = e_filereg_file_protected(buf);
          }
        else
          protect = 0;
        eina_list_free(sel);

        if ((can_w) && (can_w2) && !(protect) && !ic->info.removable)
          {
             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_DELETE))
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Delete"));
                  e_util_menu_item_theme_icon_set(mi, "edit-delete");
                  e_menu_item_callback_set(mi, _e_fm2_file_delete_menu, ic);
               }

             if (!(sd->icon_menu.flags & E_FM2_MENU_NO_RENAME))
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Rename"));
                  e_util_menu_item_theme_icon_set(mi, "edit-rename");
                  e_menu_item_callback_set(mi, _e_fm2_file_rename, ic);
               }
          }

        if (ic->info.removable)
          {
             E_Volume *v;

             v = e_fm2_device_volume_find(ic->info.link);
             if (v)
               {
                  mi = e_menu_item_new(mn);
                  e_menu_item_separator_set(mi, 1);

                  mi = e_menu_item_new(mn);
                  if (v->mounted)
                    {
                       e_menu_item_label_set(mi, _("Unmount"));
                       e_menu_item_callback_set(mi, _e_fm2_volume_unmount, v);
                    }
                  else
                    {
                       e_menu_item_label_set(mi, _("Mount"));
                       e_menu_item_callback_set(mi, _e_fm2_volume_mount, v);
                    }

                  mi = e_menu_item_new(mn);
                  e_menu_item_label_set(mi, _("Eject"));
                  e_util_menu_item_theme_icon_set(mi, "media-eject");
                  e_menu_item_callback_set(mi, _e_fm2_volume_eject, v);

                  mi = e_menu_item_new(mn);
                  e_menu_item_separator_set(mi, 1);
               }
          }
        mi = e_menu_item_new(mn);
        e_menu_item_separator_set(mi, 1);

        if ((!ic->info.removable) && ic->info.file && (ic->info.file[0] != '|') && ic->info.mime && (!strcmp(ic->info.mime, "application/x-desktop")))
          {
             mi = e_menu_item_new(mn);
             e_menu_item_label_set(mi, _("Properties"));
             e_util_menu_item_theme_icon_set(mi, "document-properties");
             e_menu_item_callback_set(mi, _e_fm2_file_properties, ic);
             sub = e_menu_new();
             e_menu_item_submenu_set(mi, sub);
             e_object_unref(E_OBJECT(sub));

             mi = e_menu_item_new(sub);
             e_menu_item_label_set(mi, _("Application Properties"));
             e_util_menu_item_theme_icon_set(mi, "configure");
             e_menu_item_callback_set(mi, _e_fm2_file_application_properties, ic);
          }
        else
          sub = NULL;

        mi = e_menu_item_new(sub ? : mn);
        e_menu_item_label_set(mi, _("File Properties"));
        e_util_menu_item_theme_icon_set(mi, "document-properties");
        e_menu_item_callback_set(mi, _e_fm2_file_properties, ic);

        if (sd->icon_menu.end.func)
          sd->icon_menu.end.func(sd->icon_menu.end.data, sd->obj, mn, &(ic->info));
     }

   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   zone = e_zone_current_get();
   if (!zone)
     {
        e_object_del(E_OBJECT(mn));
        return;
     }
   ic->menu = mn;
   e_menu_post_deactivate_callback_set(mn, _e_fm2_icon_menu_post_cb, ic);
   e_menu_activate_mouse(mn, zone,
                         x, y, 1, 1,
                         E_MENU_POP_DIRECTION_DOWN, timestamp);
}

static void
_e_fm2_context_menu_append(E_Fm2_Smart_Data *sd, const char *path, const Eina_List *list, E_Menu *mn, E_Fm2_Icon *ic)
{
   E_Fm2_Mime_Handler *handler;
   Eina_List *l;

   if (!list) return;

   l = eina_list_clone(list);
   l = eina_list_sort(l, -1, _e_fm2_context_list_sort);

   EINA_LIST_FREE(l, handler)
     {
        E_Fm2_Context_Menu_Data *md = NULL;
        E_Menu_Item *mi;

        if ((!handler) || (!handler->label) || (!e_fm2_mime_handler_test(handler, sd->obj, path)))
          continue;

        md = E_NEW(E_Fm2_Context_Menu_Data, 1);
        if (!md) continue;
        md->icon = ic;
        md->handler = handler;
        md->sd = sd;
        _e_fm2_menu_contexts = eina_list_append(_e_fm2_menu_contexts, md);

        mi = e_menu_item_new(mn);
        e_menu_item_label_set(mi, handler->label);
        if (handler->icon_group)
          {
             if (handler->icon_group[0] == '/')
               e_menu_item_icon_file_set(mi, handler->icon_group);
             else
               e_util_menu_item_theme_icon_set(mi, handler->icon_group);
          }
        e_menu_item_callback_set(mi, _e_fm2_icon_menu_item_cb, md);
     }
}

static int
_e_fm2_context_list_sort(const void *data1, const void *data2)
{
   const E_Fm2_Mime_Handler *d1, *d2;

   if (!data1) return 1;
   if (!data2) return -1;
   d1 = data1;
   if (!d1->label) return 1;
   d2 = data2;
   if (!d2->label) return -1;
   return strcmp(d1->label, d2->label);
}

static void
_e_fm2_icon_menu_post_cb(void *data, E_Menu *m EINA_UNUSED)
{
   E_Fm2_Context_Menu_Data *md;
   E_Fm2_Icon *ic;

   ic = data;
   ic->menu = NULL;
   EINA_LIST_FREE(_e_fm2_menu_contexts, md)
     E_FREE(md);
}

static void
_e_fm2_icon_menu_item_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Context_Menu_Data *md = NULL;
   Evas_Object *obj = NULL;
   char buf[PATH_MAX];

   md = data;
   if (!md) return;
   if (md->icon)
     {
        obj = md->icon->info.fm;
        if (!obj) return;
        snprintf(buf, sizeof(buf), "%s/%s",
                 e_fm2_real_path_get(obj), md->icon->info.file);
        e_fm2_mime_handler_call(md->handler, obj, buf);
     }
   else
     e_fm2_mime_handler_call(md->handler, md->sd->obj, md->sd->realpath);
}

struct e_fm2_view_menu_icon_size_data
{
   E_Fm2_Smart_Data *sd;
   short size;
};

static void
_e_fm2_view_menu_icon_size_data_free(void *obj)
{
   struct e_fm2_view_menu_icon_size_data *d = e_object_data_get(obj);
   free(d);
}

static void
_e_fm2_view_menu_icon_size_change(void *data, E_Menu *m, E_Menu_Item *mi)
{
   struct e_fm2_view_menu_icon_size_data *d = data;
   short current_size = _e_fm2_icon_w_get(d->sd);
   d->sd->icon_size = d->size;
   d->sd->inherited_dir_props = EINA_FALSE;
   if (current_size == d->size)
     return;
   _e_fm2_refresh(d->sd, m, mi);
}

static void
_e_fm2_view_menu_icon_size_use_default(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   short old, new;

   old = _e_fm2_icon_w_get(sd);

   if (sd->icon_size == -1)
     sd->icon_size = sd->config->icon.icon.w;
   else
     sd->icon_size = -1;

   new = _e_fm2_icon_w_get(sd);
   sd->inherited_dir_props = EINA_FALSE;

   if (new == old)
     return;

   _e_fm2_refresh(sd, m, mi);
}

static void
_e_fm2_view_menu_icon_size_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   E_Menu *subm;
   const short *itr, sizes[] =
   {
      22, 32, 48, 64, 96, 128, 256, -1
   };
   short current_size = _e_fm2_icon_w_get(sd);

   if (e_scale > 0.0)
     current_size /= e_scale;

   subm = e_menu_new();
   e_menu_item_submenu_set(mi, subm);

   for (itr = sizes; *itr > -1; itr++)
     {
        char buf[32];
        struct e_fm2_view_menu_icon_size_data *d;

        d = malloc(sizeof(*d));
        if (!d)
          continue;
        d->sd = sd;
        d->size = *itr;

        snprintf(buf, sizeof(buf), "%hd", *itr);

        mi = e_menu_item_new(subm);
        e_object_data_set(E_OBJECT(mi), d);
        e_object_del_attach_func_set
          (E_OBJECT(mi), _e_fm2_view_menu_icon_size_data_free);

        e_menu_item_label_set(mi, buf);
        e_menu_item_radio_group_set(mi, 1);
        e_menu_item_radio_set(mi, 1);

        if (current_size == *itr)
          e_menu_item_toggle_set(mi, 1);

        e_menu_item_callback_set(mi, _e_fm2_view_menu_icon_size_change, d);
     }

   mi = e_menu_item_new(subm);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Use default"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, sd->icon_size == -1);
   e_menu_item_callback_set(mi, _e_fm2_view_menu_icon_size_use_default, sd);
}

static void
_e_fm2_toggle_inherit_dir_props(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;

   sd->inherited_dir_props = !sd->inherited_dir_props;
   _e_fm2_dir_save_props(sd);
   _e_fm2_dir_load_props(sd);
   _e_fm2_refresh(sd, m, mi);
}

static void
_e_fm2_view_menu_common(E_Menu *subm, E_Fm2_Smart_Data *sd)
{
   char buf[64];
   E_Menu_Item *mi;
   char view_mode;
   int icon_size;

   view_mode = _e_fm2_view_mode_get(sd);
   if (!(sd->icon_menu.flags & E_FM2_MENU_NO_VIEW_CHANGE))
     {
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Grid Icons"));
        e_menu_item_radio_group_set(mi, 1);
        e_menu_item_radio_set(mi, 1);
        if (view_mode == E_FM2_VIEW_MODE_GRID_ICONS)
          e_menu_item_toggle_set(mi, 1);
        e_menu_item_callback_set(mi, _e_fm2_view_menu_grid_icons_cb, sd);

        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Custom Icons"));
        e_menu_item_radio_group_set(mi, 1);
        e_menu_item_radio_set(mi, 1);
        if (view_mode == E_FM2_VIEW_MODE_CUSTOM_ICONS)
          e_menu_item_toggle_set(mi, 1);
        e_menu_item_callback_set(mi, _e_fm2_view_menu_custom_icons_cb, sd);

        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("List"));
        e_menu_item_radio_group_set(mi, 1);
        e_menu_item_radio_set(mi, 1);
        if (view_mode == E_FM2_VIEW_MODE_LIST)
          e_menu_item_toggle_set(mi, 1);
        e_menu_item_callback_set(mi, _e_fm2_view_menu_list_cb, sd);

        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Default View"));
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, sd->view_mode == -1);
        e_menu_item_callback_set(mi, _e_fm2_view_menu_use_default_cb, sd);

        mi = e_menu_item_new(subm);
        e_menu_item_separator_set(mi, 1);
     }

   if (view_mode == E_FM2_VIEW_MODE_LIST)
     return;

   mi = e_menu_item_new(subm);
   e_menu_item_separator_set(mi, 1);

   icon_size = _e_fm2_icon_w_get(sd);

   // show the icon size as selected (even if it might be influnced by e_scale)
   /* if (e_scale > 0.0)
    *   icon_size /= e_scale; */

   snprintf(buf, sizeof(buf), _("Icon Size (%d)"), icon_size);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, buf);
   e_menu_item_submenu_pre_callback_set(mi, _e_fm2_view_menu_icon_size_pre, sd);
}

static void
_e_fm2_icon_view_menu_pre(void *data, E_Menu *subm)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (subm->items) return;
   _e_fm2_view_menu_common(subm, sd);
}

static void
_e_fm2_new_dir_notify(void *data, Ecore_Thread *eth EINA_UNUSED, char *filename)
{
   E_Fm2_Smart_Data *sd = data;

   if (!sd->new_file.done)
     {
        if (filename)
          sd->new_file.filename = eina_stringshare_add(ecore_file_file_get(filename));
        else
          e_util_dialog_internal(_("Error"), _("Could not create a directory!"));
     }
   free(filename);
}

static void
_e_fm2_new_file_notify(void *data, Ecore_Thread *eth EINA_UNUSED, char *filename)
{
   E_Fm2_Smart_Data *sd = data;

   if (!sd->new_file.done)
     {
        if (filename)
          sd->new_file.filename = eina_stringshare_add(ecore_file_file_get(filename));
        else
          e_util_dialog_internal(_("Error"), _("Could not create a file!"));
     }
   free(filename);
}

static void
_e_fm2_new_thread_helper(Ecore_Thread *eth, Eina_Bool dir)
{
   char buf[PATH_MAX];
   char *path;
   struct stat st;
   unsigned int x;
   int fd;
   Eina_List *texts, *l;

   path = ecore_thread_global_data_wait("path", 2.0);
   snprintf(buf, sizeof(buf), "%s/%s", path, dir ? _("New Directory") : _("New File"));
   errno = 0;
   if (stat(buf, &st) && (errno == ENOENT))
     {
        if (dir)
          {
             if (ecore_file_mkdir(buf))
               {
                  ecore_thread_global_data_set("efm_pending_filename", strdup(buf), free);
                  ecore_thread_feedback(eth, strdup(buf));
                  return;
               }
          }
        else
          {
             fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
             if (fd)
               {
                  texts = ecore_thread_global_data_wait("efm_text_uri_list", 0.01);
                  EINA_LIST_FOREACH(texts, l, path)
                    {
                       if (write(fd, path, strlen(path)) < 0)
                         perror("write");
                       if (write(fd, "\n", 1) < 0)
                         perror("write");
                    }
                  close(fd);
                  ecore_thread_global_data_set("efm_pending_filename", strdup(buf), free);
                  ecore_thread_feedback(eth, strdup(buf));
                  return;
               }
          }
        goto error;
     }
   else if (errno)
     goto error;
   for (x = 0; x < UINT_MAX; x++)
     {
        snprintf(buf, sizeof(buf), "%s/%s %u", path, dir ? _("New Directory") : _("New File"), x);
        errno = 0;
        if (stat(buf, &st) && (errno == ENOENT))
          {
             if (dir)
               {
                  if (ecore_file_mkdir(buf))
                    {
                       ecore_thread_global_data_set("efm_pending_filename", strdup(buf), free);
                       ecore_thread_feedback(eth, strdup(buf));
                       return;
                    }
               }
             else
               {
                  fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
                  if (fd)
                    {
                       texts = ecore_thread_global_data_wait("efm_text_uri_list", 0.01);
                       EINA_LIST_FOREACH(texts, l, path)
                         {
                            if (write(fd, path, strlen(path)) < 0)
                              perror("write");
                            if (write(fd, "\n", 1) < 0)
                              perror("write");
                         }
                       close(fd);
                       ecore_thread_global_data_set("efm_pending_filename", strdup(buf), free);
                       ecore_thread_feedback(eth, strdup(buf));
                       return;
                    }
               }
             goto error;
          }
        else if (errno)
          goto error;
     }
error:
   ecore_thread_feedback(eth, NULL);
}

static void
_e_fm2_new_file_thread(void *data EINA_UNUSED, Ecore_Thread *eth)
{
   _e_fm2_new_thread_helper(eth, EINA_FALSE);
}

static void
_e_fm2_new_dir_thread(void *data EINA_UNUSED, Ecore_Thread *eth)
{
   _e_fm2_new_thread_helper(eth, EINA_TRUE);
}

static void
_e_fm2_new_file_end(void *data, Ecore_Thread *eth EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   sd->new_file.thread = NULL;
   ecore_thread_global_data_del("path");
   ecore_thread_global_data_del("efm_text_uri_list");
   evas_object_unref(sd->obj);
   e_fm2_refresh(sd->obj);
}

static void
_e_fm2_new_file_cancel(void *data, Ecore_Thread *eth EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;
   sd->new_file.thread = NULL;
   ecore_thread_global_data_del("path");
   ecore_thread_global_data_del("efm_text_uri_list");
   evas_object_unref(sd->obj);
}

static void
_e_fm2_new_file(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (sd->new_file.thread || sd->new_file.filename)
     {
        e_util_dialog_internal(_("Error"), _("Already creating a new file for this directory!"));
        return;
     }
   if (!ecore_file_can_write(sd->realpath))
     {
        e_util_dialog_show(_("Error"), _("%s can't be written to!"), sd->realpath);
        return;
     }
   sd->new_file.thread = ecore_thread_feedback_run(_e_fm2_new_file_thread, (Ecore_Thread_Notify_Cb)_e_fm2_new_file_notify,
                                                   _e_fm2_new_file_end, _e_fm2_new_file_cancel, sd, EINA_FALSE);
   ecore_thread_global_data_add("path", (void *)eina_stringshare_ref(sd->realpath), (void *)eina_stringshare_del, EINA_FALSE);
   evas_object_ref(sd->obj);
}

static void
_e_fm2_new_directory(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (sd->new_file.thread || sd->new_file.filename)
     {
        e_util_dialog_internal(_("Error"), _("Already creating a new file for this directory!"));
        return;
     }
   if (!ecore_file_can_write(sd->realpath))
     {
        e_util_dialog_show(_("Error"), _("%s can't be written to!"), sd->realpath);
        return;
     }
   sd->new_file.thread = ecore_thread_feedback_run(_e_fm2_new_dir_thread, (Ecore_Thread_Notify_Cb)_e_fm2_new_dir_notify,
                                                   _e_fm2_new_file_end, _e_fm2_new_file_cancel, sd, EINA_FALSE);
   ecore_thread_global_data_add("path", (void *)eina_stringshare_ref(sd->realpath), (void *)eina_stringshare_del, EINA_FALSE);
   evas_object_ref(sd->obj);
}

static void
_e_fm2_add_menu_pre(void *data, E_Menu *subm)
{
   E_Menu_Item *mi;
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (subm->items) return;

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Directory"));
   e_util_menu_item_theme_icon_set(mi, "folder-new");
   e_menu_item_callback_set(mi, _e_fm2_new_directory, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("File"));
   e_util_menu_item_theme_icon_set(mi, "document-new");
   e_menu_item_callback_set(mi, _e_fm2_new_file, sd);
}

static void
_e_fm2_settings_icon_item(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_configure_registry_call("fileman/file_icons", NULL, NULL);
}

static void
_e_fm2_settings_item(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_configure_registry_call("fileman/fileman", NULL, NULL);
}

static void
_e_fm2_options_menu_pre(void *data, E_Menu *subm)
{
   E_Fm2_Smart_Data *sd;
   E_Menu_Item *mi;

   sd = data;

   if (subm->items) return;

   if ((!(sd->icon_menu.flags & E_FM2_MENU_NO_INHERIT_PARENT)) &&
       (sd->view_flags & E_FM2_VIEW_INHERIT_DIR_CUSTOM))
     {
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Inherit parent settings"));
        e_util_menu_item_theme_icon_set(mi, "view-inherit");
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, sd->inherited_dir_props);
        e_menu_item_callback_set(mi, _e_fm2_toggle_inherit_dir_props, sd);
     }
   if (!(sd->icon_menu.flags & E_FM2_MENU_NO_SHOW_HIDDEN))
     {
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Show Hidden Files"));
        e_util_menu_item_theme_icon_set(mi, "view-refresh");
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, sd->show_hidden_files);
        e_menu_item_callback_set(mi, _e_fm2_toggle_hidden_files, sd);
     }

   if (!(sd->icon_menu.flags & E_FM2_MENU_NO_REMEMBER_ORDERING))
     {
        if (!sd->config->view.always_order)
          {
             mi = e_menu_item_new(subm);
             e_menu_item_label_set(mi, _("Remember Ordering"));
             e_util_menu_item_theme_icon_set(mi, "view-order");
             e_menu_item_check_set(mi, 1);
             e_menu_item_toggle_set(mi, sd->order_file);
             e_menu_item_callback_set(mi, _e_fm2_toggle_ordering, sd);
          }
        if ((sd->order_file) || (sd->config->view.always_order))
          {
             mi = e_menu_item_new(subm);
             e_menu_item_label_set(mi, _("Sort Now"));
             e_util_menu_item_theme_icon_set(mi, "view-sort");
             e_menu_item_callback_set(mi, _e_fm2_sort, sd);
          }
     }
   if (!(sd->icon_menu.flags & E_FM2_MENU_NO_ACTIVATE_CHANGE))
     {
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Single Click Activation"));
        e_util_menu_item_theme_icon_set(mi, "access");
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, sd->config->view.single_click);
        e_menu_item_callback_set(mi, _e_fm2_toggle_single_click, sd);
     }

   if (!e_config->filemanager_secure_rm)
     {
        /* can't disable this if it's globally enabled */
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Secure Deletion"));
        e_util_menu_item_theme_icon_set(mi, "security-high");
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, e_config->filemanager_secure_rm | sd->config->secure_rm);
        e_menu_item_callback_set(mi, _e_fm2_toggle_secure_rm, sd);
     }

   if (!e_configure_registry_exists("fileman/fileman")) return;

   mi = e_menu_item_new(subm);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("File Manager Settings"));
   e_util_menu_item_theme_icon_set(mi, "system-file-manager");
   e_menu_item_callback_set(mi, _e_fm2_settings_item, sd);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("File Icon Settings"));
   e_util_menu_item_theme_icon_set(mi, "preferences-file-icons");
   e_menu_item_callback_set(mi, _e_fm2_settings_icon_item, sd);
}

static void
_custom_file_key_del(E_Fm2_Smart_Data *sd, const char *key)
{
   Efreet_Desktop *ef;
   char buf[PATH_MAX];

   if (sd->desktop) ef = sd->desktop;
   else
     {
        snprintf(buf, sizeof(buf), "%s/.directory.desktop", sd->realpath);
        ef = efreet_desktop_new(buf);
     }
   if (!ef) return;

   if (efreet_desktop_x_field_del(ef, key))
     efreet_desktop_save(ef);

   if (!sd->desktop) efreet_desktop_free(ef);
}

static void
_e_fm2_view_menu_del(void *data)
{
   E_Fm2_Smart_Data *sd = e_object_data_get(data);

   if (!sd) return;
   if (sd->image_dialog) return;
   if (!sd->desktop) return;
   efreet_desktop_free(sd->desktop);
   sd->desktop = NULL;
}

static void
_clear_background_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (!sd) return;

   _custom_file_key_del(sd, "X-Enlightenment-Directory-Wallpaper");
   evas_object_smart_callback_call(sd->obj, "dir_changed", NULL);
}

static void
_clear_overlay_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (!sd) return;

   _custom_file_key_del(sd, "X-Enlightenment-Directory-Overlay");
   evas_object_smart_callback_call(sd->obj, "dir_changed", NULL);
}

static void
_e_fm2_view_menu_pre(void *data, E_Menu *subm)
{
   E_Fm2_Smart_Data *sd = data;
   E_Menu_Item *mi;

   if (subm->items) return;

   _e_fm2_view_menu_common(subm, sd);

   if (_e_fm2_desktop_open(sd) < 0) return;
   e_object_data_set(E_OBJECT(subm), sd);
   e_object_del_attach_func_set(E_OBJECT(subm), _e_fm2_view_menu_del);

   if (e_menu_item_nth(subm, 0) != NULL)
     {
        mi = e_menu_item_new(subm);
        e_menu_item_separator_set(mi, 1);
     }

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Set background..."));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-wallpaper");
   e_menu_item_callback_set(mi, _e_fm2_view_menu_set_background_cb, sd);

   while (sd->desktop)
     {
        if (!eina_hash_find(sd->desktop->x, "X-Enlightenment-Directory-Wallpaper")) break;
        mi = e_menu_item_new(subm);
        e_menu_item_label_set(mi, _("Clear background"));
        e_util_menu_item_theme_icon_set(mi, "preferences-desktop-wallpaper");
        e_menu_item_callback_set(mi, _clear_background_cb, sd);
        break;
     }

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Set overlay..."));
   e_menu_item_callback_set(mi, _e_fm2_view_menu_set_overlay_cb, sd);

   if (!sd->desktop) return;
   if (!eina_hash_find(sd->desktop->x, "X-Enlightenment-Directory-Overlay")) return;
   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Clear overlay"));
   e_menu_item_callback_set(mi, _clear_overlay_cb, sd);
}

static void
_e_fm2_view_menu_grid_icons_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   char old;

   old = _e_fm2_view_mode_get(sd);
   sd->view_mode = E_FM2_VIEW_MODE_GRID_ICONS;
   sd->inherited_dir_props = EINA_FALSE;
   if (old == E_FM2_VIEW_MODE_GRID_ICONS)
     return;

   _e_fm2_refresh(sd, m, mi);
}

static void
_e_fm2_view_menu_custom_icons_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   char old;

   old = _e_fm2_view_mode_get(sd);
   sd->view_mode = E_FM2_VIEW_MODE_CUSTOM_ICONS;
   sd->inherited_dir_props = EINA_FALSE;
   if (old == E_FM2_VIEW_MODE_CUSTOM_ICONS)
     return;

   _e_fm2_refresh(sd, m, mi);
}

static void
_e_fm2_view_menu_list_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   char old;

   old = _e_fm2_view_mode_get(sd);
   sd->view_mode = E_FM2_VIEW_MODE_LIST;
   sd->inherited_dir_props = EINA_FALSE;
   if (old == E_FM2_VIEW_MODE_LIST)
     return;

   _e_fm2_refresh(sd, m, mi);
}

static void
_e_fm2_view_menu_use_default_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd = data;
   char old, new;

   old = _e_fm2_view_mode_get(sd);

   if (sd->view_mode == -1)
     sd->view_mode = sd->config->view.mode;
   else
     sd->view_mode = -1;

   new = _e_fm2_view_mode_get(sd);
   sd->inherited_dir_props = EINA_FALSE;

   if (new == old)
     return;

   _e_fm2_refresh(sd, m, mi);
}

static void
_image_sel_del(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = e_object_data_get(data);
   if (!sd) return;
   sd->image_dialog = NULL;
   if (sd->desktop) efreet_desktop_free(sd->desktop);
   sd->desktop = NULL;
}

static void
_e_fm2_view_image_sel(E_Fm2_Smart_Data *sd, const char *title,
                      void (*ok_cb)(void *data, E_Dialog *dia))
{
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord w, h;

   dia = e_dialog_new(NULL, "E", "_fm2_view_image_select_dialog");
   if (!dia) return;
   e_dialog_resizable_set(dia, 1);
   e_dialog_title_set(dia, title);

   o = e_widget_fsel_add(evas_object_evas_get(dia->win), "/", sd->realpath, NULL, NULL, NULL, sd, NULL, sd, 1);
   evas_object_show(o);
   e_widget_size_min_get(o, &w, &h);
   e_dialog_content_set(dia, o, w, h);
   dia->data = o;

   e_dialog_button_add(dia, _("OK"), NULL, ok_cb, sd);
   e_dialog_button_add(dia, _("Cancel"), NULL, _e_fm2_view_image_sel_close, sd);
   elm_win_center(dia->win, 1, 1);
   e_object_data_set(E_OBJECT(dia), sd);
   e_object_del_attach_func_set(E_OBJECT(dia), _image_sel_del);
   e_dialog_show(dia);

   sd->image_dialog = dia;
}

static void
_e_fm2_view_image_sel_close(void *data, E_Dialog *dia)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   e_object_del(E_OBJECT(dia));
   sd->image_dialog = NULL;
}

static int
_e_fm2_desktop_open(E_Fm2_Smart_Data *sd)
{
   Efreet_Desktop *ef;
   char buf[PATH_MAX];
   Eina_Bool ret;

   snprintf(buf, sizeof(buf), "%s/.directory.desktop", sd->realpath);
   if (sd->desktop)
     {
        if (!e_util_strcmp(buf, sd->desktop->orig_path)) return 1;
     }
   ret = ecore_file_exists(buf) ? ecore_file_can_write(buf)
     : ecore_file_can_write(sd->realpath);
   if (!ret) return -1;
   ef = efreet_desktop_new(buf);
   if (!ef) return 0;
   efreet_desktop_free(sd->desktop);
   sd->desktop = ef;
   return 1;
}

static void
_custom_file_key_set(E_Fm2_Smart_Data *sd, const char *key, const char *value)
{
   Efreet_Desktop *ef;
   char buf[PATH_MAX];
   int len;

   if (sd->desktop) ef = sd->desktop;
   else
     {
        snprintf(buf, sizeof(buf), "%s/.directory.desktop", sd->realpath);
        ef = efreet_desktop_new(buf);
        if (!ef)
          {
             ef = efreet_desktop_empty_new(buf);
             if (!ef) return;
             ef->type = EFREET_DESKTOP_TYPE_DIRECTORY;
             ef->name = strdup("Directory look and feel");
          }
     }

   len = strlen(sd->realpath);
   if (!strncmp(value, sd->realpath, len))
     efreet_desktop_x_field_set(ef, key, value + len + 1);
   else
     efreet_desktop_x_field_set(ef, key, value);

   efreet_desktop_save(ef);
   if (!sd->desktop) efreet_desktop_free(ef);
}

static void
_set_background_cb(void *data, E_Dialog *dia)
{
   E_Fm2_Smart_Data *sd;
   const char *file;

   sd = data;
   if (!sd) return;

   file = e_widget_fsel_selection_path_get(dia->data);

   if (file)
     _custom_file_key_set(sd, "X-Enlightenment-Directory-Wallpaper", file);

   _e_fm2_view_image_sel_close(data, dia);
   evas_object_smart_callback_call(sd->obj, "dir_changed", NULL);
}

static void
_e_fm2_view_menu_set_background_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (sd->image_dialog) return;

   _e_fm2_view_image_sel(sd, _("Set background..."), _set_background_cb);
}

static void
_set_overlay_cb(void *data, E_Dialog *dia)
{
   E_Fm2_Smart_Data *sd;
   const char *file;

   sd = data;
   if (!sd) return;

   file = e_widget_fsel_selection_path_get(dia->data);

   if (file)
     _custom_file_key_set(sd, "X-Enlightenment-Directory-Overlay", file);

   _e_fm2_view_image_sel_close(data, dia);
   evas_object_smart_callback_call(sd->obj, "dir_changed", NULL);
}

static void
_e_fm2_view_menu_set_overlay_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   if (sd->image_dialog) return;

   _e_fm2_view_image_sel(sd, _("Set overlay..."), _set_overlay_cb);
}

static void
_e_fm2_refresh(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (sd->refresh_job) ecore_job_del(sd->refresh_job);
   sd->refresh_job = ecore_job_add(_e_fm2_refresh_job_cb, sd->obj);
}

static void
_e_fm2_toggle_hidden_files(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   if (sd->show_hidden_files)
     sd->show_hidden_files = EINA_FALSE;
   else
     sd->show_hidden_files = EINA_TRUE;

   sd->inherited_dir_props = EINA_FALSE;
   _e_fm2_refresh(data, m, mi);
}

static void
_e_fm2_toggle_secure_rm(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd = data;

   sd->config->secure_rm = !sd->config->secure_rm;
}

static void
_e_fm2_toggle_single_click(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   sd->config->view.single_click = !sd->config->view.single_click;
}

static void
_e_fm2_toggle_ordering(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd;
   char buf[4096];

   sd = data;
   if (sd->order_file)
     {
        snprintf(buf, sizeof(buf), "%s/.order", sd->realpath);
        /* FIXME: move to e_fm_main */
        ecore_file_unlink(buf);
     }
   else
     {
        FILE *f;

        snprintf(buf, sizeof(buf), "%s/.order", sd->realpath);
        f = fopen(buf, "w");
        if (f) fclose(f);
     }
   sd->inherited_dir_props = EINA_FALSE;
   _e_fm2_refresh(data, m, mi);
}

static void
_e_fm2_sort(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Fm2_Smart_Data *sd;

   sd = data;
   sd->icons = eina_list_sort(sd->icons, eina_list_count(sd->icons),
                              _e_fm2_cb_icon_sort);
   _e_fm2_refresh(data, m, mi);
}

static void
_e_fm2_file_rename(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Icon *ic;
   char text[PATH_MAX + 256];

   ic = data;
   if ((ic->entry_dialog) || (ic->entry_widget)) return;
   if (ic->sd->icon_menu.flags & E_FM2_MENU_NO_RENAME) return;

   if (!_e_fm2_icon_entry_widget_add(ic))
     {
        snprintf(text, PATH_MAX + 256,
                 _("Rename %s to:"),
                 ic->info.file);
        ic->entry_dialog =
          e_entry_dialog_show(NULL, _("Rename File"), "edit-rename",
                              text, ic->info.file, NULL, NULL,
                              _e_fm2_file_rename_yes_cb,
                              _e_fm2_file_rename_no_cb, ic);
        E_OBJECT(ic->entry_dialog)->data = ic;
        e_object_del_attach_func_set(E_OBJECT(ic->entry_dialog),
                                     _e_fm2_file_rename_delete_cb);
     }
}

/* FIXME: this is a stupid hack because it's impossible to get a focus event
 * from e_widget_entry.
 */
static void
_e_fm2_icon_entry_widget_focus_out(void *data, E_Client *ec)
{
   E_Fm2_Icon *ic = data;
   Evas_Object *win;

   win = e_win_evas_win_get(evas_object_evas_get(ic->obj));
   if ((win != e_comp->elm) && (ec == e_win_client_get(win))) return;
   if (ic->entry_widget)
     _e_fm2_icon_entry_widget_del(ic);
}

static Evas_Object *
_e_fm2_icon_entry_widget_add(E_Fm2_Icon *ic)
{
   Evas *e;

   if (ic->sd->iop_icon)
     _e_fm2_icon_entry_widget_accept(ic->sd->iop_icon);

   if (!edje_object_part_exists(ic->obj, "e.swallow.entry"))
     return NULL;

   e = evas_object_evas_get(ic->obj);
   ic->entry_widget = e_widget_entry_add(e_win_evas_win_get(e), NULL, NULL, NULL, NULL);
   evas_object_event_callback_add(ic->entry_widget, EVAS_CALLBACK_KEY_DOWN,
                                  _e_fm2_icon_entry_widget_cb_key_down, ic);
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     evas_event_feed_mouse_out(evas_object_evas_get(ic->obj), 0, NULL);
   if (e_comp->evas == e)
     e_comp_grab_input(0, 1);
   ic->keygrab = (e_comp->evas == e);
   edje_object_part_swallow(ic->obj, "e.swallow.entry", ic->entry_widget);
   evas_object_show(ic->entry_widget);
   edje_object_signal_emit(ic->obj, "e,state,rename,on", "e");
   e_widget_entry_text_set(ic->entry_widget, ic->info.file);
   e_widget_focus_set(ic->entry_widget, 1);
   ic->focus_hook = e_client_hook_add(E_CLIENT_HOOK_FOCUS_SET, _e_fm2_icon_entry_widget_focus_out, ic);
   char *dot = strchr(ic->info.file, '.');
   if (dot)
     {
        int end = (int)((long)(dot - ic->info.file));
        printf("SEL: %i -> %i\n", 0, end);
        e_widget_entry_select_set(ic->entry_widget, 0,
                                  (int)((long)(dot - ic->info.file)));
     }
   else
     e_widget_entry_select_all(ic->entry_widget);
   ic->sd->iop_icon = ic;
   ic->sd->typebuf.disabled = EINA_TRUE;
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     evas_event_feed_mouse_in(e, 0, NULL);
   return ic->entry_widget;
}

static void
_e_fm2_icon_entry_widget_del(E_Fm2_Icon *ic)
{
   ic->sd->iop_icon = NULL;
   evas_object_focus_set(ic->sd->obj, 1);
   evas_object_del(ic->entry_widget);
   ic->entry_widget = NULL;
   E_FREE_FUNC(ic->focus_hook, e_client_hook_del);
   ic->sd->typebuf.disabled = EINA_FALSE;
   if (ic->keygrab)
     {
        if (evas_object_evas_get(ic->obj) == e_comp->evas)
          e_comp_ungrab_input(0, 1);
     }
   ic->keygrab = 0;
   _e_fm2_icon_select(ic);
   edje_object_signal_emit(ic->obj, "e,state,rename,off", "e");
}

static void
_e_fm2_icon_entry_widget_cb_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Key_Down *ev;
   E_Fm2_Icon *ic;

   ev = event_info;
   ic = data;

   if (!strcmp(ev->key, "Escape"))
     _e_fm2_icon_entry_widget_del(ic);
   else if ((!strcmp(ev->key, "Return")) || (!strcmp(ev->key, "KP_Enter")))
     _e_fm2_icon_entry_widget_accept(ic);
}

static void
_e_fm2_icon_entry_widget_accept(E_Fm2_Icon *ic)
{
   const char *txt;

   txt = e_widget_entry_text_get(ic->entry_widget);
   switch (_e_fm2_file_do_rename(txt, ic))
     {
      case -2:
        e_util_dialog_show(_("Error"), _("%s already exists!"), txt);
        return;

      case -1:
        e_util_dialog_show(_("Error"), _("%s could not be renamed because it is protected"), ic->info.file);
        break;

      case 0:
        e_util_dialog_show(_("Error"), _("Internal filemanager error :("));

      default: /* success! */
        break;
     }
   _e_fm2_icon_entry_widget_del(ic);
}

static void
_e_fm2_file_rename_delete_cb(void *obj)
{
   E_Fm2_Icon *ic;

   ic = E_OBJECT(obj)->data;
   ic->entry_dialog = NULL;
}

static void
_e_fm2_file_rename_yes_cb(void *data, char *text)
{
   E_Fm2_Icon *ic;

   ic = data;
   ic->entry_dialog = NULL;

   switch (_e_fm2_file_do_rename(text, ic))
     {
      case -2:
        e_util_dialog_show(_("Error"), _("%s already exists!"), text);
        _e_fm2_file_rename(ic, NULL, NULL);
        break;

      case -1:
        e_util_dialog_show(_("Error"), _("%s could not be renamed because it is protected"), ic->info.file);
        break;

      case 0:
        e_util_dialog_show(_("Error"), _("Internal filemanager error :("));

      default: /* success! */
        break;
     }
}

static void
_e_fm2_file_rename_no_cb(void *data)
{
   E_Fm2_Icon *ic;

   ic = data;
   ic->entry_dialog = NULL;
}

static int
_e_fm2_file_do_rename(const char *text, E_Fm2_Icon *ic)
{
   char oldpath[PATH_MAX];
   char newpath[PATH_MAX];
   char *args = NULL;
   size_t size = 0;
   size_t length = 0;

   if (!text) return 0;
   if (!strcmp(text, ic->info.file)) return EINA_TRUE;
   _e_fm2_icon_realpath(ic, oldpath, sizeof(oldpath));
   snprintf(newpath, sizeof(newpath), "%s/%s", ic->sd->realpath, text);
   if (e_filereg_file_protected(oldpath)) return -1;
   if (ecore_file_exists(newpath)) return -2;

   args = e_util_string_append_quoted(args, &size, &length, oldpath);
   if (!args) return 0;
   args = e_util_string_append_char(args, &size, &length, ' ');
   if (!args) return 0;
   args = e_util_string_append_quoted(args, &size, &length, newpath);
   if (!args) return 0;

   e_fm2_client_file_move(ic->sd->obj, args);
   free(args);
   return 1;
}

static E_Dialog *
_e_fm_retry_abort_dialog(int pid, const char *str)
{
   E_Dialog *dialog;
   void *id;
   char text[4096 + PATH_MAX];

   id = (intptr_t *)(long)pid;

   dialog = e_dialog_new(NULL, "E", "_fm_overwrite_dialog");
   E_OBJECT(dialog)->data = id;
   e_dialog_button_add(dialog, _("Retry"), NULL, _e_fm_retry_abort_retry_cb, NULL);
   e_dialog_button_add(dialog, _("Abort"), NULL, _e_fm_retry_abort_abort_cb, NULL);

   e_dialog_button_focus_num(dialog, 0);
   e_dialog_title_set(dialog, _("Error"));
   e_dialog_icon_set(dialog, "dialog-error", 64);
   snprintf(text, sizeof(text),
            "%s",
            str);

   e_dialog_text_set(dialog, text);
   elm_win_center(dialog->win, 1, 1);
   e_dialog_show(dialog);
   return dialog;
}

static void
_e_fm_retry_abort_retry_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_RETRY, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_retry_abort_abort_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_aborted(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_ABORT, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static E_Dialog *
_e_fm_overwrite_dialog(int pid, const char *str)
{
   E_Dialog *dialog;
   void *id;
   char text[4096 + PATH_MAX];
   E_Fm2_Op_Registry_Entry *ere;

   id = (intptr_t *)(long)pid;
   ere = e_fm2_op_registry_entry_get(pid);
   if (ere)
     {
        E_Fm2_Smart_Data *sd;
        Eina_List *fms;
        Evas_Object *fm;

        fms = _e_fm2_file_fm2_find(ere->src);
        EINA_LIST_FREE(fms, fm)
        {
           sd = evas_object_smart_data_get(fm);
           E_LIST_FOREACH(sd->icons, _e_fm2_cb_drag_finished_show);
        }
     }

   dialog = e_dialog_new(NULL, "E", "_fm_overwrite_dialog");
   E_OBJECT(dialog)->data = id;
   e_dialog_button_add(dialog, _("No"), NULL, _e_fm_overwrite_no_cb, NULL);
   e_dialog_button_add(dialog, _("No to all"), NULL, _e_fm_overwrite_no_all_cb, NULL);
   e_dialog_button_add(dialog, _("Rename"), NULL, _e_fm_overwrite_rename, NULL);
   e_dialog_button_add(dialog, _("Yes"), NULL, _e_fm_overwrite_yes_cb, NULL);
   e_dialog_button_add(dialog, _("Yes to all"), NULL, _e_fm_overwrite_yes_all_cb, NULL);

   e_dialog_button_focus_num(dialog, 0);
   e_dialog_title_set(dialog, _("Warning"));
   e_dialog_icon_set(dialog, "dialog-warning", 64);
   snprintf(text, sizeof(text),
            _("File already exists, overwrite?<ps/><hilight>%s</hilight>"), str);

   e_dialog_text_set(dialog, text);
   elm_win_center(dialog->win, 1, 1);
   e_dialog_show(dialog);
   return dialog;
}

static void
_e_fm_overwrite_no_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_OVERWRITE_RESPONSE_NO, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_overwrite_no_all_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_OVERWRITE_RESPONSE_NO_ALL, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_overwrite_rename_del(void *data)
{
   E_Fm2_Op_Registry_Entry *ere;
   E_Fm2_Smart_Data *sd;

   ere = e_object_data_get(data);
   if (!ere) return;
   sd = evas_object_smart_data_get(ere->e_fm);
   if (!sd) return;
   sd->rename_dialogs = eina_list_remove(sd->rename_dialogs, data);
   e_fm2_op_registry_entry_unref(ere);
   _e_fm_client_send(E_FM_OP_OVERWRITE_RESPONSE_NO, ere->id, NULL, 0);
}

static void
_e_fm_overwrite_rename_yes_cb(void *data, char *text)
{
   E_Fm2_Op_Registry_Entry *ere = data;
   char newpath[PATH_MAX];
   char *args = NULL;
   const char *f;
   size_t size = 0;
   size_t length = 0;

   if ((!text) || (!text[0])) return;
   if ((!ere->src) || (!ere->dst)) return;
   f = ecore_file_file_get(ere->dst);
   if (!f) return;
   length = strlen(text);
   if (f - ere->dst + length >= PATH_MAX) return;

   newpath[0] = 0;
   strncat(newpath, ere->dst, f - ere->dst);
   strcat(newpath, text);
   if (e_filereg_file_protected(newpath)) return;
   length = 0;
   args = e_util_string_append_quoted(args, &size, &length, ere->src);
   if (!args) return;
   args = e_util_string_append_char(args, &size, &length, ' ');
   if (!args) return;
   args = e_util_string_append_quoted(args, &size, &length, newpath);
   if (!args) return;

   e_fm2_client_file_copy(ere->e_fm, args);
   free(args);
}

static void
_e_fm_overwrite_rename(void *data EINA_UNUSED, E_Dialog *dialog)
{
   char text[PATH_MAX + 256];
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   E_Fm2_Op_Registry_Entry *ere;
   E_Entry_Dialog *ed;
   E_Fm2_Smart_Data *sd;
   const char *file;

   ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;
   sd = evas_object_smart_data_get(ere->e_fm);
   e_object_del(E_OBJECT(dialog));
   file = ecore_file_file_get(ere->src);
   snprintf(text, sizeof(text), _("Rename %s to:"), file);
   ed = e_entry_dialog_show(NULL, _("Rename File"), "edit-rename",
                            text, file, NULL, NULL,
                            _e_fm_overwrite_rename_yes_cb,
                            NULL, ere);
   sd->rename_dialogs = eina_list_append(sd->rename_dialogs, ed);
   e_object_del_attach_func_set(E_OBJECT(ed), _e_fm_overwrite_rename_del);
   E_OBJECT(ed)->data = ere;
   e_fm2_op_registry_entry_ref(ere);
   _e_fm2_op_registry_go_on(id);
}

static void
_e_fm_overwrite_yes_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_OVERWRITE_RESPONSE_YES, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_overwrite_yes_all_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_OVERWRITE_RESPONSE_YES_ALL, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static E_Dialog *
_e_fm_error_dialog(int pid, const char *str)
{
   E_Dialog *dialog;
   void *id;
   char text[4096 + PATH_MAX];
   E_Fm2_Op_Registry_Entry *ere;
   E_Fm2_Smart_Data *sd;
   E_Fm2_Mount *m;
   Eina_Bool devlink = EINA_FALSE;

   id = (intptr_t *)(long)pid;
   ere = e_fm2_op_registry_entry_get(pid);
   if (!ere) return NULL;
   sd = evas_object_smart_data_get(ere->e_fm);
   E_LIST_FOREACH(sd->icons, _e_fm2_cb_drag_finished_show);
   while (sd->realpath)
     {
        /* trying to make or move a link onto a device will fail, create button for
         * moving source at this point if this is what failed
         */
        struct stat st;
        if (sd->config->view.link_drop) break;
        m = e_fm2_device_mount_find(sd->realpath);
        if (!m) break;
        if (ere->op == E_FM_OP_SYMLINK)
          {
             devlink = EINA_TRUE;
             break;
          }
        if (stat(ere->src, &st)) break;
        if (S_ISLNK(st.st_mode)) devlink = EINA_TRUE;
        break;
     }

   dialog = e_dialog_new(NULL, "E", "_fm_error_dialog");
   E_OBJECT(dialog)->data = id;
   e_dialog_button_add(dialog, _("Retry"), NULL, _e_fm_error_retry_cb, NULL);
   e_dialog_button_add(dialog, _("Abort"), NULL, _e_fm_error_abort_cb, NULL);
   if (devlink)
     e_dialog_button_add(dialog, _("Move Source"), NULL, _e_fm_error_link_source, ere);
   e_dialog_button_add(dialog, _("Ignore this"), NULL, _e_fm_error_ignore_this_cb, NULL);
   e_dialog_button_add(dialog, _("Ignore all"), NULL, _e_fm_error_ignore_all_cb, NULL);

   e_dialog_button_focus_num(dialog, 0);
   e_dialog_title_set(dialog, _("Error"));
   snprintf(text, sizeof(text),
            _("An error occurred while performing an operation.<ps/>"
              "%s"),
            str);

   e_dialog_text_set(dialog, text);
   elm_win_center(dialog->win, 1, 1);
   e_dialog_show(dialog);
   return dialog;
}

static void
_e_fm_error_retry_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_RETRY, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_error_abort_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_aborted(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_ABORT, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_error_link_source(void *data, E_Dialog *dialog)
{
   E_Fm2_Op_Registry_Entry *ere = data;
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   char *file;
   char newpath[PATH_MAX];
   char *args = NULL;
   const char *f;
   size_t size = 0;
   size_t length = 0;

   file = ecore_file_readlink(ere->src);
   if (!file) file = (char *)ere->src;

   f = ecore_file_file_get(file);
   if (!f) return;
   length = strlen(f);
   if (strlen(ere->dst) + length >= PATH_MAX) return;

   newpath[0] = 0;
   strncat(newpath, ere->dst, f - ere->dst);
   strcat(newpath, f);
   if (e_filereg_file_protected(newpath)) return;
   length = 0;
   args = e_util_string_append_quoted(args, &size, &length, file);
   if (!args) return;
   args = e_util_string_append_char(args, &size, &length, ' ');
   if (!args) return;
   args = e_util_string_append_quoted(args, &size, &length, newpath);
   if (!args) return;

   e_fm2_client_file_move(ere->e_fm, args);
   free(args);
   if (file != ere->src) free(file);

   _e_fm2_op_registry_aborted(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_ABORT, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_error_ignore_this_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_IGNORE_THIS, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_error_ignore_all_cb(void *data EINA_UNUSED, E_Dialog *dialog)
{
   int id = (int)(intptr_t)E_OBJECT(dialog)->data;
   _e_fm2_op_registry_go_on(id);
   _e_fm_client_send(E_FM_OP_ERROR_RESPONSE_IGNORE_ALL, id, NULL, 0);
   e_object_del(E_OBJECT(dialog));
}

static void
_e_fm_device_error_dialog(const char *title, const char *msg, const char *pstr)
{
   E_Dialog *dialog;
   char text[PATH_MAX];
   const char *u, *d, *n, *m;

   dialog = e_dialog_new(NULL, "E", "_fm_device_error_dialog");
   e_dialog_title_set(dialog, title);
   e_dialog_icon_set(dialog, "drive-harddisk", 64);
   e_dialog_button_add(dialog, _("OK"), NULL, NULL, NULL);

   u = pstr;
   pstr += strlen(pstr) + 1;
   d = pstr;
   pstr += strlen(pstr) + 1;
   n = pstr;
   pstr += strlen(pstr) + 1;
   m = pstr;
   snprintf(text, sizeof(text), "%s<ps/>%s<ps/>%s<ps/>%s<ps/>%s", msg, u, d, n, m);
   e_dialog_text_set(dialog, text);

   elm_win_center(dialog->win, 1, 1);
   e_dialog_button_focus_num(dialog, 0);
   e_dialog_show(dialog);
}

static void
_e_fm2_file_application_properties(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   Efreet_Desktop *desktop;
   E_Fm2_Icon *ic;
   char buf[PATH_MAX];

   ic = data;
   if (!_e_fm2_icon_realpath(ic, buf, sizeof(buf)))
     return;
   desktop = efreet_desktop_get(buf);

   e_desktop_edit(desktop);
}

static void
_e_fm2_file_properties(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Icon *ic;

   ic = data;
   if ((ic->entry_dialog) || (ic->entry_widget)) return;

   if (ic->prop_dialog) e_object_del(E_OBJECT(ic->prop_dialog));
   ic->prop_dialog = e_fm_prop_file(ic);
   E_OBJECT(ic->prop_dialog)->data = ic;
   e_object_del_attach_func_set(E_OBJECT(ic->prop_dialog), _e_fm2_file_properties_delete_cb);
}

static void
_e_fm2_file_properties_delete_cb(void *obj)
{
   E_Fm2_Icon *ic;

   ic = E_OBJECT(obj)->data;
   ic->prop_dialog = NULL;
}

static void
_e_fm2_file_delete_menu(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Fm2_Icon *ic = data;
   if ((!ic) || (!ic->sd)) return;
   _e_fm2_file_delete(ic->sd->obj);
}

static void
_e_fm2_file_delete(Evas_Object *obj)
{
   E_Dialog *dialog;
   E_Fm2_Icon *ic;
   char text[4096 + 256];
   Eina_List *sel;
   int n = 0, folder_count = 0;

   ic = _e_fm2_icon_first_selected_find(obj);
   if (!ic) return;
   if (ic->dialog) return;
   dialog = e_dialog_new(NULL, "E", "_fm_file_delete_dialog");
   ic->dialog = dialog;
   E_OBJECT(dialog)->data = ic;
   e_object_del_attach_func_set(E_OBJECT(dialog), _e_fm2_file_delete_delete_cb);
   e_dialog_button_add(dialog, _("Delete"), NULL, _e_fm2_file_delete_yes_cb, ic);
   e_dialog_button_add(dialog, _("No"), NULL, _e_fm2_file_delete_no_cb, ic);
   e_dialog_button_focus_num(dialog, 0);
   e_dialog_title_set(dialog, _("Confirm Delete"));
   e_dialog_icon_set(dialog, "dialog-warning", 64);
   sel = e_fm2_selected_list_get(obj);
   if (sel)
     {
        n = eina_list_count(sel);
        folder_count = eina_list_count(ic->sd->icons);
     }
   if ((!sel) || (n == 1))
     snprintf(text, sizeof(text),
              _("Are you sure you want to delete<ps/>"
                "<hilight>%s</hilight>?"),
              ic->info.file);
   else if (n == folder_count)
     snprintf(text, sizeof(text),
              _("Are you sure you want to delete<ps/>"
                "<hilight>all</hilight> the %d files in<ps/>"
                "<hilight>%s</hilight>?"),
              n, ic->sd->realpath);
   else
     {
        /* Here the singular version is not used in english, but plural support
         * is nonetheless needed for languages who have multiple plurals
         * depending on the number of files. */
        snprintf(text, sizeof(text),
                 P_("Are you sure you want to delete<ps/>"
                    "the %d selected file in<ps/>"
                    "<hilight>%s</hilight>?",
                    "Are you sure you want to delete<ps/>"
                    "the %d selected files in<ps/>"
                    "<hilight>%s</hilight>?", n),
                 n, ic->sd->realpath);
     }
   if (sel) eina_list_free(sel);
   e_dialog_text_set(dialog, text);
   elm_win_center(dialog->win, 1, 1);
   e_dialog_show(dialog);
}

static void
_e_fm2_file_delete_delete_cb(void *obj)
{
   E_Fm2_Icon *ic;

   ic = E_OBJECT(obj)->data;
   ic->dialog = NULL;
}

static void
_e_fm2_file_delete_yes_cb(void *data, E_Dialog *dialog)
{
   E_Fm2_Icon *ic, *ic_next;
   char buf[PATH_MAX];
   char *files = NULL;
   size_t size = 0;
   size_t len = 0;
   Eina_List *sel, *l;
   E_Fm2_Icon_Info *ici;
   Eina_Bool memerr = EINA_FALSE;

   ic = data;
   ic->dialog = NULL;

   e_object_del(E_OBJECT(dialog));
   ic_next = _e_fm2_icon_next_find(ic->sd->obj, 1, NULL, NULL);
   sel = e_fm2_selected_list_get(ic->sd->obj);
   if (sel && (eina_list_count(sel) != 1))
     {
        EINA_LIST_FOREACH(sel, l, ici)
          {
             if (ic_next && (&(ic_next->info) == ici))
               ic_next = NULL;

             snprintf(buf, sizeof(buf), "%s/%s", ic->sd->realpath, ici->file);
             if (e_filereg_file_protected(buf)) continue;

             if (!memerr)
               {
                  files = e_util_string_append_quoted(files, &size, &len, buf);
                  if (!files) memerr = EINA_TRUE;
                  else
                    {
                       if (eina_list_next(l))
                         {
                            files = e_util_string_append_char(files, &size, &len, ' ');
                            if (!files) memerr = EINA_TRUE;
                         }
                    }
               }
                evas_object_pass_events_set(ici->ic->obj, 1);
          }
        eina_list_free(sel);
     }
   else
     {
        if (sel) eina_list_free(sel);
        _e_fm2_icon_realpath(ic, buf, sizeof(buf));
        if (e_filereg_file_protected(buf)) return;
        evas_object_pass_events_set(ic->obj, 1);
        files = e_util_string_append_quoted(files, &size, &len, buf);
     }
   if (files)
     {
        _e_fm_client_file_del(files, e_config->filemanager_secure_rm | ic->sd->config->secure_rm, ic->sd->obj);
        free(files);
     }

   if (ic_next)
     {
        _e_fm2_icon_select(ic_next);
        evas_object_smart_callback_call(ic_next->sd->obj, "selection_change", NULL);
        _e_fm2_icon_make_visible(ic_next);
     }

   evas_object_smart_callback_call(ic->sd->obj, "files_deleted", NULL);
}

static void
_e_fm2_file_delete_no_cb(void *data, E_Dialog *dialog)
{
   E_Fm2_Icon *ic;

   ic = data;
   ic->dialog = NULL;
   e_object_del(E_OBJECT(dialog));
}

static Eina_Bool
_e_fm2_sys_suspend_hibernate(void *d EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   Eina_List *l, *ll, *lll;
   E_Volume *v;
   E_Fm2_Mount *m;

   EINA_LIST_FOREACH(e_fm2_device_volume_list_get(), l, v)
     {
        EINA_LIST_FOREACH_SAFE(v->mounts, ll, lll, m)
          e_fm2_device_unmount(m);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_fm2_refresh_job_cb(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return;
   e_fm2_refresh(data);
   sd->refresh_job = NULL;
}

static void
_e_fm2_live_file_add(Evas_Object *obj, const char *file, const char *file_rel, int after, E_Fm2_Finfo *finf)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Action *a;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   a = E_NEW(E_Fm2_Action, 1);
   if (!a) return;
   sd->live.actions = eina_list_append(sd->live.actions, a);
   a->type = FILE_ADD;
   a->file = eina_stringshare_add(file);
   a->file2 = eina_stringshare_add(file_rel);
   a->flags = after;
   if (finf) memcpy(&(a->finf), finf, sizeof(E_Fm2_Finfo));
   a->finf.lnk = eina_stringshare_add(a->finf.lnk);
   a->finf.rlnk = eina_stringshare_add(a->finf.rlnk);
   _e_fm2_live_process_begin(obj);
}

static void
_e_fm2_live_file_del(Evas_Object *obj, const char *file)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Action *a;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   a = E_NEW(E_Fm2_Action, 1);
   if (!a) return;
   sd->live.actions = eina_list_append(sd->live.actions, a);
   a->type = FILE_DEL;
   a->file = eina_stringshare_add(file);
   _e_fm2_live_process_begin(obj);
}

static void
_e_fm2_live_file_changed(Evas_Object *obj, const char *file, E_Fm2_Finfo *finf)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Action *a;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   a = E_NEW(E_Fm2_Action, 1);
   if (!a) return;
   sd->live.actions = eina_list_append(sd->live.actions, a);
   a->type = FILE_CHANGE;
   a->file = eina_stringshare_add(file);
   if (finf) memcpy(&(a->finf), finf, sizeof(E_Fm2_Finfo));
   a->finf.lnk = eina_stringshare_add(a->finf.lnk);
   a->finf.rlnk = eina_stringshare_add(a->finf.rlnk);
   _e_fm2_live_process_begin(obj);
}

static void
_e_fm2_live_process_begin(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd->live.actions) return;
   if ((sd->live.idler) || (sd->live.timer) ||
       (sd->listing) || (sd->scan_timer)) return;
   sd->live.idler = ecore_idler_add(_e_fm2_cb_live_idler, obj);
   sd->live.timer = ecore_timer_loop_add(0.2, _e_fm2_cb_live_timer, obj);
   sd->tmp.last_insert = NULL;
}

static void
_e_fm2_live_process_end(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Action *a;

   sd = evas_object_smart_data_get(obj);
   EINA_LIST_FREE(sd->live.actions, a)
     {
        eina_stringshare_del(a->file);
        eina_stringshare_del(a->file2);
        eina_stringshare_del(a->finf.lnk);
        eina_stringshare_del(a->finf.rlnk);
        free(a);
     }
   if (sd->live.idler)
     {
        ecore_idler_del(sd->live.idler);
        sd->live.idler = NULL;
     }
   if (sd->live.timer)
     {
        ecore_timer_del(sd->live.timer);
        sd->live.timer = NULL;
     }
   sd->tmp.last_insert = NULL;
}

static void
_e_fm2_live_process(Evas_Object *obj)
{
   E_Fm2_Smart_Data *sd;
   E_Fm2_Action *a;
   Eina_List *l;
   E_Fm2_Icon *ic;

   sd = evas_object_smart_data_get(obj);
   if (!sd->live.actions) return;
   a = eina_list_data_get(sd->live.actions);
   sd->live.actions = eina_list_remove_list(sd->live.actions, sd->live.actions);
   switch (a->type)
     {
      case FILE_ADD:
        /* new file to sort in place */
        if (!strcmp(a->file, ".order"))
          {
             sd->order_file = EINA_TRUE;
             /* FIXME: reload fm view */
          }
        else
          {
             if (!((a->file[0] == '.') && (!sd->show_hidden_files)))
               _e_fm2_file_add(obj, a->file, 1, a->file2, a->flags, &(a->finf));
          }
        break;

      case FILE_DEL:
        if (!strcmp(a->file, ".order"))
          {
             sd->order_file = EINA_FALSE;
             /* FIXME: reload fm view */
          }
        else
          {
             if (!((a->file[0] == '.') && (!sd->show_hidden_files)))
               _e_fm2_file_del(obj, a->file);
             sd->live.deletions = EINA_TRUE;
          }
        break;

      case FILE_CHANGE:
        if (!strcmp(a->file, ".order"))
          {
             /* FIXME: reload fm view - ignore for now */
          }
        else
          {
             if (!((a->file[0] == '.') && (!sd->show_hidden_files)))
               {
                  EINA_LIST_FOREACH(sd->icons, l, ic)
                    {
                       if (!strcmp(ic->info.file, a->file))
                         {
                            if (ic->removable_state_change)
                              {
                                 _e_fm2_icon_unfill(ic);
                                 _e_fm2_icon_fill(ic, &(a->finf));
                                 ic->removable_state_change = EINA_FALSE;
                                 if ((ic->realized) && (ic->obj_icon))
                                   {
                                      _e_fm2_icon_removable_update(ic);
                                      _e_fm2_icon_label_set(ic, ic->obj);
                                   }
                              }
                            else if (!eina_str_has_extension(ic->info.file, ".part"))
                              {
                                 int realized;

                                 realized = ic->realized;
                                 if (realized) _e_fm2_icon_unrealize(ic);
                                 _e_fm2_icon_unfill(ic);
                                 _e_fm2_icon_fill(ic, &(a->finf));
                                 if (realized) _e_fm2_icon_realize(ic);
                              }
                            break;
                         }
                    }
               }
          }
        break;

      default:
        break;
     }
   eina_stringshare_del(a->file);
   eina_stringshare_del(a->file2);
   eina_stringshare_del(a->finf.lnk);
   eina_stringshare_del(a->finf.rlnk);
   free(a);
}

static Eina_Bool
_e_fm2_cb_live_idler(void *data)
{
   E_Fm2_Smart_Data *sd;
   double t;

   sd = evas_object_smart_data_get(data);
   if (!sd) return ECORE_CALLBACK_CANCEL;
   t = ecore_time_get();
   do
     {
        if (!sd->live.actions) break;
        _e_fm2_live_process(data);
     }
   while ((ecore_time_get() - t) > 0.02);
   if (sd->live.actions) return ECORE_CALLBACK_RENEW;
   _e_fm2_live_process_end(data);
   _e_fm2_cb_live_timer(data);
   if ((sd->order_file) || (sd->config->view.always_order))
     {
        e_fm2_refresh(data);
     }
   sd->live.idler = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_fm2_cb_live_timer(void *data)
{
   E_Fm2_Smart_Data *sd;

   sd = evas_object_smart_data_get(data);
   if (!sd) return ECORE_CALLBACK_CANCEL;
   if (sd->queue) _e_fm2_queue_process(data);
   else if (sd->iconlist_changed)
     {
        if (sd->resize_job) ecore_job_del(sd->resize_job);
        sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, sd->obj);
     }
   else
     {
        if (sd->live.deletions)
          {
             sd->iconlist_changed = EINA_TRUE;
             if (sd->resize_job) ecore_job_del(sd->resize_job);
             sd->resize_job = ecore_job_add(_e_fm2_cb_resize_job, sd->obj);
          }
     }
   sd->live.deletions = EINA_FALSE;
   sd->live.timer = NULL;
   if ((!sd->queue) && (!sd->live.idler)) return ECORE_CALLBACK_CANCEL;
   sd->live.timer = ecore_timer_loop_add(0.2, _e_fm2_cb_live_timer, data);
   return ECORE_CALLBACK_CANCEL;
}

static int
_e_fm2_theme_edje_object_set(E_Fm2_Smart_Data *sd, Evas_Object *o, const char *category, const char *group)
{
   char buf[1024];
   int ret;

   if (sd->custom_theme_content)
     snprintf(buf, sizeof(buf), "e/fileman/%s/%s", sd->custom_theme_content, group);
   else
     snprintf(buf, sizeof(buf), "e/fileman/default/%s", group);

   if (sd->custom_theme)
     {
        if (edje_object_file_set(o, sd->custom_theme, buf)) return 1;
     }
   if (sd->custom_theme)
     {
        if (!ecore_file_exists(sd->custom_theme))
          {
             eina_stringshare_del(sd->custom_theme);
             sd->custom_theme = NULL;
          }
     }
   ret = e_theme_edje_object_set(o, category, buf);
   return ret;
}

static int
_e_fm2_theme_edje_icon_object_set(E_Fm2_Smart_Data *sd, Evas_Object *o, const char *category, const char *group)
{
   char buf[1024];
   int ret;

//   if (sd->custom_theme_content)
//     snprintf(buf, sizeof(buf), "e/icons/fileman/%s/%s", sd->custom_theme_content, group);
//   else
   snprintf(buf, sizeof(buf), "e/icons/fileman/mime/%s", group);

   if (sd->custom_theme)
     {
        if (edje_object_file_set(o, sd->custom_theme, buf)) return 1;
     }
   if (sd->custom_theme)
     {
        if (!ecore_file_exists(sd->custom_theme))
          {
             eina_stringshare_del(sd->custom_theme);
             sd->custom_theme = NULL;
          }
     }
   ret = e_theme_edje_object_set(o, category, buf);
   return ret;
}

static void
_e_fm2_volume_mount(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Volume *v;
   const char *mp;

   v = data;
   if (!v) return;

   mp = e_fm2_device_volume_mountpoint_get(v);
   _e_fm2_client_mount(v->udi, mp);
   eina_stringshare_del(mp);
}

static void
_e_fm2_volume_unmount(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Volume *v;

   v = data;
   if (!v) return;

   v->auto_unmount = EINA_FALSE;
   _e_fm2_client_unmount(v->udi);
}

static void
_e_fm2_volume_eject(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Volume *v;

   v = data;
   if (!v) return;

   v->auto_unmount = EINA_FALSE;
   _e_fm2_client_eject(v->udi);
}

static void
_update_volume_icon(E_Volume *v, E_Fm2_Icon *ic)
{
   Evas_Object *e = e_icon_edje_get(ic->obj_icon);
   if (e)
     {
        if (ic->info.removable_full)
          e_icon_edje_emit(ic->obj_icon, "e,state,removable,full", "e");
        else
          e_icon_edje_emit(ic->obj_icon, "e,state,removable,empty", "e");
     }

   if (v)
     {
        if (v->mounted)
          edje_object_signal_emit(ic->obj, "e,state,volume,mounted", "e");
        else
          edje_object_signal_emit(ic->obj, "e,state,volume,unmounted", "e");
     }
   else
     edje_object_signal_emit(ic->obj, "e,state,volume,off", "e");
}

static void
_e_fm2_volume_icon_update(E_Volume *v)
{
   Evas_Object *o;
   char file[PATH_MAX], fav[PATH_MAX];
   const char *desk;
   Eina_List *l;
   E_Fm2_Icon *ic;

   if (!v || !v->storage) return;

   e_user_dir_snprintf(fav, sizeof(fav), "fileman/favorites");
   desk = efreet_desktop_dir_get();
   snprintf(file, sizeof(file), "|%s_%d.desktop",
            ecore_file_file_get(v->storage->udi), v->partition_number);

   EINA_LIST_FOREACH(_e_fm2_list, l, o)
     {
        const char *rp;

        if ((_e_fm2_list_walking > 0) &&
            (eina_list_data_find(_e_fm2_list_remove, o))) continue;

        rp = e_fm2_real_path_get(o);
        if ((rp) && (strcmp(rp, fav)) && (strcmp(rp, desk))) continue;

        ic = _e_fm2_icon_find(o, file);
        if (ic)
          _update_volume_icon(v, ic);
     }
}

static void
_e_fm2_icon_removable_update(E_Fm2_Icon *ic)
{
   E_Volume *v;

   if (!ic) return;
   v = e_fm2_device_volume_find(ic->info.link);
   _update_volume_icon(v, ic);
}

static void
_e_fm2_operation_abort_internal(E_Fm2_Op_Registry_Entry *ere)
{
   ere->status = E_FM2_OP_STATUS_ABORTED;
   ere->finished = 1;
   ere->needs_attention = 0;
   ere->dialog = NULL;
   e_fm2_op_registry_entry_changed(ere);
   _e_fm_client_send(E_FM_OP_ABORT, ere->id, NULL, 0);
}

E_API void
e_fm2_operation_abort(int id)
{
   E_Fm2_Op_Registry_Entry *ere;

   ere = e_fm2_op_registry_entry_get(id);
   if (!ere) return;

   e_fm2_op_registry_entry_ref(ere);
   e_fm2_op_registry_entry_abort(ere);
   e_fm2_op_registry_entry_unref(ere);
}

E_API Eina_Bool
e_fm2_optimal_size_calc(Evas_Object *obj, int minw, int maxw, int maxh, int *w, int *h)
{
   int x, y, step_w, step_h;
   EFM_SMART_CHECK(EINA_FALSE);
   if ((!w) || (!h)) return EINA_FALSE;
   if (!sd->icons) return EINA_FALSE;
   if (maxw < 0) maxw = 0;
   if (maxh < 0) maxh = 0;
   step_w = sd->min.w + 5, step_h = sd->min.h + 5;
   switch (_e_fm2_view_mode_get(sd))
     {
      case E_FM2_VIEW_MODE_LIST:
        *w = MIN(step_w, maxw);
        *h = MIN(step_h * eina_list_count(sd->icons), (unsigned int)maxh);
        return EINA_TRUE;

      default:
        break;
     }
   y = x = (int)sqrt(eina_list_count(sd->icons));
   if (maxw && (x * step_w > maxw))
     {
        x = maxw / step_w;
        y = (eina_list_count(sd->icons) / x) + ((maxw % step_w) ? 1 : 0);
     }
   if (minw && (x * step_w < minw))
     {
        x = minw / step_w;
        y = (eina_list_count(sd->icons) / x) + ((minw % step_w) ? 1 : 0);
     }
   *w = step_w * x;
   *w = MIN(*w, maxw);
   *h = step_h * y;
   *h = MIN(*h, maxh);
   return EINA_TRUE;
}

E_API E_Fm2_View_Mode
e_fm2_view_mode_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(0);
   return _e_fm2_view_mode_get(sd);
}

E_API Eina_Bool
e_fm2_typebuf_visible_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(EINA_FALSE);
   return sd->typebuf_visible;
}

E_API void
e_fm2_typebuf_clear(Evas_Object *obj)
{
   EFM_SMART_CHECK();
   _e_fm2_typebuf_hide(obj);
}

E_API void
e_fm2_overlay_clip_to(Evas_Object *obj, Evas_Object *clip)
{
   int x, y, w, h;
   EFM_SMART_CHECK();
   if (sd->overlay_clip)
     {
        evas_object_event_callback_del_full(sd->overlay_clip, EVAS_CALLBACK_MOVE, _e_fm2_overlay_clip_move, sd);
        evas_object_event_callback_del_full(sd->overlay_clip, EVAS_CALLBACK_RESIZE, _e_fm2_overlay_clip_resize, sd);
     }
   sd->overlay_clip = clip;
   if (clip)
     {
        evas_object_clip_set(sd->overlay, clip);
        evas_object_geometry_get(clip, &x, &y, &w, &h);
        evas_object_move(sd->overlay, x, y);
        evas_object_resize(sd->overlay, w, h);
        evas_object_event_callback_add(clip, EVAS_CALLBACK_MOVE, _e_fm2_overlay_clip_move, sd);
        evas_object_event_callback_add(clip, EVAS_CALLBACK_RESIZE, _e_fm2_overlay_clip_resize, sd);
     }
   else
     {
        evas_object_clip_set(sd->overlay, sd->clip);
        evas_object_move(sd->overlay, sd->x, sd->y);
        evas_object_resize(sd->overlay, sd->w, sd->h);
     }
}

E_API const char *
e_fm2_real_path_map(const char *dev, const char *path)
{
   return _e_fm2_dev_path_map(NULL, dev, path);
}

E_API void
e_fm2_favorites_init(void)
{
   Eina_List *files;
   char buf[PATH_MAX], buf2[PATH_MAX], *file;

   // make dir for favorites and install ones shipped
   e_user_dir_concat_static(buf, "fileman/favorites");
   ecore_file_mkpath(buf);
   if (!ecore_file_dir_is_empty(buf)) return;
   e_prefix_data_concat(buf, sizeof(buf), "data/favorites");
   files = ecore_file_ls(buf);
   if (!files) return;
   EINA_LIST_FREE(files, file)
     {
        e_prefix_data_snprintf(buf, sizeof(buf), "data/favorites/%s", file);
        snprintf(buf2, sizeof(buf2), "%s/fileman/favorites/%s",
                 e_user_dir_get(), file);
        ecore_file_cp(buf, buf2);
        free(file);
     }
}

E_API unsigned int
e_fm2_selected_count(Evas_Object *obj)
{
   EFM_SMART_CHECK(0);
   return eina_list_count(sd->selected_icons);
}

E_API E_Fm2_Icon_Info *
e_fm2_drop_icon_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   return sd->drop_icon ? &sd->drop_icon->info : NULL;
}

E_API Eina_List *
e_fm2_uri_path_list_get(const Eina_List *uri_list)
{
   E_Fm2_Uri *uri;
   const Eina_List *l;
   Eina_List *path_list = NULL;
   char current_hostname[_POSIX_HOST_NAME_MAX];
   const char *uri_str;

   if (gethostname(current_hostname, _POSIX_HOST_NAME_MAX) == -1)
     current_hostname[0] = '\0';

   EINA_LIST_FOREACH(uri_list, l, uri_str)
     {
        if (!(uri = _e_fm2_uri_parse(uri_str)))
          continue;

        if (!uri->hostname || !strcmp(uri->hostname, "localhost")
            || !strcmp(uri->hostname, current_hostname))
          {
             path_list = eina_list_append(path_list, uri->path);
          }
        else
          eina_stringshare_del(uri->path);

        eina_stringshare_del(uri->hostname);
        E_FREE(uri);
     }

   return path_list;
}

E_API Efreet_Desktop *
e_fm2_desktop_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   if (_e_fm2_desktop_open(sd) != 1) return NULL;
   return sd->desktop;
}

E_API void
e_fm2_drop_menu(Evas_Object *obj, char *args)
{
   E_Menu *menu;
   E_Menu_Item *item;
   E_Zone *zone;
   int x, y;

   EFM_SMART_CHECK();

   EINA_SAFETY_ON_TRUE_RETURN(!!sd->menu);
   sd->menu = menu = e_menu_new();
   if (!menu) return;

   e_menu_post_deactivate_callback_set(menu, _e_fm2_menu_post_cb, sd);

   evas_object_data_set(obj, "drop_menu_data", args);
   e_object_data_set(E_OBJECT(menu), obj);
   e_object_free_attach_func_set(E_OBJECT(menu), _e_fm_drop_menu_free);

   item = e_menu_item_new(menu);
   e_menu_item_label_set(item, _("Copy"));
   e_menu_item_callback_set(item, _e_fm_drop_menu_copy_cb, obj);
   e_util_menu_item_theme_icon_set(item, "edit-copy");

   item = e_menu_item_new(menu);
   e_menu_item_label_set(item, _("Move"));
   e_menu_item_callback_set(item, _e_fm_drop_menu_move_cb, obj);
   e_menu_item_icon_edje_set(item,
                             e_theme_edje_file_get("base/theme/fileman",
                                                   "e/fileman/default/button/move"),
                             "e/fileman/default/button/move");

   item = e_menu_item_new(menu);
   e_menu_item_label_set(item, _("Link"));
   e_menu_item_callback_set(item, _e_fm_drop_menu_symlink_cb, obj);
   e_util_menu_item_theme_icon_set(item, "emblem-symbolic-link");

   item = e_menu_item_new(menu);
   e_menu_item_separator_set(item, 1);

   item = e_menu_item_new(menu);
   e_menu_item_label_set(item, _("Abort"));
   e_menu_item_callback_set(item, _e_fm_drop_menu_abort_cb, obj);
   e_menu_item_icon_edje_set(item,
                             e_theme_edje_file_get("base/theme/fileman",
                                                   "e/fileman/default/button/abort"),
                             "e/fileman/default/button/abort");

   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   zone = e_zone_current_get();
   if (!zone) goto error;
   e_menu_activate_mouse(menu, zone, x, y, 1, 1, E_MENU_POP_DIRECTION_DOWN, 0);
   return;
error:
   e_object_del(E_OBJECT(menu));
   sd->menu = NULL;
}

E_API E_Fm2_Icon *
e_fm2_icon_editing_get(Evas_Object *obj)
{
   EFM_SMART_CHECK(NULL);
   return sd->iop_icon;
}
#include "e.h"

static Eina_Bool  _e_fm2_custom_file_hash_foreach_list(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static Eina_List *_e_fm2_custom_hash_key_base_list(Eina_Hash *hash, const char *str);
//static Eina_Bool _e_fm2_custom_file_hash_foreach_sub_list(Eina_Hash *hash, const char *key, void *data, void *fdata);
//static Eina_List *_e_fm2_custom_hash_key_sub_list(Eina_Hash *hash, const char *str);
static Eina_Bool  _e_fm2_custom_file_hash_foreach(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static Eina_Bool  _e_fm2_custom_file_hash_foreach_save(const Eina_Hash *hash, const void *key, void *data, void *fdata);
static void       _e_fm2_custom_file_info_load(void);
static void       _e_fm2_custom_file_info_save(void);
static void       _e_fm2_custom_file_info_free(void);
static void       _e_fm2_custom_file_cb_defer_save(void *data);

static E_Powersave_Deferred_Action *_e_fm2_flush_defer = NULL;
static Eet_File *_e_fm2_custom_file = NULL;
static Eet_Data_Descriptor *_e_fm2_custom_file_edd = NULL;
static Eet_Data_Descriptor *_e_fm2_custom_dir_edd = NULL;
static Eina_Hash *_e_fm2_custom_hash = NULL;
static int _e_fm2_custom_writes = 0;
static int _e_fm2_custom_init = 0;

/* FIXME: this uses a flat path key for custom file info. this is fine as
 * long as we only expect a limited number of custom info nodes. if we
 * start to see whole dire trees stored this way things will suck. we need
 * to use a tree struct to store this so deletes and renmes are sane.
 */

/* externally accessible functions */
EINTERN int
e_fm2_custom_file_init(void)
{
   Eet_Data_Descriptor_Class eddc;

   _e_fm2_custom_init++;
   if (_e_fm2_custom_init > 1) return _e_fm2_custom_init;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof (eddc), "e_fm2_custom_dir", sizeof (E_Fm2_Custom_Dir)))
     {
        _e_fm2_custom_init--;
        return 0;
     }

   _e_fm2_custom_hash = eina_hash_string_superfast_new(NULL);

   _e_fm2_custom_dir_edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(y, z) EET_DATA_DESCRIPTOR_ADD_BASIC(_e_fm2_custom_dir_edd, E_Fm2_Custom_Dir, #y, y, z)
   DAT(pos.x, EET_T_DOUBLE);
   DAT(pos.y, EET_T_DOUBLE);
   DAT(prop.icon_size, EET_T_SHORT);
   DAT(prop.view_mode, EET_T_CHAR);
   DAT(prop.order_file, EET_T_UCHAR);
   DAT(prop.show_hidden_files, EET_T_UCHAR);
   DAT(prop.in_use, EET_T_UCHAR);

   DAT(prop.sort.no_case, EET_T_UCHAR);
   DAT(prop.sort.size, EET_T_UCHAR);
   DAT(prop.sort.extension, EET_T_UCHAR);
   DAT(prop.sort.mtime, EET_T_UCHAR);
   DAT(prop.sort.dirs.first, EET_T_UCHAR);
   DAT(prop.sort.dirs.last, EET_T_UCHAR);
#undef DAT
   eddc.size = sizeof (E_Fm2_Custom_File);
   eddc.name = "e_fm_custom_file";

   _e_fm2_custom_file_edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(x, y, z) EET_DATA_DESCRIPTOR_ADD_BASIC(_e_fm2_custom_file_edd, E_Fm2_Custom_File, x, y, z)
   DAT("g.x", geom.x, EET_T_INT);
   DAT("g.y", geom.y, EET_T_INT);
   DAT("g.w", geom.w, EET_T_INT);
   DAT("g.h", geom.h, EET_T_INT);
   DAT("g.rw", geom.res_w, EET_T_INT);
   DAT("g.rh", geom.res_h, EET_T_INT);
   DAT("g.s", geom.scale, EET_T_DOUBLE);
   DAT("g.v", geom.valid, EET_T_UCHAR);

   DAT("i.t", icon.type, EET_T_INT);
   DAT("i.i", icon.icon, EET_T_STRING);
   DAT("i.v", icon.valid, EET_T_UCHAR);

   DAT("l", label, EET_T_STRING);

#undef DAT

   EET_DATA_DESCRIPTOR_ADD_SUB(_e_fm2_custom_file_edd, E_Fm2_Custom_File, "dir",
                               dir, _e_fm2_custom_dir_edd);

   return 1;
}

EINTERN void
e_fm2_custom_file_shutdown(void)
{
   _e_fm2_custom_init--;
   if (_e_fm2_custom_init != 0) return;

   _e_fm2_custom_file_info_save();
   _e_fm2_custom_file_info_free();
   if (_e_fm2_flush_defer) e_powersave_deferred_action_del(_e_fm2_flush_defer);
   _e_fm2_flush_defer = NULL;
   eet_data_descriptor_free(_e_fm2_custom_file_edd);
   _e_fm2_custom_file_edd = NULL;
   eet_data_descriptor_free(_e_fm2_custom_dir_edd);
   _e_fm2_custom_dir_edd = NULL;
}

E_API E_Fm2_Custom_File *
e_fm2_custom_file_get(const char *path)
{
   E_Fm2_Custom_File *cf;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return NULL;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();
   cf = eina_hash_find(_e_fm2_custom_hash, path);
   return cf;
}

static void
_e_fm2_custom_dir_del(E_Fm2_Custom_Dir *dir)
{
   free(dir);
}

static void
_e_fm2_custom_file_del(E_Fm2_Custom_File *cf)
{
   if (!cf) return;

   eina_stringshare_del(cf->icon.icon);
   eina_stringshare_del(cf->label);
   _e_fm2_custom_dir_del(cf->dir);
   free(cf);
}

static E_Fm2_Custom_Dir *
_e_fm2_custom_dir_dup(const E_Fm2_Custom_Dir *dir)
{
   E_Fm2_Custom_Dir *copy;

   if (!dir) return NULL;

   copy = calloc(1, sizeof(*copy));
   if (!copy) return NULL;

   memcpy(copy, dir, sizeof(*copy));
   return copy;
}

E_API E_Fm2_Custom_File *
e_fm2_custom_file_dup(const E_Fm2_Custom_File *cf)
{
   E_Fm2_Custom_File *copy;

   if (!cf) return NULL;

   copy = calloc(1, sizeof(*copy));
   if (!copy) return NULL;

   memcpy(copy, cf, sizeof(*copy));
   copy->icon.icon = eina_stringshare_add(cf->icon.icon);
   copy->label = eina_stringshare_add(cf->label);
   copy->dir = _e_fm2_custom_dir_dup(cf->dir);
   return copy;
}

E_API void
e_fm2_custom_file_set(const char *path, const E_Fm2_Custom_File *cf)
{
   E_Fm2_Custom_File *cf1;
   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();

   cf1 = eina_hash_find(_e_fm2_custom_hash, path);
   if ((cf1 != cf) || ((cf1) && (cf) && (cf1->dir != cf->dir)))
     {
        E_Fm2_Custom_File *cf2 = e_fm2_custom_file_dup(cf);
        if (cf2)
          {
             if (cf1)
               {
                  eina_hash_modify(_e_fm2_custom_hash, path, cf2);
                  _e_fm2_custom_file_del(cf1);
               }
             else
               eina_hash_add(_e_fm2_custom_hash, path, cf2);
          }
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_del(const char *path)
{
   Eina_List *list, *l;
   E_Fm2_Custom_File *cf2;
   const void *key;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();

   list = _e_fm2_custom_hash_key_base_list(_e_fm2_custom_hash, path);
   if (list)
     {
        EINA_LIST_FOREACH(list, l, key)
          {
             cf2 = eina_hash_find(_e_fm2_custom_hash, key);
             if (cf2)
               {
                  eina_hash_del(_e_fm2_custom_hash, key, cf2);
                  _e_fm2_custom_file_del(cf2);
               }
          }
        eina_list_free(list);
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_rename(const char *path, const char *new_path)
{
   E_Fm2_Custom_File *cf, *cf2;
   Eina_List *list, *l;
   const void *key;

   _e_fm2_custom_file_info_load();
   if (!_e_fm2_custom_file) return;
   if (_e_fm2_flush_defer) e_fm2_custom_file_flush();
   cf2 = eina_hash_find(_e_fm2_custom_hash, path);
   if (cf2)
     {
        eina_hash_del(_e_fm2_custom_hash, path, cf2);
        cf = eina_hash_find(_e_fm2_custom_hash, new_path);
        if (cf)
          _e_fm2_custom_file_del(cf);
        eina_hash_add(_e_fm2_custom_hash, new_path, cf2);
     }
   list = _e_fm2_custom_hash_key_base_list(_e_fm2_custom_hash, path);
   if (list)
     {
        EINA_LIST_FOREACH(list, l, key)
          {
             cf2 = eina_hash_find(_e_fm2_custom_hash, key);
             if (cf2)
               {
                  char buf[PATH_MAX];

                  strcpy(buf, new_path);
                  strcat(buf, (char *)key + strlen(path));
                  eina_hash_del(_e_fm2_custom_hash, key, cf2);
                  cf = eina_hash_find(_e_fm2_custom_hash, buf);
                  if (cf)
                    _e_fm2_custom_file_del(cf);
                  eina_hash_add(_e_fm2_custom_hash, buf, cf2);
               }
          }
        eina_list_free(list);
     }
   _e_fm2_custom_writes = 1;
}

E_API void
e_fm2_custom_file_flush(void)
{
   if (!_e_fm2_custom_file) return;

   if (_e_fm2_flush_defer)
     e_powersave_deferred_action_del(_e_fm2_flush_defer);
   _e_fm2_flush_defer =
     e_powersave_deferred_action_add(_e_fm2_custom_file_cb_defer_save, NULL);
}

/**/

struct _E_Custom_List
{
   Eina_List  *l;
   const char *base;
   int         base_len;
};

static Eina_Bool
_e_fm2_custom_file_hash_foreach_list(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data EINA_UNUSED, void *fdata)
{
   struct _E_Custom_List *cl;

   cl = fdata;
   if (!strncmp(cl->base, key, cl->base_len))
     cl->l = eina_list_append(cl->l, key);
   return 1;
}

static Eina_List *
_e_fm2_custom_hash_key_base_list(Eina_Hash *hash, const char *str)
{
   struct _E_Custom_List cl;

   cl.l = NULL;
   cl.base = str;
   cl.base_len = strlen(cl.base);
   eina_hash_foreach(hash, _e_fm2_custom_file_hash_foreach_list, &cl);
   return cl.l;
}

/*
   static Eina_Bool
   _e_fm2_custom_file_hash_foreach_sub_list(const Eina_Hash *hash, const void *key, void *data, void *fdata)
   {
   struct _E_Custom_List *cl;

   cl = fdata;
   if (!strncmp(cl->base, key, strlen(key)))
     cl->l = eina_list_append(cl->l, key);
   return 1;
   }
 */

/*
   static Eina_List *
   _e_fm2_custom_hash_key_sub_list(const Eina_Hash *hash, const void *str)
   {
   struct _E_Custom_List cl;

   cl.l = NULL;
   cl.base = str;
   eina_hash_foreach(hash,
                     _e_fm2_custom_file_hash_foreach_sub_list, &cl);
   return cl.l;
   }
 */

static Eina_Bool
_e_fm2_custom_file_hash_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   _e_fm2_custom_file_del(data);
   return 1;
}

static Eina_Bool
_e_fm2_custom_file_hash_foreach_save(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
{
   Eet_File *ef;
   E_Fm2_Custom_File *cf;

   ef = fdata;
   cf = data;
   eet_data_write(ef, _e_fm2_custom_file_edd, key, cf, 1);
   return 1;
}

static void
_e_fm2_custom_file_info_load(void)
{
   char buf[PATH_MAX];

   if (_e_fm2_custom_file) return;
   _e_fm2_custom_writes = 0;
   e_user_dir_concat_static(buf, "fileman/custom.cfg");
   _e_fm2_custom_file = eet_open(buf, EET_FILE_MODE_READ);
   if (!_e_fm2_custom_file)
     _e_fm2_custom_file = eet_open(buf, EET_FILE_MODE_WRITE);
   if (_e_fm2_custom_file)
     {
        E_Fm2_Custom_File *cf;
        char **list;
        int i, num;

        list = eet_list(_e_fm2_custom_file, "*", &num);
        if (list)
          {
             for (i = 0; i < num; i++)
               {
                  cf = eet_data_read(_e_fm2_custom_file,
                                     _e_fm2_custom_file_edd, list[i]);
                  if (cf)
                    eina_hash_add(_e_fm2_custom_hash, list[i], cf);
               }
             free(list);
          }
     }
}

static void
_e_fm2_custom_file_info_save(void)
{
   Eet_File *ef;
   char buf[PATH_MAX], buf2[PATH_MAX];
   size_t len;
   int ret;

   if (!_e_fm2_custom_file) return;
   if (!_e_fm2_custom_writes) return;

   len = e_user_dir_concat_static(buf, "fileman/custom.cfg.tmp");
   if (len >= sizeof(buf)) return;
   ef = eet_open(buf, EET_FILE_MODE_WRITE);
   if (!ef) return;
   eina_hash_foreach(_e_fm2_custom_hash,
                     _e_fm2_custom_file_hash_foreach_save, ef);
   eet_close(ef);

   memcpy(buf2, buf, len - (sizeof(".tmp") - 1));
   buf2[len - (sizeof(".tmp") - 1)] = '\0';
   eet_close(_e_fm2_custom_file);
   _e_fm2_custom_file = NULL;
   ret = rename(buf, buf2);
   if (ret < 0)
     {
        /* FIXME: do we want to trap individual errno
           and provide a short blurp to the user? */
        perror("rename");
     }
}

static void
_e_fm2_custom_file_info_free(void)
{
   _e_fm2_custom_writes = 0;
   if (_e_fm2_custom_file)
     {
        eet_close(_e_fm2_custom_file);
        _e_fm2_custom_file = NULL;
     }
   if (_e_fm2_custom_hash)
     {
        eina_hash_foreach(_e_fm2_custom_hash,
                          _e_fm2_custom_file_hash_foreach, NULL);
        eina_hash_free(_e_fm2_custom_hash);
        _e_fm2_custom_hash = eina_hash_string_superfast_new(NULL);
     }
}

static void
_e_fm2_custom_file_cb_defer_save(void *data EINA_UNUSED)
{
   _e_fm2_custom_file_info_save();
   _e_fm2_custom_file_info_free();
   _e_fm2_flush_defer = NULL;
}

#include "e_fm_device.h"
#include "e_fm_shared_codec.h"
#include "e_fm_shared_device.h"

static void _e_fm2_volume_write(E_Volume *v) EINA_ARG_NONNULL(1);
static void _e_fm2_volume_erase(E_Volume *v) EINA_ARG_NONNULL(1);
static void _e_fm2_device_mount_ok(E_Fm2_Mount *m) EINA_ARG_NONNULL(1);
static void _e_fm2_device_mount_fail(E_Fm2_Mount *m) EINA_ARG_NONNULL(1);
static void _e_fm2_device_unmount_ok(E_Fm2_Mount *m) EINA_ARG_NONNULL(1);
static void _e_fm2_device_unmount_fail(E_Fm2_Mount *m) EINA_ARG_NONNULL(1);

static Eina_List *_e_stores = NULL;
static Eina_List *_e_vols = NULL;

static Eina_Bool _check_run_show = EINA_FALSE;
static Eina_Bool _check_run_hide = EINA_FALSE;
static Ecore_Thread *_check_vols = NULL;

static inline E_Fm2_Device_Mount_Op *
_e_fm2_device_mount_op_new(char *args, size_t size, size_t length)
{
   E_Fm2_Device_Mount_Op *mop;

   mop = E_NEW(E_Fm2_Device_Mount_Op, 1);
   mop->args = args;
   mop->size = size;
   mop->length = length;
   return mop;
}

static inline void
_e_fm2_device_mount_op_free(E_Fm2_Device_Mount_Op *mop)
{
   if (!mop) return;
   free(mop->args);
   free(mop);
}

static void
_e_fm2_device_volume_setup(E_Volume *v)
{
   char label[1024] = {0};
   char *size = NULL;
   const char *icon = NULL;
   /* unsigned long long sz; */

   /* Compute the size in a readable form */
   if (v->size)
     size = e_util_size_string_get(v->size);

   /* Choose the label */
   if ((v->label) && (v->label[0]))
     {}else if ((v->partition_label) && (v->partition_label[0]))
     snprintf(label, sizeof(label) - 1, "%s", v->partition_label);
   else if (((v->storage->vendor) && (v->storage->vendor[0])) &&
            ((v->storage->model) && (v->storage->model[0])))
     {
        if (size && (size[0] != '\0'))
          snprintf(label, sizeof(label) - 1, _("%s %s—%s"), v->storage->vendor, v->storage->model, size);
        else
          snprintf(label, sizeof(label) - 1, "%s %s", v->storage->vendor, v->storage->model);
     }
   else if ((v->storage->model) && (v->storage->model[0]))
     {
        if (size && (size[0] != '\0'))
          snprintf(label, sizeof(label) - 1, _("%s—%s"), v->storage->model, size);
        else
          snprintf(label, sizeof(label) - 1, "%s", v->storage->model);
     }
   else if ((v->storage->vendor) && (v->storage->vendor[0]))
     {
        if (size && (size[0] != '\0'))
          snprintf(label, sizeof(label) - 1, _("%s—%s"), v->storage->vendor, size);
        else
          snprintf(label, sizeof(label) - 1, "%s", v->storage->vendor);
     }
   else if (v->storage->drive_type && (!strcmp(v->storage->drive_type, "sd_mmc")))
     snprintf(label, sizeof(label) - 1, _("Flash Card—%s"), size);
   else
     snprintf(label, sizeof(label), _("Unknown Volume"));

   if (label[0]) eina_stringshare_replace(&v->label, label);

   /* Choose the icon */
   /* http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html */
   if (v->storage->icon.volume)
     icon = v->storage->icon.volume;
   else
     {
        if ((!v->storage->drive_type) || (!strcmp(v->storage->drive_type, "disk")) || (!strcmp(v->storage->drive_type, "partition")))
          {
             if (v->storage->removable == 0)
               icon = "drive-harddisk";
             else
               icon = "drive-removable-media";
          }
        else if ((!strcmp(v->storage->drive_type, "cdrom")) || (!strncmp(v->storage->drive_type, "optical", 7)))
          icon = "drive-optical";
        else if (!strncmp(v->storage->drive_type, "floppy", 6))
          icon = "media-floppy";
        else if (!strcmp(v->storage->drive_type, "tape"))
          icon = "media-tape";
        else if (!strcmp(v->storage->drive_type, "thumb"))
          // XXX: maybe a spcial type?
          icon = "drive-removable-media";
        else if (!strcmp(v->storage->drive_type, "compact_flash")
                 || !strcmp(v->storage->drive_type, "memory_stick")
                 || !strcmp(v->storage->drive_type, "smart_media")
                 || !strcmp(v->storage->drive_type, "sd_mmc")
                 || !strncmp(v->storage->drive_type, "flash", 5))
          icon = "media-flash";
        else // XXX: fallback if we don't know
          icon = "media-floppy";
     }
   if (icon) eina_stringshare_replace(&v->icon, icon);

   if ((!v->mount_point) ||
       // filter out these mountouts - hack hack
       (strcmp(v->mount_point, "/") &&
        strcmp(v->mount_point, "/home") &&
        strcmp(v->mount_point, "/tmp")))
     _e_fm2_volume_write(v);

   E_FREE(size);
}

E_API void
e_fm2_device_storage_add(E_Storage *s)
{
   Eina_List *l;
   E_Volume *v;

   if (e_fm2_device_storage_find(s->udi)) return;

   s->validated = EINA_TRUE;
   _e_stores = eina_list_append(_e_stores, s);
/*
   printf("STO+\n"
          "  udi: %s\n"
          "  bus: %s\n"
          "  drive_type: %s\n"
          "  model: %s\n"
          "  vendor: %s\n"
          "  serial: %s\n"
          "  removable: %i\n"
          "  media_available: %i\n"
          "  media_size: %lli\n"
          "  requires_eject: %i\n"
          "  hotpluggable: %i\n"
          "  media_check_enabled: %i\n"
          "  icon.drive: %s\n"
          "  icon.volume: %s\n\n"
          ,
          s->udi,
          s->bus,
          s->drive_type,
          s->model,
          s->vendor,
          s->serial,
          s->removable,
          s->media_available,
          s->media_size,
          s->requires_eject,
          s->hotpluggable,
          s->media_check_enabled,
          s->icon.drive,
          s->icon.volume);
 */
   if ((s->removable == 0) &&
       (s->media_available == 0) &&
       (s->media_size == 0) &&
       (s->requires_eject == 0) &&
       (s->hotpluggable == 0) &&
       (s->media_check_enabled == 0))
     {
//	printf("      Ignore this storage\n");
     }
   else
     {
        s->trackable = EINA_TRUE;
     }

   EINA_LIST_FOREACH(_e_vols, l, v) /* catch volumes which were added before their storage */
     {
        if ((!v->storage) && (s->udi == v->parent))
          {
             v->storage = s;
             _e_fm2_device_volume_setup(v);
          }
     }
}

E_API void
e_fm2_device_storage_del(E_Storage *s)
{
//   printf("STO- %s\n", s->udi);
   _e_stores = eina_list_remove(_e_stores, s);
   _e_fm_shared_device_storage_free(s);
}

E_API E_Storage *
e_fm2_device_storage_find(const char *udi)
{
   Eina_List *l;
   E_Storage *s;

   if (!udi) return NULL;
   EINA_LIST_FOREACH(_e_stores, l, s)
     {
        if (!strcmp(udi, s->udi)) return s;
     }
   return NULL;
}

E_API void
e_fm2_device_volume_add(E_Volume *v)
{
   E_Storage *s;

   if (e_fm2_device_volume_find(v->udi)) return;

   v->validated = EINA_TRUE;
   _e_vols = eina_list_append(_e_vols, v);
/*
   printf("VOL+\n"
          "  udi: %s\n"
          "  uuid: %s\n"
          "  fstype: %s\n"
          "  size: %llu\n"
          "  label: %s\n"
          "  partition: %d\n"
          "  partition_number: %d\n"
          "  partition_label: %s\n"
          "  mounted: %d\n"
          "  mount_point: %s\n"
          "  parent: %s\n"
          ,
          v->udi,
          v->uuid,
          v->fstype,
          v->size,
          v->label,
          v->partition,
          v->partition_number,
          v->partition ? v->partition_label : "(not a partition)",
          v->mounted,
          v->mount_point,
          v->parent);
 */
/* Check mount point */
   if ((v->efm_mode == EFM_MODE_USING_HAL_MOUNT) &&
       ((!v->mount_point) || (!v->mount_point[0])))
     {
        eina_stringshare_del(v->mount_point);
        v->mount_point = NULL;
        v->mount_point = e_fm2_device_volume_mountpoint_get(v);
        if ((!v->mount_point) || (!v->mount_point[0]))
          {
             char buf[PATH_MAX];
             const char *id;

             if (v->mount_point) eina_stringshare_del(v->mount_point);
             v->mount_point = NULL;
             id = "disk";
             if ((v->uuid) && (v->uuid[0])) id = v->uuid;
             if (ecore_file_is_dir("/media"))
               snprintf(buf, sizeof(buf), "/media/%s", id);
             else if (ecore_file_is_dir("/mnt"))
               snprintf(buf, sizeof(buf), "/mnt/%s", id);
             else if (ecore_file_is_dir("/tmp"))
               snprintf(buf, sizeof(buf), "/tmp/%s", id);
             else
               buf[0] = 0;
             v->mount_point = eina_stringshare_add(buf);
          }
     }

   /* Search parent storage */
   if ((s = e_fm2_device_storage_find(v->parent)))
     {
        v->storage = s;
        s->volumes = eina_list_append(s->volumes, v);
     }

   if (v->storage) _e_fm2_device_volume_setup(v);
}

E_API void
e_fm2_device_volume_del(E_Volume *v)
{
   E_Fm2_Mount *m;

//   printf("VOL- %s\n", v->udi);
   _e_vols = eina_list_remove(_e_vols, v);
   _e_fm2_volume_erase(v);
   if (v->storage)
     {
        v->storage->volumes = eina_list_remove(v->storage->volumes, v);
        v->storage = NULL;
     }
   EINA_LIST_FREE(v->mounts, m)
     {
        _e_fm2_device_unmount_ok(m);
        e_fm2_device_mount_free(m);
     }
   while (v->mount_ops)
     {
        E_Fm2_Device_Mount_Op *mop;

        mop = (E_Fm2_Device_Mount_Op *)v->mount_ops;
        v->mount_ops = eina_inlist_remove(v->mount_ops, v->mount_ops);
        _e_fm2_device_mount_op_free(mop);
     }
   _e_fm_shared_device_volume_free(v);
}

static void
_e_fm2_volume_write(E_Volume *v)
{
   char buf[PATH_MAX], buf2[PATH_MAX];
   FILE *f;
   const char *id;

   if (!v->storage) return;
   id = ecore_file_file_get(v->storage->udi);
   e_user_dir_snprintf(buf, sizeof(buf), "fileman/favorites/|%s_%d.desktop",
                       id, v->partition_number);

   f = fopen(buf, "w");
   if (f)
     {
        fprintf(f,
                "[Desktop Entry]\n"
                "Encoding=UTF-8\n"
                "Type=Link\n"
                "X-Enlightenment-Type=Removable\n"
                "X-Enlightenment-Removable-State=Full\n"
                "Name=%s\n"
                "Icon=%s\n"
                "Comment=%s\n"
                "URL=file:/%s\n"
                ,
                v->label,
                v->icon,
                _("Removable Device"),
                v->udi);
        fclose(f);

        if (e_config->device_desktop)
          {
             snprintf(buf2, sizeof(buf2), "%s/|%s_%d.desktop",
                      efreet_desktop_dir_get(), id, v->partition_number);
             ecore_file_symlink(buf, buf2);
          }

        /* FIXME: manipulate icon directly */
        _e_fm2_file_force_update(buf);
        _e_fm2_file_force_update(buf2);
     }
}

static void
_e_fm2_volume_erase(E_Volume *v)
{
   char buf[PATH_MAX] = {0};
   const char *id;

   if (!v->storage) return;
   id = ecore_file_file_get(v->storage->udi);
   if (e_config->device_desktop)
     {
        snprintf(buf, sizeof(buf), "%s/|%s_%d.desktop",
                 efreet_desktop_dir_get(), id, v->partition_number);
        ecore_file_unlink(buf);
        _e_fm2_file_force_update(buf);
     }
   e_user_dir_snprintf(buf, sizeof(buf),
                       "fileman/favorites/|%s_%d.desktop",
                       id, v->partition_number);
   ecore_file_unlink(buf);
   _e_fm2_file_force_update(buf);
}

E_API E_Volume *
e_fm2_device_volume_find(const char *udi)
{
   Eina_List *l;
   E_Volume *v;

   if (!udi) return NULL;

   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (!v->udi) continue;
        if (!strcmp(udi, v->udi)) return v;
     }

   return NULL;
}

E_API E_Volume *
e_fm2_device_volume_find_fast(const char *udi)
{
   Eina_List *l;
   E_Volume *v;

   if (!udi) return NULL;

   EINA_LIST_FOREACH(_e_vols, l, v)
     if (udi == v->udi) return v;

   return NULL;
}

E_API const char *
e_fm2_device_volume_mountpoint_get(E_Volume *v)
{
   char buf[PATH_MAX] = {0};

   if (!v) return NULL;
   if (v->mount_point)
     {
        //	printf("GET MOUNTPOINT = %s\n", v->mount_point);
        return eina_stringshare_add(v->mount_point);
     }
   else if (v->efm_mode != EFM_MODE_USING_HAL_MOUNT)
     return NULL;

   if (v->label && v->label[0] != '\0')
     snprintf(buf, sizeof(buf) - 1, "/media/%s", v->label);
   else if (v->uuid && v->uuid[0] != '\0')
     snprintf(buf, sizeof(buf) - 1, "/media/%s", v->uuid);
   else if ((v->storage) && (v->storage->serial) && v->storage->serial[0] != '\0')
     snprintf(buf, sizeof(buf) - 1, "/media/%s", v->storage->serial);
   else
     {
        static int mount_count = 1;
        snprintf(buf, sizeof(buf) - 1, "/media/unknown-%i", mount_count++);
     }
//   printf("GET MOUNTPOINT = %s\n", buf);
   return eina_stringshare_add(buf);
}

E_API E_Fm2_Device_Mount_Op *
e_fm2_device_mount_op_add(E_Fm2_Mount *m, char *args, size_t size, size_t length)
{
   E_Fm2_Device_Mount_Op *mop;

   mop = _e_fm2_device_mount_op_new(args, size, length);
   m->volume->mount_ops = eina_inlist_append(m->volume->mount_ops, EINA_INLIST_GET(mop));
   return mop;
}

E_API void
e_fm2_device_mount_add(E_Volume *v,
                       const char *mountpoint)
{
   v->mounted = EINA_TRUE;
   if (mountpoint && (mountpoint[0]))
     eina_stringshare_replace(&v->mount_point, mountpoint);

   E_LIST_FOREACH(v->mounts, _e_fm2_device_mount_ok);
//   printf("MOUNT %s %s\n", v->udi, v->mount_point);
}

E_API void
e_fm2_device_mount_del(E_Volume *v)
{
   E_Fm2_Mount *m;

   v->mounted = EINA_FALSE;
   if (v->mount_point)
     {
        eina_stringshare_del(v->mount_point);
        v->mount_point = NULL;
     }

   EINA_LIST_FREE(v->mounts, m)
     {
        _e_fm2_device_unmount_ok(m);
        e_fm2_device_mount_free(m);
     }
}

E_API void
e_fm2_device_mount_free(E_Fm2_Mount *m)
{
   if (!m) return;

   eina_stringshare_del(m->udi);
   eina_stringshare_del(m->mount_point);

   free(m);
}

E_API E_Fm2_Mount *
e_fm2_device_mount_find(const char *path)
{
   Eina_List *l;
   E_Volume *v;

   if (!path) return NULL;

   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (v->mounted
            && v->mount_point
            && !strncmp(path, v->mount_point, strlen(v->mount_point))
            && v->mounts)
          return eina_list_data_get(v->mounts);
     }
   return NULL;
}

E_API E_Fm2_Mount *
e_fm2_device_mount(E_Volume *v,
                   Ecore_Cb mount_ok,
                   Ecore_Cb mount_fail,
                   Ecore_Cb unmount_ok,
                   Ecore_Cb unmount_fail,
                   void *data)
{
   E_Fm2_Mount *m;

   if (!v) return NULL;

   m = calloc(1, sizeof(E_Fm2_Mount));
   if (!m) return NULL;
   m->udi = eina_stringshare_add(v->udi);
   m->mount_ok = mount_ok;
   m->mount_fail = mount_fail;
   m->unmount_ok = unmount_ok;
   m->unmount_fail = unmount_fail;
   m->data = data;
   m->volume = v;
   m->mounted = v->mounted;

   v->mounts = eina_list_prepend(v->mounts, m);

   if (!v->mounted)
     {
        //printf("BEGIN MOUNT %p '%s'\n", m, v->mount_point);
        v->auto_unmount = EINA_TRUE;
        _e_fm2_client_mount(v->udi, v->mount_point);
     }
   else
     {
        v->auto_unmount = EINA_FALSE;
        m->mount_point = eina_stringshare_add(v->mount_point);
     }

   return m;
}

E_API void
e_fm2_device_mount_fail(E_Volume *v)
{
   E_Fm2_Mount *m;

   v->mounted = EINA_FALSE;
   if (v->mount_point)
     {
        eina_stringshare_del(v->mount_point);
        v->mount_point = NULL;
     }

   EINA_LIST_FREE(v->mounts, m)
     {
        _e_fm2_device_mount_fail(m);
        e_fm2_device_mount_free(m);
     }
}

E_API void
e_fm2_device_unmount(E_Fm2_Mount *m)
{
   E_Volume *v;

   if (!(v = m->volume)) return;
   if (v->mount_ops)
     {
        m->deleted = EINA_TRUE;
        return;
     }
   v->mounts = eina_list_remove(v->mounts, m);
   e_fm2_device_mount_free(m);

   if (v->auto_unmount && v->mounted && !eina_list_count(v->mounts))
     {
        //printf("BEGIN UNMOUNT %p '%s'\n", m, v->udi);
        _e_fm2_client_unmount(v->udi);
     }
}

E_API void
e_fm2_device_unmount_fail(E_Volume *v)
{
   Eina_List *l;
   E_Fm2_Mount *m;

   v->mounted = EINA_TRUE;

   EINA_LIST_FOREACH(v->mounts, l, m)
     _e_fm2_device_unmount_fail(m);
}

static void
_e_fm2_device_mount_ok(E_Fm2_Mount *m)
{
   if (m->mounted) return;
   m->mounted = EINA_TRUE;
   if (m->volume)
     m->mount_point = eina_stringshare_add(m->volume->mount_point);
   if (m->mount_ok)
     m->mount_ok(m->data);
   if (m->deleted) ecore_job_add((Ecore_Cb)e_fm2_device_unmount, m);
   //printf("MOUNT OK '%s'\n", m->mount_point);
}

static void
_e_fm2_device_mount_fail(E_Fm2_Mount *m)
{
   //printf("MOUNT FAIL '%s'\n", m->mount_point);
   m->mounted = EINA_FALSE;
   if (m->mount_point)
     {
        eina_stringshare_del(m->mount_point);
        m->mount_point = NULL;
     }
   if (m->mount_fail)
     m->mount_fail(m->data);
}

static void
_e_fm2_device_unmount_ok(E_Fm2_Mount *m)
{
   if (!m->mounted) return;
   m->mounted = EINA_FALSE;
   if (m->mount_point)
     {
        eina_stringshare_del(m->mount_point);
        m->mount_point = NULL;
     }
   if (m->unmount_ok)
     m->unmount_ok(m->data);
}

static void
_e_fm2_device_unmount_fail(E_Fm2_Mount *m)
{
   if (m->mounted) return;
   m->mounted = EINA_TRUE;
   if (m->unmount_fail)
     m->unmount_fail(m->data);
}

static void
_e_fm2_device_check_desktop_icons_cb_end(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED)
{
   _check_vols = NULL;
   if (_check_run_show)
     e_fm2_device_show_desktop_icons();
   else if (_check_run_hide)
     e_fm2_device_hide_desktop_icons();
   _check_run_hide = _check_run_show = EINA_FALSE;
}

static void
_e_fm2_device_check_desktop_icons_list_cb(const char *name, const char *path, void *data)
{
   char buf[PATH_MAX], buf2[PATH_MAX];
   Eina_List *l;
   char *file;

   if (path[0] != '|') return;  // not e17 link
   if (!eina_str_has_extension(name, ".desktop")) return;
   snprintf(buf, sizeof(buf), "%s/%s", path, name);
   if (ecore_file_size(buf) > 200) return;  // unlikely that we're going to write a desktop file this long
   e_user_dir_snprintf(buf2, sizeof(buf2), "fileman/favorites/%s", name);
   if (!ecore_file_exists(buf2)) return;
   EINA_LIST_FOREACH(data, l, file)
     if (!strcmp(file, buf)) return;  // valid link

   ecore_file_unlink(buf);
   ecore_file_unlink(buf2);
}

static void
_e_fm2_device_check_desktop_icons_cb(void *data, Ecore_Thread *eth EINA_UNUSED)
{
   Eina_List *list = data;

   eina_file_dir_list(efreet_desktop_dir_get(), EINA_FALSE, _e_fm2_device_check_desktop_icons_list_cb, data);

   E_FREE_LIST(list, free);
}

E_API void
e_fm2_device_show_desktop_icons(void)
{
   Eina_List *l;
   E_Volume *v;
   char buf[PATH_MAX] = {0};
   char buf2[PATH_MAX] = {0};
   const char *id;

   if (_check_vols)
     {
        _check_run_hide = EINA_FALSE;
        _check_run_show = EINA_TRUE;
        return;
     }

   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (!v) continue;
        if (!v->storage) continue;

        id = ecore_file_file_get(v->storage->udi);

        e_user_dir_snprintf(buf, sizeof(buf),
                            "fileman/favorites/|%s_%d.desktop",
                            id, v->partition_number);

        snprintf(buf2, sizeof(buf2), "%s/|%s_%d.desktop",
                 efreet_desktop_dir_get(), id, v->partition_number);

        if (ecore_file_exists(buf) && !ecore_file_exists(buf2))
          {
             ecore_file_symlink(buf, buf2);
             _e_fm2_file_force_update(buf2);
          }
     }
}

E_API void
e_fm2_device_hide_desktop_icons(void)
{
   Eina_List *l;
   E_Volume *v;
   char buf[PATH_MAX] = {0};
   const char *id;

   if (_check_vols)
     {
        _check_run_show = EINA_FALSE;
        _check_run_hide = EINA_TRUE;
        return;
     }

   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (!v) continue;
        if (!v->storage) continue;

        id = ecore_file_file_get(v->storage->udi);

        snprintf(buf, sizeof(buf), "%s/|%s_%d.desktop",
                 efreet_desktop_dir_get(), id, v->partition_number);

        if (ecore_file_exists(buf))
          {
             ecore_file_unlink(buf);
             _e_fm2_file_force_update(buf);
          }
     }
}

E_API void
e_fm2_device_check_desktop_icons(void)
{
   Eina_List *l, *thd = NULL;
   E_Volume *v;
   char buf[PATH_MAX] = {0};
   const char *id;

   if (_check_vols) return;
   EINA_LIST_FOREACH(_e_vols, l, v)
     {
        if (!v) continue;
        if (!v->storage) continue;

        id = ecore_file_file_get(v->storage->udi);

        snprintf(buf, sizeof(buf), "%s/|%s_%d.desktop",
                 efreet_desktop_dir_get(), id, v->partition_number);
        thd = eina_list_append(thd, strdup(buf));
     }
   _check_vols = ecore_thread_run(_e_fm2_device_check_desktop_icons_cb, _e_fm2_device_check_desktop_icons_cb_end, _e_fm2_device_check_desktop_icons_cb_end, thd);
}

E_API Eina_List *
e_fm2_device_volume_list_get(void)
{
   return _e_vols;
}

#include "e.h"

/* local types */
typedef struct _E_Fm2_Mime_Handler_Tuple E_Fm2_Mime_Handler_Tuple;
struct _E_Fm2_Mime_Handler_Tuple
{
   Eina_List  *list;
   const char *str;
};

/* local subsystem functions */
static Eina_Bool _e_fm2_mime_handler_glob_match_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata);
static Eina_Bool _e_fm_mime_icon_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata);

static Eina_Hash *icon_map = NULL;
static Eina_Hash *_mime_handlers = NULL;
static Eina_Hash *_glob_handlers = NULL;

/* externally accessible functions */
E_API const char *
e_fm_mime_filename_get(const char *fname)
{
   return efreet_mime_globs_type_get(fname);
}

/* returns:
 * NULL == don't know
 * "THUMB" == generate a thumb
 * "e/icons/fileman/mime/..." == theme icon
 * "/path/to/file....edj" = explicit icon edje file
 * "/path/to/file..." = explicit image file to use
 */
E_API const char *
e_fm_mime_icon_get(const char *mime)
{
   char buf[4096], buf2[4096], *val;
   Eina_List *l = NULL;
   E_Config_Mime_Icon *mi;
   size_t len;

   /* 0.0 clean out hash cache once it has more than 512 entries in it */
   if (eina_hash_population(icon_map) > 512) e_fm_mime_icon_cache_flush();

   /* 0. look in mapping cache */
   val = eina_hash_find(icon_map, mime);
   if (val) return val;

   eina_strlcpy(buf2, mime, sizeof(buf2));
   val = strchr(buf2, '/');
   if (val) *val = 0;

   /* 1. look up in mapping to file or thumb (thumb has flag)*/
   EINA_LIST_FOREACH(e_config->mime_icons, l, mi)
     {
        if (e_util_glob_match(mi->mime, mime))
          {
             eina_strlcpy(buf, mi->icon, sizeof(buf));
             goto ok;
          }
     }

   /* 2. look up in ~/.e/e/icons */
   len = e_user_dir_snprintf(buf, sizeof(buf), "icons/%s.edj", mime);
   if (len >= sizeof(buf))
     goto try_e_icon_generic;

   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "svg", sizeof("svg"));
   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "png", sizeof("png"));
   if (ecore_file_exists(buf)) goto ok;

try_e_icon_generic:
   len = e_user_dir_snprintf(buf, sizeof(buf), "icons/%s.edj", buf2);
   if (len >= sizeof(buf))
     goto try_theme;

   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "svg", sizeof("svg"));
   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "png", sizeof("png"));
   if (ecore_file_exists(buf)) goto ok;

   /* 3. look up icon in theme */
try_theme:
   memcpy(buf, "e/icons/fileman/mime/", sizeof("e/icons/fileman/mime/"));
   eina_strlcpy(buf + sizeof("e/icons/fileman/mime/") - 1, mime,
                sizeof(buf) - (sizeof("e/icons/fileman/mime/") - 1));
   val = (char *)e_theme_edje_file_get("base/theme/fileman", buf);
   if ((val) && (e_util_edje_collection_exists(val, buf))) goto ok;

   eina_strlcpy(buf + sizeof("e/icons/fileman/mime/") - 1, buf2,
                sizeof(buf) - (sizeof("e/icons/fileman/mime/") - 1));
   val = (char *)e_theme_edje_file_get("base/theme/fileman", buf);
   if ((val) && (e_util_edje_collection_exists(val, buf))) goto ok;

   /* 4. look up icon in PREFIX/share/enlightent/data/icons */
   len = e_prefix_data_snprintf(buf, sizeof(buf), "data/icons/%s.edj", mime);
   if (len >= sizeof(buf))
     goto try_efreet_icon_generic;

   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "svg", sizeof("svg"));
   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "png", sizeof("png"));
   if (ecore_file_exists(buf)) goto ok;

try_efreet_icon_generic:
   len = e_prefix_data_snprintf(buf, sizeof(buf), "data/icons/%s.edj", buf2);
   if (len >= sizeof(buf))
     goto try_efreet_icon_generic;

   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "svg", sizeof("svg"));
   if (ecore_file_exists(buf)) goto ok;
   memcpy(buf + len - (sizeof("edj") - 1), "png", sizeof("png"));
   if (ecore_file_exists(buf)) goto ok;

   return NULL;

ok:
   val = (char *)eina_stringshare_add(buf);
   if (!icon_map) icon_map = eina_hash_string_superfast_new(NULL);
   eina_hash_add(icon_map, mime, val);
   return val;
}

E_API void
e_fm_mime_icon_cache_flush(void)
{
   Eina_List *freelist = NULL;

   eina_hash_foreach(icon_map, _e_fm_mime_icon_foreach, &freelist);
   E_FREE_LIST(freelist, eina_stringshare_del);
   eina_hash_free(icon_map);
   icon_map = NULL;
}

/* create (allocate), set properties, and return a new mime handler */
E_API E_Fm2_Mime_Handler *
e_fm2_mime_handler_new(const char *label, const char *icon_group, void (*action_func)(void *data, Evas_Object *obj, const char *path), void *action_data, int(test_func) (void *data, Evas_Object * obj, const char *path), void *test_data)
{
   E_Fm2_Mime_Handler *handler;

   if ((!label) || (!action_func)) return NULL;

   handler = E_NEW(E_Fm2_Mime_Handler, 1);
   if (!handler) return NULL;

   handler->label = eina_stringshare_add(label);
   handler->icon_group = icon_group ? eina_stringshare_add(icon_group) : NULL;
   handler->action_func = action_func;
   handler->action_data = action_data;
   handler->test_func = test_func;
   handler->test_data = test_data;

   return handler;
}

E_API void
e_fm2_mime_handler_free(E_Fm2_Mime_Handler *handler)
{
   if (!handler) return;

   eina_stringshare_del(handler->label);
   if (handler->icon_group) eina_stringshare_del(handler->icon_group);
   E_FREE(handler);
}

/* associate a certain mime type with a handler */
E_API Eina_Bool
e_fm2_mime_handler_mime_add(E_Fm2_Mime_Handler *handler, const char *mime)
{
   Eina_List *handlers = NULL;

   if ((!handler) || (!mime)) return 0;

   /* if there's an entry for this mime already, then append to its list */
   if ((handlers = eina_hash_find(_mime_handlers, mime)))
     {
        handlers = eina_list_append(handlers, handler);
        eina_hash_modify(_mime_handlers, mime, handlers);
     }
   else
     {
        /* no previous entry for this mime, lets add one */
        handlers = eina_list_append(handlers, handler);
        if (!_mime_handlers) _mime_handlers = eina_hash_string_superfast_new(NULL);
        eina_hash_add(_mime_handlers, mime, handlers);
     }

   return 1;
}

/* associate a certain glob with a handler */
E_API Eina_Bool
e_fm2_mime_handler_glob_add(E_Fm2_Mime_Handler *handler, const char *glob_)
{
   Eina_List *handlers = NULL;

   if ((!handler) || (!glob_)) return 0;

   /* if there's an entry for this glob already, then append to its list */
   if ((handlers = eina_hash_find(_glob_handlers, glob_)))
     {
        handlers = eina_list_append(handlers, handler);
        eina_hash_modify(_glob_handlers, glob_, handlers);
     }
   else
     {
        /* no previous entry for this glob, lets add one */
        handlers = eina_list_append(handlers, handler);
        if (!_glob_handlers) _glob_handlers = eina_hash_string_superfast_new(NULL);
        eina_hash_add(_glob_handlers, glob_, handlers);
     }

   return 1;
}

/* delete a certain handler for a certain mime */
E_API void
e_fm2_mime_handler_mime_del(E_Fm2_Mime_Handler *handler, const char *mime)
{
   Eina_List *handlers = NULL;

   if ((!handler) || (!mime)) return;

   /* if there's an entry for this mime already, then remove from list */
   if ((handlers = eina_hash_find(_mime_handlers, mime)))
     {
        handlers = eina_list_remove(handlers, handler);
        if (handlers)
          eina_hash_modify(_mime_handlers, mime, handlers);
        else
          {
             eina_hash_del(_mime_handlers, mime, handlers);
             if (!eina_hash_population(_mime_handlers))
               {
                  eina_hash_free(_mime_handlers);
                  _mime_handlers = NULL;
               }
          }
     }
}

/* delete a certain handler for a certain glob */
E_API void
e_fm2_mime_handler_glob_del(E_Fm2_Mime_Handler *handler, const char *glob_)
{
   Eina_List *handlers = NULL;

   if ((!handler) || (!glob_)) return;

   /* if there's an entry for this glob already, then remove from list */
   if ((handlers = eina_hash_find(_glob_handlers, glob_)))
     {
        handlers = eina_list_remove(handlers, handler);
        if (handlers)
          eina_hash_modify(_glob_handlers, glob_, handlers);
        else
          {
             eina_hash_del(_glob_handlers, glob_, handlers);
             if (!eina_hash_population(_glob_handlers))
               {
                  eina_hash_free(_glob_handlers);
                  _glob_handlers = NULL;
               }
          }
     }
}

E_API const Eina_List *
e_fm2_mime_handler_mime_handlers_get(const char *mime)
{
   if ((!mime) || (!_mime_handlers)) return NULL;
   return eina_hash_find(_mime_handlers, mime);
}

/* get the list of glob handlers for a glob.
   NOTE: the list should be free()'ed */
E_API Eina_List *
e_fm2_mime_handler_glob_handlers_get(const char *glob_)
{
   E_Fm2_Mime_Handler_Tuple *tuple = NULL;
   Eina_List *handlers = NULL;

   if ((!glob_) || (!_glob_handlers)) return NULL;

   tuple = E_NEW(E_Fm2_Mime_Handler_Tuple, 1);
   tuple->list = NULL;
   tuple->str = glob_;
   eina_hash_foreach(_glob_handlers, _e_fm2_mime_handler_glob_match_foreach, tuple);
   handlers = tuple->list;
   E_FREE(tuple);
   return handlers;
}

/* call a certain handler */
E_API Eina_Bool
e_fm2_mime_handler_call(E_Fm2_Mime_Handler *handler, Evas_Object *obj, const char *path)
{
   if ((!handler) || (!obj) || (!path) || (!handler->action_func))
     return 0;

   if (handler->test_func)
     {
        if (handler->test_func(handler->test_data, obj, path))
          {
             handler->action_func(handler->action_data, obj, path);
             return 1;
          }
        else
          return 0;
     }

   handler->action_func(handler->action_data, obj, path);
   return 1;
}

/* call all handlers related to a certain mime */
E_API void
e_fm2_mime_handler_mime_handlers_call_all(Evas_Object *obj, const char *path, const char *mime)
{
   const Eina_List *l, *handlers;
   E_Fm2_Mime_Handler *handler = NULL;

   if ((!obj) || (!path) || (!mime)) return;

   handlers = e_fm2_mime_handler_mime_handlers_get(mime);
   if (!handlers) return;

   EINA_LIST_FOREACH(handlers, l, handler)
     {
        if (!handler) continue;

        e_fm2_mime_handler_call(handler, obj, path);
     }
}

/* call all handlers related to a certain glob */
E_API void
e_fm2_mime_handler_glob_handlers_call_all(Evas_Object *obj, const char *path, const char *glob_)
{
   Eina_List *handlers = NULL;
   Eina_List *l = NULL;
   E_Fm2_Mime_Handler *handler = NULL;

   if ((!obj) || (!path) || (!glob_)) return;

   handlers = e_fm2_mime_handler_glob_handlers_get(glob_);
   if (!handlers) return;

   EINA_LIST_FOREACH(handlers, l, handler)
     {
        if (!handler) continue;

        e_fm2_mime_handler_call(handler, obj, path);
     }
}

/* run a handlers test function */
E_API Eina_Bool
e_fm2_mime_handler_test(E_Fm2_Mime_Handler *handler, Evas_Object *obj, const char *path)
{
   if ((!handler) || (!obj) || (!path)) return 0;
   if (!handler->test_func) return 1;

   return handler->test_func(handler->test_data, obj, path);
}

/* local subsystem functions */
/* used to loop a glob hash and determine if the glob handler matches the filename */
static Eina_Bool
_e_fm2_mime_handler_glob_match_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
{
   E_Fm2_Mime_Handler_Tuple *tuple;
   Eina_List *handlers = NULL;
   Eina_List *l = NULL;
   void *handler = NULL;

   tuple = fdata;
   if (e_util_glob_match(tuple->str, key))
     {
        handlers = data;
        EINA_LIST_FOREACH(handlers, l, handler)
          {
             if (handler)
               tuple->list = eina_list_append(tuple->list, handler);
          }
     }

   return 1;
}

static Eina_Bool
_e_fm_mime_icon_foreach(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata)
{
   Eina_List **freelist;

   freelist = fdata;
   *freelist = eina_list_append(*freelist, data);
   return 1;
}

#include "config.h"

#ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
#endif

#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#elif defined __GNUC__
# define alloca __builtin_alloca
#elif defined _AIX
# define alloca __alloca
#elif defined _MSC_VER
# include <malloc.h>
# define alloca _alloca
#else
# include <stddef.h>
# ifdef  __cplusplus
extern "C"
# endif
void *alloca(size_t);
#endif

#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <errno.h>
#include <limits.h>
#include <fcntl.h>

#include <Ecore.h>
#include <Ecore_File.h>

#include <eina_stringshare.h>

#define E_TYPEDEFS
#include "e_fm_op.h"
#undef E_TYPEDEFS
#include "e_fm_op.h"

#define READBUFSIZE     65536
#define COPYBUFSIZE     16384
#define REMOVECHUNKSIZE 4096
#define NB_PASS         3
#define SYNC_TIME       0.5
#define SYNC_BYTES      (5 * 1024 * 1024)

#define E_FREE(p) do { free(p); p = NULL; } while (0)

#define _E_FM_OP_ERROR_SEND_SCAN(_task, _e_fm_op_error_type, _fmt, ...)                      \
  do                                                                                         \
    {                                                                                        \
       int _errno = errno;                                                                   \
       _e_fm_op_scan_error = 1;                                                              \
       _e_fm_op_send_error(_task, _e_fm_op_error_type, _fmt, __VA_ARGS__, strerror(_errno)); \
       return 1;                                                                             \
    }                                                                                        \
  while (0)

#define _E_FM_OP_ERROR_SEND_WORK(_task, _e_fm_op_error_type, _fmt, ...)                      \
  do                                                                                         \
    {                                                                                        \
       int _errno = errno;                                                                   \
       _e_fm_op_work_error = 1;                                                              \
       _e_fm_op_send_error(_task, _e_fm_op_error_type, _fmt, __VA_ARGS__, strerror(_errno)); \
       return 1;                                                                             \
    }                                                                                        \
  while (0)

typedef struct _E_Fm_Op_Task      E_Fm_Op_Task;
typedef struct _E_Fm_Op_Copy_Data E_Fm_Op_Copy_Data;

static E_Fm_Op_Task *_e_fm_op_task_new(void);
static void          _e_fm_op_task_free(void *t);

static void          _e_fm_op_remove_link_task(E_Fm_Op_Task *task);
static Eina_Bool     _e_fm_op_stdin_data(void *data, Ecore_Fd_Handler *fd_handler);
static void          _e_fm_op_set_up_idlers(void);
static void          _e_fm_op_delete_idler(int *mark);
static int           _e_fm_op_idler_handle_error(int *mark, Eina_List **queue, Eina_List **node, E_Fm_Op_Task *task);

static Eina_Bool     _e_fm_op_work_idler(void *data);
static Eina_Bool     _e_fm_op_scan_idler(void *data);

static void          _e_fm_op_send_error(E_Fm_Op_Task *task, E_Fm_Op_Type type, const char *fmt, ...) EINA_PRINTF(3, 4);
static void          _e_fm_op_rollback(E_Fm_Op_Task *task);
static void          _e_fm_op_update_progress_report_simple(int percent, const char *src, const char *dst);
static void          _e_fm_op_update_progress(E_Fm_Op_Task *task, off_t _plus_e_fm_op_done, off_t _plus_e_fm_op_total);
static void          _e_fm_op_copy_stat_info(E_Fm_Op_Task *task);
static int           _e_fm_op_handle_overwrite(E_Fm_Op_Task *task);

static int           _e_fm_op_copy_dir(E_Fm_Op_Task *task);
static int           _e_fm_op_copy_link(E_Fm_Op_Task *task);
static int           _e_fm_op_copy_fifo(E_Fm_Op_Task *task);
static int           _e_fm_op_open_files(E_Fm_Op_Task *task);
static int           _e_fm_op_copy_chunk(E_Fm_Op_Task *task);

static int           _e_fm_op_copy_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_scan_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_copy_stat_info_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_symlink_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_remove_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_rename_atom(E_Fm_Op_Task *task);
static int           _e_fm_op_destroy_atom(E_Fm_Op_Task *task);
static void          _e_fm_op_random_buf(char *buf, ssize_t len);
static void          _e_fm_op_random_char(char *buf, size_t len);

static int           _e_fm_op_make_copy_name(const char *abs, char *buf, size_t buf_size);

Eina_List *_e_fm_op_work_queue = NULL, *_e_fm_op_scan_queue = NULL;
Ecore_Idler *_e_fm_op_work_idler_p = NULL, *_e_fm_op_scan_idler_p = NULL;

int _e_fm_op_abort = 0; /* Abort mark. */
int _e_fm_op_scan_error = 0;
int _e_fm_op_work_error = 0;
int _e_fm_op_overwrite = 0;

int _e_fm_op_error_response = E_FM_OP_NONE;
int _e_fm_op_overwrite_response = E_FM_OP_NONE;

Eina_List *_e_fm_op_separator = NULL;

char *_e_fm_op_stdin_buffer = NULL;

double _e_fm_op_last_sync = 0.0;
unsigned long long _e_fm_op_bytes_sync = 0;

struct _E_Fm_Op_Task
{
   struct
   {
      const char *name;
      struct stat st;
   } src;

   struct
   {
      const char *name;
      size_t      done;
   } dst;

   int           started, finished;
   unsigned int  passes;
   off_t         pos;

   void         *data;

   E_Fm_Op_Task *parent;
   E_Fm_Op_Type  type;
   E_Fm_Op_Type  overwrite;

   Eina_List    *link;
};

struct _E_Fm_Op_Copy_Data
{
   FILE *from;
   FILE *to;
};

int
main(int argc, char **argv)
{
   int i, last;
   E_Fm_Op_Type type;

   ecore_app_no_system_modules();
   ecore_init();

   _e_fm_op_stdin_buffer = malloc(READBUFSIZE);
   if (!_e_fm_op_stdin_buffer) return 0;

   ecore_main_fd_handler_add(STDIN_FILENO, ECORE_FD_READ, _e_fm_op_stdin_data,
                             NULL, NULL, NULL);

   if (argc < 3) return 0;

   last = argc - 1;
   i = 2;

   if (!strcmp(argv[1], "cp")) type = E_FM_OP_COPY;
   else if (!strcmp(argv[1], "mv"))
     type = E_FM_OP_MOVE;
   else if (!strcmp(argv[1], "rm"))
     type = E_FM_OP_REMOVE;
   else if (!strcmp(argv[1], "srm"))
     type = E_FM_OP_SECURE_REMOVE;
   else if (!strcmp(argv[1], "lns"))
     type = E_FM_OP_SYMLINK;
   else if (!strcmp(argv[1], "mvf"))
     type = E_FM_OP_RENAME;
   else return 0;

   if (type == E_FM_OP_SECURE_REMOVE)
     {
        _e_fm_op_work_queue = eina_list_append(_e_fm_op_work_queue, NULL);
        _e_fm_op_separator = _e_fm_op_work_queue;
     }

   if ((type == E_FM_OP_COPY) ||
       (type == E_FM_OP_SYMLINK) ||
       (type == E_FM_OP_MOVE) ||
       (type == E_FM_OP_RENAME))
     {
        if (argc < 4) goto quit;

        if (ecore_file_is_dir(argv[last]))
          {
             char buf[PATH_MAX];
             char *p2, *p3;
             int p2_len, last_len, done, total;

             p2 = ecore_file_realpath(argv[last]);
             if (!p2) goto quit;
             p2_len = strlen(p2);

             last_len = strlen(argv[last]);
             if ((last_len < 1) || (last_len + 2 >= PATH_MAX))
               {
                  E_FREE(p2);
                  goto quit;
               }
             memcpy(buf, argv[last], last_len);
             if (buf[last_len - 1] != '/')
               {
                  buf[last_len] = '/';
                  last_len++;
               }

             p3 = buf + last_len;

             done = 0;
             total = last - 2;

             for (; i < last; i++)
               {
                  const char *name;
                  size_t name_len;
                  char *dir;
                  int r;
                  Eina_Bool make_copy;

                  /* Don't move a dir into itself */
                  if (ecore_file_is_dir(argv[i]) &&
                      (strcmp(argv[i], p2) == 0))
                    goto skip_arg;

                  make_copy = EINA_FALSE;
                  /* Don't move/rename/symlink into same directory
                   * in case of copy, make copy name automatically */
                  dir = ecore_file_dir_get(argv[i]);
                  if (dir)
                    {
                       r = (strcmp(dir, p2) == 0);
                       E_FREE(dir);
                       if (r)
                         {
                            if (type == E_FM_OP_COPY) make_copy = EINA_TRUE;
                            else goto skip_arg;
                         }
                    }

                  name = ecore_file_file_get(argv[i]);
                  if (!name) goto skip_arg;
                  name_len = strlen(name);
                  if (p2_len + name_len >= PATH_MAX) goto skip_arg;
                  memcpy(p3, name, name_len + 1);
                  if (make_copy)
                    {
                      if (_e_fm_op_make_copy_name(buf, p3, PATH_MAX - p2_len - 1))
                         goto skip_arg;
                    }

                  if ((type == E_FM_OP_SYMLINK) &&
                      (symlink(argv[i], buf) == 0))
                    {
                       done++;
                       _e_fm_op_update_progress_report_simple
                         (done * 100 / total, argv[i], buf);
                    }
                  else
                    {
                       if (type == E_FM_OP_MOVE)
                         {
                            if (!strcmp(argv[i], buf))
                              goto skip_arg;

                            if (buf[0] != '/')
                              {
                                 free(p2);
                                 _E_FM_OP_ERROR_SEND_SCAN(0, E_FM_OP_ERROR,
                                                          "Unknown destination '%s': %s.", buf);
                              }
                         }
                       else if (type == E_FM_OP_RENAME)
                         {
                            if (!strcmp(argv[i], buf))
                              goto skip_arg;

                            if (buf[0] != '/')
                              {
                                 free(p2);
                                 _E_FM_OP_ERROR_SEND_SCAN(0, E_FM_OP_ERROR,
                                                          "Unknown destination '%s': %s.", buf);
                              }

                            if (access(buf, F_OK) == -1)
                              {
                                 /* race condition, i know, but it's
                                  * unvoidable. */
                                 if (rename(argv[i], buf) == -1)
                                   {
                                      /* if it's another device */
                                      if (errno == EXDEV)
                                        type = E_FM_OP_MOVE;
                                      else
                                        {
                                           free(p2);
                                           _E_FM_OP_ERROR_SEND_SCAN(0, E_FM_OP_ERROR,
                                                                    "Cannot move '%s' to '%s': %s.",
                                                                    argv[i], buf);
                                        }
                                   }
                                 else
                                   {
                                      done++;
                                      _e_fm_op_update_progress_report_simple
                                        (done * 100 / total, argv[i], buf);
                                      goto skip_arg;
                                   }
                              }
                            else
                              {
                                 /* if the destination file already exists,
                                  * store a task which handles overwrite */
                                 struct stat st1;
                                 struct stat st2;
                                 if ((stat(argv[i], &st1) == 0) &&
                                     (stat(buf, &st2) == 0))
                                   {
                                      /* if files are not on the same device */
                                      if (st1.st_dev != st2.st_dev)
                                        type = E_FM_OP_MOVE;
                                   }
                                 else type = E_FM_OP_MOVE;
                              }
                         }

                       if (type == E_FM_OP_MOVE)
                         {
                            _e_fm_op_work_queue = eina_list_append(_e_fm_op_work_queue, NULL);
                            _e_fm_op_separator = _e_fm_op_work_queue;
                         }

                       E_Fm_Op_Task *task;

                       task = _e_fm_op_task_new();
                       task->type = type;
                       task->src.name = eina_stringshare_add(argv[i]);
                       task->dst.name = eina_stringshare_add(buf);

                       _e_fm_op_scan_queue =
                         eina_list_append(_e_fm_op_scan_queue, task);
                    }

skip_arg:
                  continue;
               }

             E_FREE(p2);
          }
        else if (argc == 4)
          {
             char *p, *p2;

             p = ecore_file_realpath(argv[2]);
             p2 = ecore_file_realpath(argv[3]);

             /* Don't move a file on top of itself. */
             i = (strcmp(p, p2) == 0);
             E_FREE(p);
             E_FREE(p2);
             if (i) goto quit;

             if ((type == E_FM_OP_SYMLINK) &&
                 (symlink(argv[2], argv[3]) == 0))
               {
                  _e_fm_op_update_progress_report_simple(100, argv[2], argv[3]);
                  goto quit;
               }
             else
               {
                  if (type == E_FM_OP_MOVE)
                    type = E_FM_OP_RENAME;

                  E_Fm_Op_Task *task;

                  task = _e_fm_op_task_new();
                  task->type = type;
                  task->src.name = eina_stringshare_add(argv[2]);
                  task->dst.name = eina_stringshare_add(argv[3]);
                  _e_fm_op_scan_queue = eina_list_append(_e_fm_op_scan_queue, task);
               }
          }
        else
          goto quit;
     }
   else if ((type == E_FM_OP_REMOVE) || (type == E_FM_OP_SECURE_REMOVE))
     {
        E_Fm_Op_Task *task;

        while (i <= last)
          {
             task = _e_fm_op_task_new();
             task->type = type;
             task->src.name = eina_stringshare_add(argv[i]);
             _e_fm_op_scan_queue = eina_list_append(_e_fm_op_scan_queue, task);
             i++;
          }
     }

   _e_fm_op_set_up_idlers();

   _e_fm_op_last_sync = ecore_time_get();
   ecore_main_loop_begin();

quit:
   ecore_shutdown();

   E_FREE(_e_fm_op_stdin_buffer);

   E_FM_OP_DEBUG("Slave quit.\n");

   return 0;
}

/* Create new task. */
static E_Fm_Op_Task *
_e_fm_op_task_new()
{
   E_Fm_Op_Task *t;

   t = malloc(sizeof(E_Fm_Op_Task));
   t->src.name = NULL;
   memset(&(t->src.st), 0, sizeof(struct stat));

   t->dst.name = NULL;
   t->dst.done = 0;
   t->started = 0;
   t->finished = 0;
   t->data = NULL;
   t->type = E_FM_OP_NONE;
   t->overwrite = E_FM_OP_NONE;
   t->link = NULL;
   t->pos = t->passes = 0;

   return t;
}

/* Free task. */
static void
_e_fm_op_task_free(void *t)
{
   E_Fm_Op_Task *task = t;
   E_Fm_Op_Copy_Data *data;

   if (!task) return;

   if (task->src.name) eina_stringshare_del(task->src.name);
   if (task->dst.name) eina_stringshare_del(task->dst.name);

   if (task->data)
     {
        data = task->data;
        if (task->type == E_FM_OP_COPY)
          {
             if (data->from) fclose(data->from);
             if (data->to) fclose(data->to);
          }
        E_FREE(task->data);
     }
   E_FREE(task);
}

/* Removes link task from work queue.
 * Link task is not NULL in case of MOVE. Then two tasks are created: copy and remove.
 * Remove task is a link task for the copy task. If copy task is aborted (e.g. error
 * occurred and user chooses to ignore this), then the remove task is removed from
 * queue with this functions.
 */
static void
_e_fm_op_remove_link_task(E_Fm_Op_Task *task)
{
   E_Fm_Op_Task *ltask;

   if (!task) return;
   if (task->link)
     {
        ltask = eina_list_data_get(task->link);
        _e_fm_op_work_queue =
          eina_list_remove_list(_e_fm_op_work_queue, task->link);
        _e_fm_op_task_free(ltask);
        task->link = NULL;
     }
}

/*
 * Handles data from STDIN.
 * Received data must be in this format:
 * 1) (int) magic number,
 * 2) (int) id,
 * 3) (int) message length.
 * Right now message length is always 0. Id is what matters.
 *
 * This function uses a couple of static variables and a global
 * variable _e_fm_op_stdin_buffer to deal with a situation, when read()
 * did not actually read enough data.
 */
static Eina_Bool
_e_fm_op_stdin_data(void *data EINA_UNUSED, Ecore_Fd_Handler *fd_handler)
{
   int fd;
   static char *buf = NULL;
   static int length = 0;
   char *begin = NULL;
   ssize_t num = 0;
   int msize, identity;

   fd = ecore_main_fd_handler_fd_get(fd_handler);
   if (!buf)
     {
        buf = _e_fm_op_stdin_buffer;
        length = 0;
     }

   num = read(fd, buf, READBUFSIZE - length);

   if (num == 0)
     {
        E_FM_OP_DEBUG("STDIN was closed. Abort. \n");
        _e_fm_op_abort = 1;
     }
   else if (num < 0)
     {
        E_FM_OP_DEBUG("Error while reading from STDIN: read returned -1. (%s) Abort. \n", strerror(errno));
        _e_fm_op_abort = 1;
     }
   else
     {
        length += num;

        buf = _e_fm_op_stdin_buffer;
        begin = _e_fm_op_stdin_buffer;

        while (length >= ((int)(3 * sizeof(int))))
          {
             begin = buf;

             /* Check magic. */
             if (*((int *)(void *)buf) != E_FM_OP_MAGIC)
               {
                  E_FM_OP_DEBUG("Error while reading from STDIN: magic is not correct!\n");
                  break;
               }
             buf += sizeof(int);

             /* Read identifying data. */
             memcpy(&identity, buf, sizeof(int));
             buf += sizeof(int);

             /* Read message length. */
             memcpy(&msize, buf, sizeof(int));
             buf += sizeof(int);

             if ((length - 3 * (int)sizeof(int)) < msize)
               {
                  /* There is not enough data to read the whole message. */
                  break;
               }

             length -= (3 * sizeof(int));

             /* You may want to read msize bytes of data too,
              * but currently commands here do not have any data.
              * msize is always 0.
              */
             switch (identity)
               {
                case E_FM_OP_ABORT:
                  _e_fm_op_abort = 1;
                  E_FM_OP_DEBUG("Aborted.\n");
                  break;

                case E_FM_OP_ERROR_RESPONSE_ABORT:
                case E_FM_OP_ERROR_RESPONSE_IGNORE_THIS:
                case E_FM_OP_ERROR_RESPONSE_IGNORE_ALL:
                case E_FM_OP_ERROR_RESPONSE_RETRY:
                  _e_fm_op_error_response = identity;
                  _e_fm_op_set_up_idlers();
                  break;

                case E_FM_OP_OVERWRITE_RESPONSE_NO:
                case E_FM_OP_OVERWRITE_RESPONSE_NO_ALL:
                case E_FM_OP_OVERWRITE_RESPONSE_YES:
                case E_FM_OP_OVERWRITE_RESPONSE_YES_ALL:
                  _e_fm_op_overwrite_response = identity;
                  _e_fm_op_set_up_idlers();
                  E_FM_OP_DEBUG("Overwrite response set.\n");
                  break;
               }
          }
        if (length > 0) memmove(_e_fm_op_stdin_buffer, begin, length);
        buf = _e_fm_op_stdin_buffer + length;
     }

   return ECORE_CALLBACK_RENEW;
}

static void
_e_fm_op_set_up_idlers()
{
   if (!_e_fm_op_scan_idler_p)
     _e_fm_op_scan_idler_p = ecore_idler_add(_e_fm_op_scan_idler, NULL);
   if (!_e_fm_op_work_idler_p)
     _e_fm_op_work_idler_p = ecore_idler_add(_e_fm_op_work_idler, NULL);
}

static void
_e_fm_op_delete_idler(int *mark)
{
   if (mark == &_e_fm_op_work_error)
     {
        ecore_idler_del(_e_fm_op_work_idler_p);
        _e_fm_op_work_idler_p = NULL;
     }
   else
     {
        ecore_idler_del(_e_fm_op_scan_idler_p);
        _e_fm_op_scan_idler_p = NULL;
     }
}

/* Code to deal with overwrites and errors in idlers.
 * Basically, it checks if we got a response.
 * Returns 1 if we did; otherwise checks it and does what needs to be done.
 */
static int
_e_fm_op_idler_handle_error(int *mark, Eina_List **queue, Eina_List **node, E_Fm_Op_Task *task)
{
   if (_e_fm_op_overwrite)
     {
        if (_e_fm_op_overwrite_response != E_FM_OP_NONE)
          {
             task->overwrite = _e_fm_op_overwrite_response;
             _e_fm_op_work_error = 0;
             _e_fm_op_scan_error = 0;
          }
        else
          {
             /* No response yet. */
             /* So, delete this idler. It'll be added back when response is there. */
             _e_fm_op_delete_idler(mark);
             return 1;
          }
     }
   else if (*mark)
     {
        if (_e_fm_op_error_response == E_FM_OP_NONE)
          {
             /* No response yet. */
             /* So, delete this idler. It'll be added back when response is there. */
             _e_fm_op_delete_idler(mark);
             return 1;
          }
        else
          {
             E_FM_OP_DEBUG("Got response.\n");
             /* Got response. */
             if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_ABORT)
               {
                  /* Mark as abort. */
                  _e_fm_op_abort = 1;
                  _e_fm_op_error_response = E_FM_OP_NONE;
                  _e_fm_op_rollback(task);
               }
             else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_RETRY)
               {
                  *mark = 0;
                  _e_fm_op_error_response = E_FM_OP_NONE;
               }
             else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_THIS)
               {
                  _e_fm_op_rollback(task);
                  _e_fm_op_remove_link_task(task);
                  *queue = eina_list_remove_list(*queue, *node);
                  _e_fm_op_error_response = E_FM_OP_NONE;
                  *mark = 0;
                  *node = NULL;
                  return 1;
               }
             else if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_ALL)
               {
                  E_FM_OP_DEBUG("E_Fm_Op_Task '%s' --> '%s' was automatically aborted.\n",
                                task->src.name, task->dst.name);
                  _e_fm_op_rollback(task);
                  _e_fm_op_remove_link_task(task);
                  *queue = eina_list_remove_list(*queue, *node);
                  *node = NULL;
                  *mark = 0;
                  /* Do not clean out _e_fm_op_error_response. This way when another error
                   * occurs, it would be handled automatically. */
                  return 1;
               }
          }
     }
   else if ((_e_fm_op_work_error) || (_e_fm_op_scan_error))
     return 1;
   return 0;
}

/* This works in a simple way. Take a task from queue and run appropriate _atom() on it.
 * If after _atom() is done, task->finished is 1 remove the task from queue. Otherwise,
 * run _atom() on the same task on next call.
 *
 * If we have an abort (_e_fm_op_abort = 1), then _atom() should recognize it and do smth.
 * After this, just finish everything.
 */
static Eina_Bool
_e_fm_op_work_idler(void *data EINA_UNUSED)
{
   /* E_Fm_Op_Task is marked static here because _e_fm_op_work_queue can be populated with another
    * tasks between calls. So it is possible when a part of file is copied and then
    * another task is pushed into _e_fm_op_work_queue and the new one if performed while
    * the first one is not finished yet. This is not cool, so we make sure one task
    * is performed until it is finished. Well, this can be an issue with removing
    * directories. For example, if we are trying to remove a non-empty directory,
    * then this will go into infinite loop. But this should never happen.
    *
    * BTW, the same is propably right for the _e_fm_op_scan_idler().
    */
   static Eina_List *node = NULL;
   E_Fm_Op_Task *task = NULL;

   if (!node) node = _e_fm_op_work_queue;
   task = eina_list_data_get(node);
   if (!task)
     {
        node = _e_fm_op_work_queue;
        task = eina_list_data_get(node);
     }

   if (!task)
     {
        if ((_e_fm_op_separator) &&
            (_e_fm_op_work_queue == _e_fm_op_separator) &&
            (!_e_fm_op_scan_idler_p))
          {
             /* You may want to look at the comment in _e_fm_op_scan_atom() about this separator thing. */
             _e_fm_op_work_queue = eina_list_remove_list(_e_fm_op_work_queue, _e_fm_op_separator);
             node = NULL;
             return ECORE_CALLBACK_RENEW;
          }

        if ((!_e_fm_op_scan_idler_p) && (!_e_fm_op_work_error) &&
            (!_e_fm_op_scan_error))
          ecore_main_loop_quit();
        // if
        // _e_fm_op_scan_idler_p == NULL &&
        // _e_fm_op_work_error == NULL &&
        // _e_fm_op_scan_error == 1
        // we can spin forever. why are we spinning at all? there are no
        // tasks to be done. we have an error. wait for it to be handled
        if ((!_e_fm_op_work_queue) && (!_e_fm_op_scan_queue))
          ecore_main_loop_quit();

        return ECORE_CALLBACK_RENEW;
     }

   if (_e_fm_op_idler_handle_error(&_e_fm_op_work_error, &_e_fm_op_work_queue, &node, task))
     return ECORE_CALLBACK_RENEW;

   task->started = 1;

   if (task->type == E_FM_OP_COPY)
     _e_fm_op_copy_atom(task);
   else if (task->type == E_FM_OP_REMOVE)
     _e_fm_op_remove_atom(task);
   else if (task->type == E_FM_OP_DESTROY)
     _e_fm_op_destroy_atom(task);
   else if (task->type == E_FM_OP_COPY_STAT_INFO)
     _e_fm_op_copy_stat_info_atom(task);
   else if (task->type == E_FM_OP_SYMLINK)
     _e_fm_op_symlink_atom(task);
   else if (task->type == E_FM_OP_RENAME)
     _e_fm_op_rename_atom(task);

   if (task->finished)
     {
        _e_fm_op_work_queue = eina_list_remove_list(_e_fm_op_work_queue, node);
        _e_fm_op_task_free(task);
        node = NULL;
     }

   if (_e_fm_op_abort)
     {
        /* So, _atom did what it whats in case of abort. Now to idler. */
        ecore_main_loop_quit();
        return ECORE_CALLBACK_CANCEL;
     }

   return ECORE_CALLBACK_RENEW;
}

/* This works pretty much the same as _e_fm_op_work_idler(), except that
 * if this is a dir, then look into its contents and create a task
 * for those files. And we don't have _e_fm_op_separator here.
 */
Eina_Bool
_e_fm_op_scan_idler(void *data EINA_UNUSED)
{
   static Eina_List *node = NULL;
   E_Fm_Op_Task *task = NULL;
   char buf[PATH_MAX];
   static Eina_Iterator *dir = NULL;
   E_Fm_Op_Task *ntask = NULL;

   if (!node) node = _e_fm_op_scan_queue;
   task = eina_list_data_get(node);
   if (!task)
     {
        node = _e_fm_op_scan_queue;
        task = eina_list_data_get(node);
     }

   if (!task)
     {
        _e_fm_op_scan_idler_p = NULL;
        return ECORE_CALLBACK_CANCEL;
     }

   if (_e_fm_op_idler_handle_error(&_e_fm_op_scan_error, &_e_fm_op_scan_queue, &node, task))
     return ECORE_CALLBACK_RENEW;

   if (_e_fm_op_abort)
     {
        /* We're marked for abortion. */
        ecore_main_loop_quit();
        return ECORE_CALLBACK_CANCEL;
     }

   if (task->type == E_FM_OP_COPY_STAT_INFO)
     {
        _e_fm_op_scan_atom(task);
        if (task->finished)
          {
             _e_fm_op_scan_queue =
               eina_list_remove_list(_e_fm_op_scan_queue, node);
             _e_fm_op_task_free(task);
             node = NULL;
          }
     }
   else if (!dir && !task->started)
     {
        if (lstat(task->src.name, &(task->src.st)) < 0)
          _E_FM_OP_ERROR_SEND_SCAN(task, E_FM_OP_ERROR,
                                   "Cannot lstat '%s': %s.", task->src.name);

        if ((task->type != E_FM_OP_SYMLINK) &&
            (task->type != E_FM_OP_RENAME) &&
            S_ISDIR(task->src.st.st_mode))
          {
             /* If it's a dir, then look through it and add a task for each. */

             dir = eina_file_direct_ls(task->src.name);
             if (!dir)
               _E_FM_OP_ERROR_SEND_SCAN(task, E_FM_OP_ERROR,
                                        "Cannot open directory '%s': %s.",
                                        task->dst.name);
          }
        else
          {
             task->started = 1;
             _e_fm_op_scan_queue =
               eina_list_remove_list(_e_fm_op_scan_queue, node);
             node = NULL;
             _e_fm_op_scan_atom(task);
          }
     }
   else if (dir && !task->started)
     {
        Eina_File_Direct_Info *info;

        if (!eina_iterator_next(dir, (void **)&info))
          {
             ntask = _e_fm_op_task_new();
             ntask->type = E_FM_OP_COPY_STAT_INFO;
             ntask->parent = task;
             ntask->src.name = eina_stringshare_add(task->src.name);
             memcpy(&(ntask->src.st), &(task->src.st), sizeof(struct stat));

             if (task->dst.name)
               ntask->dst.name = eina_stringshare_add(task->dst.name);
             else
               ntask->dst.name = NULL;

             if (task->type == E_FM_OP_REMOVE)
               _e_fm_op_scan_queue =
                 eina_list_prepend(_e_fm_op_scan_queue, ntask);
             else
               _e_fm_op_scan_queue =
                 eina_list_append(_e_fm_op_scan_queue, ntask);

             task->started = 1;
             eina_iterator_free(dir);
             dir = NULL;
             node = NULL;
             return ECORE_CALLBACK_RENEW;
          }

        ntask = _e_fm_op_task_new();
        ntask->type = task->type;
        ntask->src.name = eina_stringshare_add(info->path);
        ntask->parent = task;

        if (task->dst.name)
          {
             snprintf(buf, sizeof(buf), "%s/%s", task->dst.name, info->path + info->name_start);
             ntask->dst.name = eina_stringshare_add(buf);
          }
        else
          ntask->dst.name = NULL;

        if (task->type == E_FM_OP_REMOVE)
          _e_fm_op_scan_queue = eina_list_prepend(_e_fm_op_scan_queue, ntask);
        else
          _e_fm_op_scan_queue = eina_list_append(_e_fm_op_scan_queue, ntask);
     }
   else
     {
        _e_fm_op_scan_atom(task);
        if (task->finished)
          {
             _e_fm_op_scan_queue =
               eina_list_remove_list(_e_fm_op_scan_queue, node);
             _e_fm_op_task_free(task);
             node = NULL;
          }
     }

   return ECORE_CALLBACK_RENEW;
}

/* Packs and sends an error to STDOUT.
 * type is either E_FM_OP_ERROR or E_FM_OP_OVERWRITE.
 * fmt is a printf format string, the other arguments
 * are for this format string,
 */
static void EINA_PRINTF(3, 4)
_e_fm_op_send_error(E_Fm_Op_Task *task, E_Fm_Op_Type type, const char *fmt, ...)
{
   va_list ap;
   char buffer[READBUFSIZE];
   char *buf = &buffer[0];
   char *str = buf + (3 * sizeof(int));
   int len = 0;

   va_start(ap, fmt);

   if (_e_fm_op_error_response == E_FM_OP_ERROR_RESPONSE_IGNORE_ALL)
     {
        /* Do nothing. */
     }
   else
     {
        vsnprintf(str, READBUFSIZE - 3 * sizeof(int), fmt, ap);
        len = strlen(str);

        *((int *)(void *)buf) = E_FM_OP_MAGIC;
        *((int *)(void *)(buf + sizeof(int))) = type;
        *((int *)(void *)(buf + (2 * sizeof(int)))) = len + 1;

        if (write(STDOUT_FILENO, buf, (3 * sizeof(int)) + len + 1) < 0)
          perror("write");

        E_FM_OP_DEBUG("%s", str);
        E_FM_OP_DEBUG(" Error sent.\n");
     }

   va_end(ap);
   if (!_e_fm_op_overwrite)
     _e_fm_op_remove_link_task(task);
}

/* Unrolls task: makes a clean up and updates progress info. */
static void
_e_fm_op_rollback(E_Fm_Op_Task *task)
{
   E_Fm_Op_Copy_Data *data;
   E_Fm_Op_Task *t;
   Eina_List *l, *ll;

   if (!task) return;

   EINA_LIST_FOREACH_SAFE(_e_fm_op_scan_queue, l, ll, t)
     {
        if (t == task) continue;
        if (t->parent != task) continue;
        _e_fm_op_scan_queue = eina_list_remove_list(_e_fm_op_scan_queue, l);
        _e_fm_op_task_free(t);
     }
   EINA_LIST_FOREACH_SAFE(_e_fm_op_work_queue, l, ll, t)
     {
        if (t == task) continue;
        if (t->parent != task) continue;
        _e_fm_op_work_queue = eina_list_remove_list(_e_fm_op_work_queue, l);
        _e_fm_op_task_free(t);
     }

   if (task->type == E_FM_OP_COPY)
     {
        data = task->data;
        if (data)
          {
             if (data->from)
               {
                  fclose(data->from);
                  data->from = NULL;
               }
             if (data->to)
               {
                  fclose(data->to);
                  data->to = NULL;
               }
          }
        E_FREE(task->data);
        _e_fm_op_update_progress(task, -task->dst.done,
                                 -task->src.st.st_size - (task->link ? REMOVECHUNKSIZE : 0));
     }
   else
     _e_fm_op_update_progress(task, -REMOVECHUNKSIZE, -REMOVECHUNKSIZE);
}

static void
_e_fm_op_update_progress_report(int percent, int eta, double elapsed, off_t done, off_t total, const char *src, const char *dst)
{
   const int magic = E_FM_OP_MAGIC;
   const int id = E_FM_OP_PROGRESS;
   char *p, *data;
   size_t size, src_len, dst_len;

   if (!dst) return;

   src_len = strlen(src);
   dst_len = strlen(dst);

   size = (2 * sizeof(int)) + (2 * sizeof(off_t)) + src_len + 1 + dst_len + 1;
   data = alloca(3 * sizeof(int) + size);
   if (!data) return;
   p = data;

#define P(value) memcpy(p, &(value), sizeof(int)); p += sizeof(int)
   P(magic);
   P(id);
   P(size);
   P(percent);
   P(eta);
#undef P

#define P(value) memcpy(p, &(value), sizeof(off_t)); p += sizeof(off_t)
   P(done);
   P(total);
#undef P

#define P(value) memcpy(p, value, value ## _len + 1); p += value ## _len + 1
   P(src);
   P(dst);
#undef P

   if (write(STDOUT_FILENO, data, (3 * sizeof(int)) + size) < 0)
     perror("write");

   E_FM_OP_DEBUG("Time left: %d at %e\n", eta, elapsed);
   E_FM_OP_DEBUG("Progress %d. \n", percent);
}

static void
_e_fm_op_update_progress_report_simple(int percent, const char *src, const char *dst)
{
   size_t done = (percent * REMOVECHUNKSIZE) / 100;
   _e_fm_op_update_progress_report
     (percent, 0, 0, done, REMOVECHUNKSIZE, src, dst);
}

/* Updates progress.
 * _plus_data is how much more works is done and _plus_e_fm_op_total
 * is how much more work we found out needs to be done
 * (it is not zero primarily in _e_fm_op_scan_idler())
 *
 * It calculates progress in percent. And once a second calculates eta.
 * If either of them changes from their previuos values, then the are
 * packed and written to STDOUT.
 */
static void
_e_fm_op_update_progress(E_Fm_Op_Task *task, off_t _plus_e_fm_op_done, off_t _plus_e_fm_op_total)
{
   /* Type long long should be 64 bits wide everywhere, this means
    * that its max value is 2^63 - 1, which is 8 388 608 terabytes,
    * and this should be enough. Well, we'll be multipling _e_fm_op_done
    * by 100, but still, it is big enough. */
   static off_t _e_fm_op_done = 0, _e_fm_op_total = 0;

   static int ppercent = -1;
   int percent;
   static double c_time = 0;
   static double s_time = 0;
   double eta = 0;
   static int peta = -1;
   static E_Fm_Op_Task *ptask = NULL;

   _e_fm_op_done += _plus_e_fm_op_done;
   _e_fm_op_total += _plus_e_fm_op_total;

   /* Do not send progress until scan is done.*/
   if (_e_fm_op_scan_idler_p) return;

   if (_e_fm_op_total != 0)
     {
        /* % 101 is for the case when somehow work queue works faster
           than scan queue. _e_fm_op_done * 100 should not cause arithmetic
           overflow, since long long can hold really big values. */
        percent = _e_fm_op_done * 100 / _e_fm_op_total % 101;

        eta = peta;

        if (eina_dbl_exact(s_time, 0)) s_time = ecore_time_get();

        /* Update ETA once a second */
        if ((_e_fm_op_done) && (ecore_time_get() - c_time > 1.0))
          {
             c_time = ecore_time_get();
             eta = (c_time - s_time) * (_e_fm_op_total - _e_fm_op_done) / _e_fm_op_done;
             eta = (int)(eta + 0.5);
          }

        if (!task) return;
        if ((percent != ppercent) || (!EINA_DBL_EQ(eta, peta)) || (task != ptask))
          {
             ppercent = percent;
             peta = eta;
             ptask = task;
             _e_fm_op_update_progress_report(percent, eta, c_time - s_time,
                                             _e_fm_op_done, _e_fm_op_total,
                                             task->src.name, task->dst.name);
          }
     }
}

static void
_syncfs(int fd)
{
#if defined __linux__
   syncfs(fd);
#else
   fsync(fd);
#endif
}

/* We just use this code in several places. */
static void
_e_fm_op_copy_stat_info(E_Fm_Op_Task *task)
{
   struct utimbuf ut;
   double now = ecore_time_get();

   if (!task->dst.name) return;

   if (chmod(task->dst.name, task->src.st.st_mode) < 0)
     perror("chmod");
   if (chown(task->dst.name, task->src.st.st_uid, task->src.st.st_gid) < 0)
     perror("chown");
   ut.actime = task->src.st.st_atime;
   ut.modtime = task->src.st.st_mtime;
   utime(task->dst.name, &ut);
   if ((now - _e_fm_op_last_sync) > SYNC_TIME)
     {
        int fd = open(task->dst.name, O_RDWR);

        if (fd >= 0)
          {
             _syncfs(fd);
             _e_fm_op_last_sync = now;
             close(fd);
          }
     }
}

static int
_e_fm_op_handle_overwrite(E_Fm_Op_Task *task)
{
   struct stat st;

   if ((task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_YES_ALL)
       || (_e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_YES_ALL))
     {
        _e_fm_op_overwrite = 0;
        return 0;
     }
   else if ((task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_YES)
            || (_e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_YES))
     {
        _e_fm_op_overwrite_response = E_FM_OP_NONE;
        _e_fm_op_overwrite = 0;
        return 0;
     }
   else if ((task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_NO)
            || (_e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO))
     {
        task->finished = 1;
        _e_fm_op_rollback(task);
        _e_fm_op_remove_link_task(task);
        _e_fm_op_overwrite_response = E_FM_OP_NONE;
        _e_fm_op_overwrite = 0;
        return 1;
     }
   else if ((task->overwrite == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL)
            || (_e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL))
     {
        task->finished = 1;
        _e_fm_op_rollback(task);
        _e_fm_op_remove_link_task(task);
        _e_fm_op_overwrite = 0;
        return 1;
     }

   if ( stat(task->dst.name, &st) == 0)
     {
        /* File exists. */
        if ( _e_fm_op_overwrite_response == E_FM_OP_OVERWRITE_RESPONSE_NO_ALL)
          {
             task->finished = 1;
             _e_fm_op_rollback(task);
             return 1;
          }
        else
          {
             _e_fm_op_overwrite = 1;
             _e_fm_op_update_progress_report_simple(0, task->src.name, task->dst.name);
             _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_OVERWRITE, "Cannot overwrite '%s': %s.", task->dst.name);
          }
     }

   return 0;
}

static int
_e_fm_op_copy_dir(E_Fm_Op_Task *task)
{
   struct stat st;
   double now = ecore_time_get();

   /* Directory. Just create one in destatation. */
   if (mkdir(task->dst.name,
             S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH |
             S_IXOTH) == -1)
     {
        if (errno == EEXIST)
          {
             if (lstat(task->dst.name, &st) < 0)
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR,
                                        "Cannot lstat '%s': %s.",
                                        task->dst.name);
             if (!S_ISDIR(st.st_mode))
               {
                  /* Let's try to delete the file and create a dir */
                  if (unlink(task->dst.name) == -1)
                    _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR,
                                             "Cannot unlink '%s': %s.",
                                             task->dst.name);
                  if (mkdir(task->dst.name, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1)
                    _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR,
                                             "Cannot make directory '%s': %s.",
                                             task->dst.name);
               }
          }
        else
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR,
                                   "Cannot make directory '%s': %s.",
                                   task->dst.name);
     }
   else
     {
        if ((now - _e_fm_op_last_sync) > SYNC_TIME)
          {
             int fd = open(task->dst.name, O_RDWR);

             if (fd >= 0)
               {
                  _syncfs(fd);
                  _e_fm_op_last_sync = now;
                  close(fd);
               }
          }
     }
   task->dst.done += task->src.st.st_size;
   _e_fm_op_update_progress(task, task->src.st.st_size, 0);

   /* Finish with this task. */
   task->finished = 1;

   return 0;
}

static int
_e_fm_op_copy_link(E_Fm_Op_Task *task)
{
   char *lnk_path;
   size_t lnk_len;
   double now = ecore_time_get();

   lnk_path = ecore_file_readlink(task->src.name);
   if (!lnk_path)
     {
        _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot read link '%s': %s.", task->src.name);
     }

   lnk_len = strlen(lnk_path);

   E_FM_OP_DEBUG("Creating link from '%s' to '%s'\n", lnk_path, task->dst.name);
   _e_fm_op_update_progress_report_simple(0, lnk_path, task->dst.name);

   if (symlink(lnk_path, task->dst.name) == -1)
     {
        char *buf;

        if (errno == EEXIST)
          {
             if (unlink(task->dst.name) == -1)
               {
                  free(lnk_path);
                  _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name);
               }
             if (symlink(lnk_path, task->dst.name) == -1)
               {
                  buf = memcpy(alloca(lnk_len + 1), lnk_path, lnk_len + 1);
                  free(lnk_path);
                  _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", buf, task->dst.name);
               }
          }
        else
          {
             buf = memcpy(alloca(lnk_len + 1), lnk_path, lnk_len + 1);
             free(lnk_path);
             _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", buf, task->dst.name);
          }
     }
   else
     {
        if ((now - _e_fm_op_last_sync) > SYNC_TIME)
          {
             int fd = open(task->dst.name, O_RDWR);

             if (fd >= 0)
               {
                  _syncfs(fd);
                  _e_fm_op_last_sync = now;
                  close(fd);
               }
          }
     }
   free(lnk_path);

   task->dst.done += task->src.st.st_size;

   _e_fm_op_update_progress(task, task->src.st.st_size, 0);

   task->finished = 1;

   return 0;
}

static int
_e_fm_op_copy_fifo(E_Fm_Op_Task *task)
{
   double now = ecore_time_get();

   if (mkfifo(task->dst.name, task->src.st.st_mode) == -1)
     {
        if (errno == EEXIST)
          {
             if (unlink(task->dst.name) == -1)
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name);
             if (mkfifo(task->dst.name, task->src.st.st_mode) == -1)
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make FIFO at '%s': %s.", task->dst.name);
          }
        else
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot make FIFO at '%s': %s.", task->dst.name);
     }
   else
     {
        if ((now - _e_fm_op_last_sync) > SYNC_TIME)
          {
             int fd = open(task->dst.name, O_RDWR);

             if (fd >= 0)
               {
                  _syncfs(fd);
                  _e_fm_op_last_sync = now;
                  close(fd);
               }
          }
     }

   _e_fm_op_copy_stat_info(task);

   task->dst.done += task->src.st.st_size;
   _e_fm_op_update_progress(task, task->src.st.st_size, 0);

   task->finished = 1;

   return 0;
}

static int
_e_fm_op_open_files(E_Fm_Op_Task *task)
{
   E_Fm_Op_Copy_Data *data = task->data;

   /* Ordinary file. */
   if (!data)
     {
        data = malloc(sizeof(E_Fm_Op_Copy_Data));
        task->data = data;
        data->to = NULL;
        data->from = NULL;
     }

   if (!data->from)
     {
        data->from = fopen(task->src.name, "rb");
        if (!data->from)
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot open file '%s' for reading: %s.", task->src.name);
     }

   if (!data->to)
     {
        data->to = fopen(task->dst.name, "wb");
        if (!data->to)
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot open file '%s' for writing: %s.", task->dst.name);
        _e_fm_op_copy_stat_info(task);
     }

   return 0;
}

static int
_e_fm_op_copy_chunk(E_Fm_Op_Task *task)
{
   E_Fm_Op_Copy_Data *data;
   unsigned long long dread, dwrite;
   char buf[COPYBUFSIZE];
   double now = ecore_time_get();

   data = task->data;

   if (_e_fm_op_abort)
     {
        if ((now - _e_fm_op_last_sync) > SYNC_TIME)
          {
             _syncfs(fileno(data->to));
             _e_fm_op_last_sync = now;
          }
        _e_fm_op_rollback(task);

        task->finished = 1;
        return 1;
     }

   dread = fread(buf, 1, sizeof(buf), data->from);
   if (dread == 0)
     {
        if (!feof(data->from))
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot read data from '%s': %s.", task->dst.name);

        if ((now - _e_fm_op_last_sync) > SYNC_TIME)
          {
             fflush(data->to);
             _syncfs(fileno(data->to));
             _e_fm_op_last_sync = now;
          }
        fclose(data->from);
        fclose(data->to);
        data->to = NULL;
        data->from = NULL;

        _e_fm_op_copy_stat_info(task);

        E_FREE(task->data);

        task->finished = 1;

        _e_fm_op_update_progress(task, 0, 0);

        return 1;
     }

   dwrite = fwrite(buf, 1, dread, data->to);
   _e_fm_op_bytes_sync += dread;
   if (((now - _e_fm_op_last_sync) > SYNC_TIME) ||
       (_e_fm_op_bytes_sync > SYNC_BYTES))
     {
        fflush(data->to);
        _syncfs(fileno(data->to));
        _e_fm_op_last_sync = now;
        _e_fm_op_bytes_sync = 0;
     }

   if (dwrite < dread)
     _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot write data to '%s': %s.", task->dst.name);

   task->dst.done += dread;
   _e_fm_op_update_progress(task, dwrite, 0);

   return 0;
}

/*
 * _e_fm_op_copy_atom(), _e_fm_op_remove_atom() and _e_fm_op_scan_atom() are functions that
 * perform very small operations.
 *
 * _e_fm_op_copy_atom(), for example, makes one of three things:
 * 1) opens files for writing and reading. This may take several calls -- until overwrite issues are not resolved.
 * 2) reads some bytes from one file and writes them to the other.
 * 3) closes both files if there is nothing more to read.
 *
 * _e_fm_op_remove_atom() removes smth.
 *
 * _e_fm_op_scan_atom() pushes new tasks for the _e_fm_op_work_idler(). One task for cp&rm and two tasks for mv.
 *
 * _e_fm_op_copy_atom() and _e_fm_op_remove_atom() are called from _e_fm_op_work_idler().
 * _e_fm_op_scan_atom() is called from _e_fm_op_scan_idler().
 *
 * These functions are called repeatedly until they put task->finished = 1. After that the task is removed from queue.
 *
 * Return value does not matter. It's there only to _E_FM_OP_ERROR_SEND macro to work correctly. (Well, it works fine, just don't want GCC to produce a warning.)
 */
static int
_e_fm_op_copy_atom(E_Fm_Op_Task *task)
{
   E_Fm_Op_Copy_Data *data;

   if (!task) return 1;

   data = task->data;

   if ((!data) || (!data->to) || (!data->from)) /* Did not touch the files yet. */
     {
        E_FM_OP_DEBUG("Copy: %s --> %s\n", task->src.name, task->dst.name);

        if (_e_fm_op_abort)
          {
             /* We're marked for abortion. Don't do anything.
              * Just return -- abort gets handled in _idler.
              */
             task->finished = 1;
             return 1;
          }
        if (_e_fm_op_handle_overwrite(task)) return 1;

        if (S_ISDIR(task->src.st.st_mode))
          _e_fm_op_copy_dir(task);
        else if (S_ISLNK(task->src.st.st_mode))
          _e_fm_op_copy_link(task);
        else if (S_ISFIFO(task->src.st.st_mode))
          _e_fm_op_copy_fifo(task);
        else if (S_ISREG(task->src.st.st_mode))
          _e_fm_op_open_files(task);
     }
   else
     {
        _e_fm_op_copy_chunk(task);
     }

   return 1;
}

static int
_e_fm_op_scan_atom(E_Fm_Op_Task *task)
{
   E_Fm_Op_Task *ctask, *rtask;

   if (!task) return 1;

   task->finished = 1;

   /* Now push a new task to the work idler. */

   if (task->type == E_FM_OP_COPY)
     {
        Eina_List *l;

        _e_fm_op_update_progress(NULL, 0, task->src.st.st_size);

        ctask = _e_fm_op_task_new();
        ctask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          ctask->dst.name = eina_stringshare_add(task->dst.name);
        if (task->parent)
          ctask->parent = task->parent;
        else
          {
             EINA_LIST_FOREACH(_e_fm_op_scan_queue, l, rtask)
               if (rtask->parent == task)
                 rtask->parent = ctask;
             EINA_LIST_FOREACH(_e_fm_op_work_queue, l, rtask)
               if (rtask->parent == task)
                 rtask->parent = ctask;
          }
        ctask->type = E_FM_OP_COPY;

        _e_fm_op_work_queue = eina_list_append(_e_fm_op_work_queue, ctask);
     }
   else if (task->type == E_FM_OP_COPY_STAT_INFO)
     {
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);

        ctask = _e_fm_op_task_new();
        ctask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          ctask->dst.name = eina_stringshare_add(task->dst.name);
        ctask->type = E_FM_OP_COPY_STAT_INFO;

        _e_fm_op_work_queue = eina_list_append(_e_fm_op_work_queue, ctask);
     }
   else if (task->type == E_FM_OP_REMOVE)
     {
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);

        rtask = _e_fm_op_task_new();
        rtask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          rtask->dst.name = eina_stringshare_add(task->dst.name);
        rtask->type = E_FM_OP_REMOVE;

        _e_fm_op_work_queue = eina_list_prepend(_e_fm_op_work_queue, rtask);
     }
   else if (task->type == E_FM_OP_SECURE_REMOVE)
     {
        /* Overwrite task. */
        _e_fm_op_update_progress(NULL, 0, task->src.st.st_size);
        ctask = _e_fm_op_task_new();

        ctask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          ctask->dst.name = eina_stringshare_add(task->dst.name);
        ctask->type = E_FM_OP_DESTROY;

        _e_fm_op_work_queue = eina_list_prepend(_e_fm_op_work_queue, ctask);

        /* Remove task. */
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);
        rtask = _e_fm_op_task_new();

        rtask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          rtask->dst.name = eina_stringshare_add(task->dst.name);
        rtask->type = E_FM_OP_REMOVE;

        _e_fm_op_work_queue = eina_list_append_relative_list(_e_fm_op_work_queue, rtask, _e_fm_op_separator);

        ctask->link = eina_list_next(_e_fm_op_separator);
     }
   else if (task->type == E_FM_OP_MOVE)
     {
        /* Copy task. */
        _e_fm_op_update_progress(NULL, 0, task->src.st.st_size);
        ctask = _e_fm_op_task_new();

        ctask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(ctask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          ctask->dst.name = eina_stringshare_add(task->dst.name);
        ctask->type = E_FM_OP_COPY;

        _e_fm_op_work_queue = eina_list_prepend(_e_fm_op_work_queue, ctask);

        /* Remove task. */
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);
        rtask = _e_fm_op_task_new();

        rtask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          rtask->dst.name = eina_stringshare_add(task->dst.name);
        rtask->type = E_FM_OP_REMOVE;

        /* We put remove task after the separator. Work idler won't go
         * there unless scan is done and this means that all tasks for
         * copy are already in queue. And they will be performed before
         * the delete tasks.
         *
         * If we don't use this separator trick, then there easily can be
         * a situation when remove task is performed before all files are
         * copied.
         */

        _e_fm_op_work_queue = eina_list_append_relative_list(_e_fm_op_work_queue, rtask, _e_fm_op_separator);

        ctask->link = eina_list_next(_e_fm_op_separator);
     }
   else if (task->type == E_FM_OP_SYMLINK)
     {
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);

        rtask = _e_fm_op_task_new();
        rtask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          rtask->dst.name = eina_stringshare_add(task->dst.name);
        rtask->type = E_FM_OP_SYMLINK;

        _e_fm_op_work_queue = eina_list_prepend(_e_fm_op_work_queue, rtask);
     }
   else if (task->type == E_FM_OP_RENAME)
     {
        _e_fm_op_update_progress(NULL, 0, REMOVECHUNKSIZE);

        rtask = _e_fm_op_task_new();
        rtask->src.name = eina_stringshare_add(task->src.name);
        memcpy(&(rtask->src.st), &(task->src.st), sizeof(struct stat));
        if (task->dst.name)
          rtask->dst.name = eina_stringshare_add(task->dst.name);
        rtask->type = E_FM_OP_RENAME;

        _e_fm_op_work_queue = eina_list_prepend(_e_fm_op_work_queue, rtask);
     }

   return 1;
}

static int
_e_fm_op_copy_stat_info_atom(E_Fm_Op_Task *task)
{
   E_FM_OP_DEBUG("Stat: %s --> %s\n", task->src.name, task->dst.name);

   _e_fm_op_copy_stat_info(task);
   task->finished = 1;
   task->dst.done += REMOVECHUNKSIZE;

   _e_fm_op_update_progress(task, REMOVECHUNKSIZE, 0);

   return 0;
}

static int
_e_fm_op_symlink_atom(E_Fm_Op_Task *task)
{
   if (_e_fm_op_abort) return 1;
   if (_e_fm_op_handle_overwrite(task)) return 1;

   E_FM_OP_DEBUG("Symlink: %s -> %s\n", task->src.name, task->dst.name);
   _e_fm_op_update_progress_report_simple(0, task->src.name, task->dst.name);

   if (symlink(task->src.name, task->dst.name) == -1)
     {
        if (errno == EEXIST)
          {
             if (unlink(task->dst.name) == -1)
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot unlink '%s': %s.", task->dst.name);
             if (symlink(task->src.name, task->dst.name) == -1)
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", task->src.name, task->dst.name);
          }
        else
          _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot create link from '%s' to '%s': %s.", task->src.name, task->dst.name);
     }

   task->dst.done += REMOVECHUNKSIZE;
   _e_fm_op_update_progress(task, REMOVECHUNKSIZE, 0);
   task->finished = 1;

   return 0;
}

static int
_e_fm_op_remove_atom(E_Fm_Op_Task *task)
{
   double now = ecore_time_get();

   if (_e_fm_op_abort) return 1;

   E_FM_OP_DEBUG("Remove: %s\n", task->src.name);

   if (S_ISDIR(task->src.st.st_mode))
     {
        if (rmdir(task->src.name) == -1)
          {
             if (errno == ENOTEMPTY)
               {
                  E_FM_OP_DEBUG("Attempt to remove non-empty directory.\n");
                  /* This should never happen due to way tasks are added to the work queue.
                     If this happens (for example new files were created after the scan was
                     complete), implicitly delete everything. */
                  ecore_file_recursive_rm(task->src.name);
                  if ((now - _e_fm_op_last_sync) > SYNC_TIME)
                    {
                       int fd = open(task->src.name, O_RDWR);

                       if (fd >= 0)
                         {
                            _syncfs(fd);
                            _e_fm_op_last_sync = now;
                            close(fd);
                         }
                    }
                  task->finished = 1; /* Make sure that task is removed. */
                  return 1;
               }
             else
               _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot remove directory '%s': %s.", task->src.name);
          }
     }
   else if (unlink(task->src.name) == -1)
     _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot remove file '%s': %s.", task->src.name);
   if ((now - _e_fm_op_last_sync) > SYNC_TIME)
     {
        int fd = open(task->src.name, O_RDWR);

        if (fd >= 0)
          {
             _syncfs(fd);
             _e_fm_op_last_sync = now;
             close(fd);
          }
     }

   task->dst.done += REMOVECHUNKSIZE;
   _e_fm_op_update_progress(task, REMOVECHUNKSIZE, 0);

   task->finished = 1;

   return 1;
}

static int
_e_fm_op_rename_atom(E_Fm_Op_Task *task)
{
   if (_e_fm_op_abort) return 1;
   if (_e_fm_op_handle_overwrite(task)) return 1;

   E_FM_OP_DEBUG("Move: %s -> %s\n", task->src.name, task->dst.name);
   _e_fm_op_update_progress_report_simple(0, task->src.name, task->dst.name);

   if (rename(task->src.name, task->dst.name) == -1)
     {
        _E_FM_OP_ERROR_SEND_WORK(task, E_FM_OP_ERROR, "Cannot move '%s' to '%s': %s.", task->src.name, task->dst.name);
     }

   task->dst.done += REMOVECHUNKSIZE;
   _e_fm_op_update_progress(task, REMOVECHUNKSIZE, 0);
   task->finished = 1;

   return 0;
}

static int
_e_fm_op_destroy_atom(E_Fm_Op_Task *task)
{
   if (_e_fm_op_abort) goto finish;
   static int fd = -1;
   static char *buf = NULL;
   off_t sz;

   if (fd == -1)
     {
        E_FM_OP_DEBUG("Secure remove: %s\n", task->src.name);
        struct stat st2;

        if (!S_ISREG(task->src.st.st_mode))
          goto finish;

        if (task->src.st.st_nlink > 1)
          goto finish;

        if ((fd = open(task->src.name, O_WRONLY | O_NOFOLLOW, 0)) == -1)
          goto finish;

        if (fstat(fd, &st2) == -1)
          goto finish;

        if (st2.st_dev != task->src.st.st_dev ||
            st2.st_ino != task->src.st.st_ino ||
            st2.st_mode != task->src.st.st_mode)
          goto finish;

        if ((buf = malloc(READBUFSIZE)) == NULL)
          goto finish;

        task->src.st.st_size = st2.st_size;
     }

   if (task->pos + READBUFSIZE > task->src.st.st_size) sz = task->src.st.st_size - task->pos;
   else sz = READBUFSIZE;

   _e_fm_op_random_buf(buf, sz);
   if (write(fd, buf, sz) != sz)
     goto finish;
   if (fsync(fd) == -1)
     goto finish;

   task->pos += sz;

   _e_fm_op_update_progress_report_simple(lround((double)((task->pos + (task->passes * task->src.st.st_size)) /
                                                          (double)(task->src.st.st_size * NB_PASS)) * 100.),
                                          "/dev/urandom", task->src.name);

   if (task->pos >= task->src.st.st_size)
     {
        task->passes++;

        if (task->passes == NB_PASS)
          goto finish;
        if (lseek(fd, 0, SEEK_SET) == -1)
          goto finish;

        task->pos = 0;
     }

   return 1;

finish:
   if (fd != -1) close(fd);
   fd = -1;
   E_FREE(buf);
   task->finished = 1;
   return 1;
}

static void
_e_fm_op_random_buf(char *buf, ssize_t len)
{
   int f = -1;

   if ((f = open("/dev/urandom", O_RDONLY)) == -1)
     {
        _e_fm_op_random_char(buf, len);
        return;
     }

   if (read(f, buf, len) != len)
     _e_fm_op_random_char(buf, len);

   close(f);
   return;
}

static void
_e_fm_op_random_char(char *buf, size_t len)
{
   size_t i;

   for (i = 0; i < len; i++)
     {
        buf[i] = (rand() % 256) + 'a';
     }
}

static int
_e_fm_op_make_copy_name(const char *abs, char *buf, size_t buf_size)
{
   size_t name_len;
   size_t file_len;
   char *name;
   char *ext;
   char *copy_str;

   if (!ecore_file_exists(abs))
      return 0;

   file_len = strlen(buf);

   /* TODO: need to make a policy regarding copy postfix:
    * currently attach " (copy)" continuously.
    *
    * TODO: i18n */
   copy_str = "(copy)";
   if (strlen(copy_str) + file_len + 1 >= buf_size) return 1;

   name = ecore_file_strip_ext(buf);
   name_len = strlen(name);
   E_FREE(name);

   if (file_len == name_len) ext = NULL;
   else ext = strdup(buf + name_len);

   if (ext)
     {
        sprintf(buf + name_len, " %s%s", copy_str, ext);
        E_FREE(ext);
     }
   else sprintf(buf + name_len, " %s", copy_str);

   return _e_fm_op_make_copy_name(abs, buf, buf_size);
}
#include "e.h"

E_API int E_EVENT_FM_OP_REGISTRY_ADD = 0;
E_API int E_EVENT_FM_OP_REGISTRY_DEL = 0;
E_API int E_EVENT_FM_OP_REGISTRY_CHANGED = 0;

static Eina_Hash *_e_fm2_op_registry = NULL;
static unsigned int _e_fm2_init_count = 0;

typedef struct _E_Fm2_Op_Registry_Entry_Listener E_Fm2_Op_Registry_Entry_Listener;
typedef struct _E_Fm2_Op_Registry_Entry_Internal E_Fm2_Op_Registry_Entry_Internal;

struct _E_Fm2_Op_Registry_Entry_Listener
{
   EINA_INLIST;
   void  (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry);
   void *data;
   void  (*free_data)(void *data);
};

struct _E_Fm2_Op_Registry_Entry_Internal
{
   E_Fm2_Op_Registry_Entry entry;
   Eina_Inlist            *listeners;
   int                     references;
   Ecore_Event            *changed_event;
};

static void
_e_fm2_op_registry_entry_e_fm_deleted(void *data, Evas *evas EINA_UNUSED, Evas_Object *e_fm EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry *entry = data;

   entry->e_fm = NULL;
   e_fm2_op_registry_entry_changed(entry);
}

static void
_e_fm2_op_registry_entry_e_fm_monitor_start(const E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry->e_fm) return;
   evas_object_event_callback_add
     (entry->e_fm, EVAS_CALLBACK_DEL,
     _e_fm2_op_registry_entry_e_fm_deleted, entry);
}

static void
_e_fm2_op_registry_entry_e_fm_monitor_stop(const E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry->e_fm) return;
   evas_object_event_callback_del_full
     (entry->e_fm, EVAS_CALLBACK_DEL,
     _e_fm2_op_registry_entry_e_fm_deleted, entry);
}

static inline E_Fm2_Op_Registry_Entry_Internal *
_e_fm2_op_registry_entry_internal_get(const E_Fm2_Op_Registry_Entry *entry)
{
   return (E_Fm2_Op_Registry_Entry_Internal *)entry;
}

static void
_e_fm2_op_registry_entry_internal_free(E_Fm2_Op_Registry_Entry_Internal *e)
{
   _e_fm2_op_registry_entry_e_fm_monitor_stop(&(e->entry));

   while (e->listeners)
     {
        E_Fm2_Op_Registry_Entry_Listener *listener = (void *)e->listeners;
        e->listeners = eina_inlist_remove(e->listeners, e->listeners);

        if (listener->free_data) listener->free_data(listener->data);
        free(listener);
     }

   eina_stringshare_del(e->entry.src);
   eina_stringshare_del(e->entry.dst);
   free(e);
}

static inline int
_e_fm2_op_registry_entry_internal_unref(E_Fm2_Op_Registry_Entry_Internal *e)
{
   if (e->references < 1)
     return 0;

   e->references--;
   if (e->references > 0)
     return e->references;

   _e_fm2_op_registry_entry_internal_free(e);
   return 0;
}

static inline int
_e_fm2_op_registry_entry_internal_ref(E_Fm2_Op_Registry_Entry_Internal *e)
{
   e->references++;
   return e->references;
}

static void
_e_fm2_op_registry_entry_listeners_call(const E_Fm2_Op_Registry_Entry_Internal *e)
{
   E_Fm2_Op_Registry_Entry_Listener *listener;
   Eina_Inlist *l;

   if (!e->listeners) return;

   EINA_INLIST_FOREACH_SAFE(e->listeners, l, listener)
     listener->cb(listener->data, &e->entry);
}

static void
_e_fm2_op_registry_entry_internal_unref_on_event(void *data, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry_Internal *e = data;
   _e_fm2_op_registry_entry_internal_unref(e);
}

static void
_e_fm2_op_registry_entry_internal_event(E_Fm2_Op_Registry_Entry_Internal *e, int event_type)
{
   _e_fm2_op_registry_entry_internal_ref(e);
   ecore_event_add(event_type, &(e->entry),
                   _e_fm2_op_registry_entry_internal_unref_on_event, e);
}

Eina_Bool
e_fm2_op_registry_entry_add(int id, Evas_Object *e_fm, E_Fm_Op_Type op, E_Fm2_Op_Registry_Abort_Func abrt)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   e = E_NEW(E_Fm2_Op_Registry_Entry_Internal, 1);
   if (!e) return 0;

   e->entry.id = id;
   e->entry.e_fm = e_fm;
   e->entry.start_time = ecore_loop_time_get();
   e->entry.op = op;
   e->entry.status = E_FM2_OP_STATUS_IN_PROGRESS;
   e->entry.func.abort = abrt;
   e->references = 1;

   if (!eina_hash_add(_e_fm2_op_registry, &id, e))
     {
        free(e);
        return 0;
     }

   _e_fm2_op_registry_entry_e_fm_monitor_start(&(e->entry));
   _e_fm2_op_registry_entry_internal_event(e, E_EVENT_FM_OP_REGISTRY_ADD);

   return 1;
}

Eina_Bool
e_fm2_op_registry_entry_del(int id)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   e = eina_hash_find(_e_fm2_op_registry, &id);
   if (!e) return 0;
   eina_hash_del_by_key(_e_fm2_op_registry, &id);

   _e_fm2_op_registry_entry_internal_event(e, E_EVENT_FM_OP_REGISTRY_DEL);
   _e_fm2_op_registry_entry_internal_unref(e);

   return 1;
}

static void
_e_fm2_op_registry_entry_internal_unref_on_changed_event(void *data, void *event EINA_UNUSED)
{
   E_Fm2_Op_Registry_Entry_Internal *e = data;
   e->changed_event = NULL;
   _e_fm2_op_registry_entry_internal_unref(e);
}

void
e_fm2_op_registry_entry_changed(const E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return;
   e = _e_fm2_op_registry_entry_internal_get(entry);

   _e_fm2_op_registry_entry_listeners_call(e);

   if (e->changed_event) return;
   _e_fm2_op_registry_entry_internal_ref(e);
   e->changed_event = ecore_event_add
       (E_EVENT_FM_OP_REGISTRY_CHANGED, &(e->entry),
       _e_fm2_op_registry_entry_internal_unref_on_changed_event, e);
}

/**
 * Set the new e_fm for this operation.
 *
 * Use this call instead of directly setting in order to have the
 * object to be monitored, when it is gone, the pointer will be made
 * NULL.
 *
 * @note: it will not call any listener or add any event, please use
 * e_fm2_op_registry_entry_changed().
 */
void
e_fm2_op_registry_entry_e_fm_set(E_Fm2_Op_Registry_Entry *entry, Evas_Object *e_fm)
{
   if (!entry) return;
   _e_fm2_op_registry_entry_e_fm_monitor_stop(entry);
   entry->e_fm = e_fm;
   _e_fm2_op_registry_entry_e_fm_monitor_start(entry);
}

/**
 * Set the new files for this operation.
 *
 * Use this call instead of directly setting in order to have
 * stringshare references right.
 *
 * @note: it will not call any listener or add any event, please use
 * e_fm2_op_registry_entry_changed().
 */
void
e_fm2_op_registry_entry_files_set(E_Fm2_Op_Registry_Entry *entry, const char *src, const char *dst)
{
   if (!entry) return;

   eina_stringshare_replace(&entry->src, src);
   eina_stringshare_replace(&entry->dst, dst);
}

/**
 * Adds a reference to given entry.
 *
 * @return: new number of references after operation or -1 on error.
 */
E_API int
e_fm2_op_registry_entry_ref(E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return -1;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   return _e_fm2_op_registry_entry_internal_ref(e);
}

/**
 * Releases a reference to given entry.
 *
 * @return: new number of references after operation or -1 on error,
 *    if 0 the entry was freed and pointer is then invalid.
 */
E_API int
e_fm2_op_registry_entry_unref(E_Fm2_Op_Registry_Entry *entry)
{
   E_Fm2_Op_Registry_Entry_Internal *e;

   if (!entry) return -1;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   return _e_fm2_op_registry_entry_internal_unref(e);
}

/**
 * Returns the X window associated to this operation.
 *
 * This will handle all bureaucracy to get X window based on e_fm evas
 * object.
 *
 * @return: 0 if no window, window identifier otherwise.
 */
E_API Ecore_X_Window
e_fm2_op_registry_entry_xwin_get(const E_Fm2_Op_Registry_Entry *entry)
{
   Evas *e;
   Ecore_Evas *ee;

   if (!entry) return 0;
   if (!entry->e_fm) return 0;

   e = evas_object_evas_get(entry->e_fm);
   if (!e) return 0;

   ee = evas_data_attach_get(e);
   if (!ee) return 0;

   return (Ecore_X_Window)(long)ecore_evas_window_get(ee);
}

/**
 * Discover entry based on its identifier.
 *
 * @note: does not increment reference.
 */
E_API E_Fm2_Op_Registry_Entry *
e_fm2_op_registry_entry_get(int id)
{
   return eina_hash_find(_e_fm2_op_registry, &id);
}

/**
 * Adds a function to be called when entry changes.
 *
 * When entry changes any attribute this function will be called.
 *
 * @param: entry entry to operate on.
 * @param: cb function to callback on changes.
 * @param: data extra data to give to @p cb
 * @param: free_data function to call when listener is removed, entry
 *    is deleted or any error occur in this function and listener
 *    cannot be added.
 *
 * @note: does not increment reference.
 * @note: on errors, @p free_data will be called.
 */
E_API void
e_fm2_op_registry_entry_listener_add(E_Fm2_Op_Registry_Entry *entry, void (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry), const void *data, void (*free_data)(void *data))
{
   E_Fm2_Op_Registry_Entry_Internal *e;
   E_Fm2_Op_Registry_Entry_Listener *listener;

   if ((!entry) || (!cb))
     {
        if (free_data) free_data((void *)data);
        return;
     }

   listener = malloc(sizeof(*listener));
   if (!listener)
     {
        if (free_data) free_data((void *)data);
        return;
     }
   listener->cb = cb;
   listener->data = (void *)data;
   listener->free_data = free_data;

   e = _e_fm2_op_registry_entry_internal_get(entry);
   e->listeners = eina_inlist_append(e->listeners, EINA_INLIST_GET(listener));
}

/**
 * Removes the function to be called when entry changes.
 *
 * @param: entry entry to operate on.
 * @param: cb function to callback on changes.
 * @param: data extra data to give to @p cb
 *
 * @note: does not decrement reference.
 * @see: e_fm2_op_registry_entry_listener_add()
 */
E_API void
e_fm2_op_registry_entry_listener_del(E_Fm2_Op_Registry_Entry *entry, void (*cb)(void *data, const E_Fm2_Op_Registry_Entry *entry), const void *data)
{
   E_Fm2_Op_Registry_Entry_Internal *e;
   E_Fm2_Op_Registry_Entry_Listener *l;

   if ((!entry) || (!cb)) return;
   e = _e_fm2_op_registry_entry_internal_get(entry);

   EINA_INLIST_FOREACH(e->listeners, l)
     if ((l->cb == cb) && (l->data == data))
       {
          e->listeners = eina_inlist_remove(e->listeners, EINA_INLIST_GET(l));
          if (l->free_data) l->free_data(l->data);
          free(l);
          return;
       }
}

/**
 * Returns an iterator over all the entries in the fm operations registry.
 *
 * @warning: this iterator is just valid until new entries are added
 *    or removed (usually happens from main loop). This is because
 *    when system is back to main loop it can report new events and
 *    operations can be added or removed from this registry. In other
 *    words, it is fine to call this function, immediately walk the
 *    iterator and do something, then free the iterator. You can use
 *    it to create a shadow list if you wish.
 *
 * @see e_fm2_op_registry_get_all()
 */
E_API Eina_Iterator *
e_fm2_op_registry_iterator_new(void)
{
   return eina_hash_iterator_data_new(_e_fm2_op_registry);
}

/**
 * Returns a shadow list with all entries in the registry.
 *
 * All entries will have references incremented, so you must free the
 * list with e_fm2_op_registry_get_all_free() to free the list and
 * release these references.
 *
 * @note: List is unsorted!
 * @note: if you need a simple, immediate walk, use
 *    e_fm2_op_registry_iterator_new()
 */
E_API Eina_List *
e_fm2_op_registry_get_all(void)
{
   Eina_List *list;
   Eina_Iterator *it;
   E_Fm2_Op_Registry_Entry_Internal *e;

   list = NULL;
   it = eina_hash_iterator_data_new(_e_fm2_op_registry);
   EINA_ITERATOR_FOREACH(it, e)
     {
        _e_fm2_op_registry_entry_internal_ref(e);
        list = eina_list_append(list, &(e->entry));
     }
   eina_iterator_free(it);

   return list;
}

E_API void
e_fm2_op_registry_get_all_free(Eina_List *list)
{
   E_Fm2_Op_Registry_Entry *entry;
   EINA_LIST_FREE(list, entry)
     e_fm2_op_registry_entry_unref(entry);
}

E_API Eina_Bool
e_fm2_op_registry_is_empty(void)
{
   return eina_hash_population(_e_fm2_op_registry) == 0;
}

E_API int
e_fm2_op_registry_count(void)
{
   return eina_hash_population(_e_fm2_op_registry);
}

EINTERN unsigned int
e_fm2_op_registry_init(void)
{
   _e_fm2_init_count++;
   if (_e_fm2_init_count > 1) return _e_fm2_init_count;

   _e_fm2_op_registry = eina_hash_int32_new(NULL);
   if (!_e_fm2_op_registry)
     {
        _e_fm2_init_count = 0;
        return 0;
     }

   if (E_EVENT_FM_OP_REGISTRY_ADD == 0)
     E_EVENT_FM_OP_REGISTRY_ADD = ecore_event_type_new();
   if (E_EVENT_FM_OP_REGISTRY_DEL == 0)
     E_EVENT_FM_OP_REGISTRY_DEL = ecore_event_type_new();
   if (E_EVENT_FM_OP_REGISTRY_CHANGED == 0)
     E_EVENT_FM_OP_REGISTRY_CHANGED = ecore_event_type_new();

   return 1;
}

EINTERN unsigned int
e_fm2_op_registry_shutdown(void)
{
   if (_e_fm2_init_count == 0) return 0;
   _e_fm2_init_count--;
   if (_e_fm2_init_count > 0) return _e_fm2_init_count;

   eina_hash_free(_e_fm2_op_registry);
   _e_fm2_op_registry = NULL;

   return 0;
}

E_API void
e_fm2_op_registry_entry_abort(E_Fm2_Op_Registry_Entry *entry)
{
   if (!entry) return;

   if (entry->func.abort)
     entry->func.abort(entry);
}

#include "e.h"
#ifdef HAVE_LIBEXIF
#include <libexif/exif-data.h>
#endif

/* FIXME:
 *
 * basic -
 * + show file
 * + show size
 * + show last access date
 * + show modified date
 * + show mimetype
 * + show permissions (others read, others write)
 * + show preview
 * + show owner
 * + show icon
 * * show symlink/fifo/socket/etc. status
 * + show broken link status
 * + change icon for mime type
 * * change icon for just this file
 * + change permissions
 *
 * advanced (extra) -
 * * show change date
 * * show comment
 * * show generic
 * * show mount status
 * * show setuid bit
 * + show link destination (if symlink or link)
 * * show group
 * * change link destination
 * * change app to open THIS file with (or dir)
 *
 */

/* PROTOTYPES - same all the time */

static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _basic_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
#if 0
static int          _advanced_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
#endif

static void         _cb_icon_sel(void *data, void *data2);
static void         _cb_type(void *data, Evas_Object *obj, void *event_info);
static void         _cb_preview_update(void *data, Evas_Object *obj, void *event_info);
static void         _cb_fsel_sel(void *data, Evas_Object *obj);
static void         _cb_fsel_ok(void *data, E_Dialog *dia);
static void         _cb_fsel_cancel(void *data, E_Dialog *dia);

/* Actual config data we will be playing with while the dialog is active */
struct _E_Config_Dialog_Data
{
   E_Fm2_Icon      *ic;
   E_Fm2_Icon_Info *fi;
   struct
   {
      Evas_Object *icon_wid;
      Evas_Object *preview;
      Evas_Object *preview_table;
      Evas_Object *fsel_wid;
      E_Dialog    *fsel;
   } gui;
   /*- BASIC -*/
   char            *file;
   char            *location;
   char            *size;
   char            *mod_date;
   char            *acc_date;
   char            *pms_date;
   char            *exif_date;
   char            *mime;
   char            *owner;
   char            *link;
   char            *plink;
   char            *blocks;
   int              owner_read;
   int              owner_write;
   int              owner_exec;
   int              others_read;
   int              others_write;
   int              others_exec;
   int              group_read;
   int              group_write;
   int              group_exec;
   int              picon_type;
   int              picon_mime;
   int              picon_changed;
   int              icon_type;
   int              icon_mime;
   char            *icon;
   /*- ADVANCED -*/
};

/* a nice easy setup function that does the dirty work */
E_API E_Config_Dialog *
e_fm_prop_file(E_Fm2_Icon *ic)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);

   /* methods */
   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply_data;
   v->basic.create_widgets = _basic_create_widgets;
#if 0
   v->advanced.apply_cfdata = _advanced_apply_data;
   v->advanced.create_widgets = _advanced_create_widgets;
#endif
   /* create config dialog for NULL object/data */
   cfd = e_config_dialog_new(NULL,
                             _("File Properties"),
                             "E", "_fm_prop",
                             "enlightenment/file_properties", 0, v, ic);
   return cfd;
}

#ifdef HAVE_LIBEXIF
static ExifEntry *
_exif_entry_find(ExifData *ed, ExifTag tag)
{
   ExifEntry *entry;

   if ((entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], tag))) return entry;
   if ((entry = exif_content_get_entry(ed->ifd[EXIF_IFD_1], tag))) return entry;
   if ((entry = exif_content_get_entry(ed->ifd[EXIF_IFD_EXIF], tag))) return entry;
   if ((entry = exif_content_get_entry(ed->ifd[EXIF_IFD_GPS], tag))) return entry;
   if ((entry = exif_content_get_entry(ed->ifd[EXIF_IFD_INTEROPERABILITY], tag))) return entry;
   return NULL;
}
#endif

/**--CREATE--**/
#ifdef HAVE_LIBEXIF
static inline int
_parse_exif_digit(char c)
{
   if ((c >= '0') && (c <= '9')) return (c - '0');
   return -1;
}

static inline int
_parse_digits(const char **s, char end)
{
   int vnow = 0, v;

   for (;;)
     {
        v = _parse_exif_digit(**s);
        if (v < 0)
          {
             if (**s != end) return -1;
             (*s)++;
             return vnow;
          }
        else vnow = (vnow * 10) + v;
        if (!(**s)) return -1;
        (*s)++;
     }
   return vnow;
}

static int
_parse_exif_time(const char *str, struct tm *tm)
{
   const char *s;
   int v;

   s = str;
   v = _parse_digits(&s, ':');
   if (v < 0) return -1;
   tm->tm_year = v;
   v = _parse_digits(&s, ':');
   if (v < 0) return -1;
   tm->tm_mon = v;
   v = _parse_digits(&s, ' ');
   if (v < 0) return -1;
   tm->tm_mday = v;
   v = _parse_digits(&s, ':');
   if (v < 0) return -1;
   tm->tm_hour = v;
   v = _parse_digits(&s, ':');
   if (v < 0) return -1;
   tm->tm_min = v;
   v = _parse_digits(&s, 0);
   if (v < 0) return -1;
   tm->tm_sec = v;
   return 0;
}
#endif

static void
_fill_data(E_Config_Dialog_Data *cfdata, E_Fm2_Icon *ic)
{
   char loc[PATH_MAX];
   char blks[256];
   struct passwd *pw;

   cfdata->ic = ic;
   cfdata->fi = e_fm2_icon_file_info_get(ic);
   if (cfdata->fi->file) cfdata->file = strdup(cfdata->fi->file);
   cfdata->size = e_util_size_string_get(cfdata->fi->statinfo.st_size);
   cfdata->mod_date = e_util_file_time_get(cfdata->fi->statinfo.st_mtime);
   cfdata->acc_date = e_util_file_time_get(cfdata->fi->statinfo.st_atime);
   cfdata->pms_date = e_util_file_time_get(cfdata->fi->statinfo.st_ctime);
#ifdef HAVE_LIBEXIF
   snprintf(loc, sizeof(loc), "%s/%s", e_fm2_real_path_get(cfdata->fi->fm), cfdata->fi->file);
   ExifData *ed = exif_data_new_from_file(loc);
   if (ed)
     {
        ExifEntry *entry = _exif_entry_find(ed, EXIF_TAG_DATE_TIME_ORIGINAL);
        if (!entry) entry = _exif_entry_find(ed, EXIF_TAG_DATE_TIME_DIGITIZED);
        if (!entry) entry = _exif_entry_find(ed, EXIF_TAG_DATE_TIME);
        if (entry)
          {
             char tbuf[128];
             struct tm tm;
             time_t tim;

             exif_entry_get_value(entry, tbuf, sizeof(tbuf));
             tbuf[127] = 0;
             memset(&tm, 0, sizeof(tm));
             tm.tm_isdst = -1;
             // "YYYY:MM:DD HH:MM:SS"
             if (_parse_exif_time(tbuf, &tm) == 0)
               {
                  tm.tm_year -= 1900;
                  tm.tm_mon -= 1;
                  tim = mktime(&tm);
                  if (tim != (time_t)-1)
                    cfdata->exif_date = e_util_file_time_get(tim);
               }
          }
        exif_data_unref(ed);
     }
#endif
   if (cfdata->fi->mime) cfdata->mime = strdup(cfdata->fi->mime);

   snprintf(blks, sizeof(blks), "%lu", (unsigned long)cfdata->fi->statinfo.st_blocks);
   cfdata->blocks = strdup(blks);

   snprintf(loc, sizeof(loc), "%s", e_fm2_real_path_get(cfdata->fi->fm));
   cfdata->location = strdup(loc);

   pw = getpwuid(cfdata->fi->statinfo.st_uid);
   if (pw) cfdata->owner = strdup(pw->pw_name);
   if (cfdata->fi->link) cfdata->link = strdup(cfdata->fi->link);
   if (cfdata->fi->link) cfdata->plink = strdup(cfdata->fi->link);
   if (cfdata->fi->statinfo.st_mode & S_IRUSR) cfdata->owner_read = 1;
   if (cfdata->fi->statinfo.st_mode & S_IWUSR) cfdata->owner_write = 1;
   if (cfdata->fi->statinfo.st_mode & S_IEXEC) cfdata->owner_exec = 1;
   if (cfdata->fi->statinfo.st_mode & S_IROTH) cfdata->others_read = 1;
   if (cfdata->fi->statinfo.st_mode & S_IWOTH) cfdata->others_write = 1;
   if (cfdata->fi->statinfo.st_mode & S_IXOTH) cfdata->others_exec = 1;
   if (cfdata->fi->statinfo.st_mode & S_IRGRP) cfdata->group_read = 1;
   if (cfdata->fi->statinfo.st_mode & S_IWGRP) cfdata->group_write = 1;
   if (cfdata->fi->statinfo.st_mode & S_IXGRP) cfdata->group_exec = 1;
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   /* Create cfdata - cfdata is a temporary block of config data that this
    * dialog will be dealing with while configuring. it will be applied to
    * the running systems/config in the apply methods
    */
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   _fill_data(cfdata, cfd->data);
   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   if (cfdata->gui.fsel)
     e_object_del(E_OBJECT(cfdata->gui.fsel));
   E_FREE(cfdata->file);
   E_FREE(cfdata->location);
   E_FREE(cfdata->size);
   E_FREE(cfdata->blocks);
   E_FREE(cfdata->mod_date);
   E_FREE(cfdata->acc_date);
   E_FREE(cfdata->pms_date);
   E_FREE(cfdata->exif_date);
   E_FREE(cfdata->mime);
   E_FREE(cfdata->owner);
   E_FREE(cfdata->link);
   E_FREE(cfdata->plink);
   E_FREE(cfdata->icon);
   free(cfdata);
}

/**--APPLY--**/
static int
_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   char buf[PATH_MAX];
   Eina_Bool fperm = EINA_FALSE;

   snprintf(buf, sizeof(buf), "%s/%s",
            cfdata->location, cfdata->fi->file);
   if (((cfdata->fi->statinfo.st_mode & S_IRUSR) && (cfdata->owner_read)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IRUSR)) && (!cfdata->owner_read)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IWUSR) && (cfdata->owner_write)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IWUSR)) && (!cfdata->owner_write)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IROTH) && (cfdata->others_read)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IROTH)) && (!cfdata->others_read)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IEXEC) && (cfdata->owner_exec)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IEXEC)) && (!cfdata->owner_exec)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IWOTH) && (cfdata->others_write)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IWOTH)) && (!cfdata->others_write)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IXOTH) && (cfdata->others_exec)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IXOTH)) && (!cfdata->others_exec)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IRGRP) && (cfdata->group_read)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IRGRP)) && (!cfdata->group_read)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IWGRP) && (cfdata->group_write)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IWGRP)) && (!cfdata->group_write)))
     fperm = EINA_TRUE;
   if (((cfdata->fi->statinfo.st_mode & S_IXGRP) && (cfdata->group_exec)) ||
       ((!(cfdata->fi->statinfo.st_mode & S_IXGRP)) && (!cfdata->group_exec)))
     fperm = EINA_TRUE;
   if (fperm == EINA_TRUE)
     {
        mode_t pmode;

        pmode = cfdata->fi->statinfo.st_mode;
        if (cfdata->owner_read) cfdata->fi->statinfo.st_mode |= S_IRUSR;
        else cfdata->fi->statinfo.st_mode &= ~S_IRUSR;
        if (cfdata->owner_write) cfdata->fi->statinfo.st_mode |= S_IWUSR;
        else cfdata->fi->statinfo.st_mode &= ~S_IWUSR;
        if (cfdata->owner_exec) cfdata->fi->statinfo.st_mode |= S_IEXEC;
        else cfdata->fi->statinfo.st_mode &= ~S_IEXEC;
        if (cfdata->others_read) cfdata->fi->statinfo.st_mode |= S_IROTH;
        else cfdata->fi->statinfo.st_mode &= ~S_IROTH;
        if (cfdata->others_write) cfdata->fi->statinfo.st_mode |= S_IWOTH;
        else cfdata->fi->statinfo.st_mode &= ~S_IWOTH;
        if (cfdata->others_exec) cfdata->fi->statinfo.st_mode |= S_IXOTH;
        else cfdata->fi->statinfo.st_mode &= ~S_IXOTH;
        if (cfdata->group_read) cfdata->fi->statinfo.st_mode |= S_IRGRP;
        else cfdata->fi->statinfo.st_mode &= ~S_IRGRP;
        if (cfdata->group_write) cfdata->fi->statinfo.st_mode |= S_IWGRP;
        else cfdata->fi->statinfo.st_mode &= ~S_IWGRP;
        if (cfdata->group_exec) cfdata->fi->statinfo.st_mode |= S_IXGRP;
        else cfdata->fi->statinfo.st_mode &= ~S_IXGRP;
        if (chmod(buf, cfdata->fi->statinfo.st_mode) == -1)
          {
             e_util_dialog_show(_("Error"),
                                _("Cannot change permissions: %s"),
                                strerror(errno));

             cfdata->fi->statinfo.st_mode = pmode;
          }
     }
   if ((cfdata->link) && ((cfdata->fi->real_link) || (cfdata->fi->broken_link)))
     {
        if ((cfdata->link[0]) && (strcmp(cfdata->plink, cfdata->link)))
          {
             ecore_file_unlink(buf);
             ecore_file_symlink(cfdata->link, buf);
             free(cfdata->plink);
             cfdata->plink = strdup(cfdata->link);
          }
     }
   if ((cfdata->picon_type != cfdata->icon_type) ||
       (cfdata->picon_mime != cfdata->icon_mime) ||
       (cfdata->picon_changed))
     {
        if ((cfdata->icon_mime) && (cfdata->mime)) /* modify mimetype */
          {
             Eina_List *l;
             E_Config_Mime_Icon *mi = NULL;
             Eina_Bool found = EINA_FALSE;

             if (!cfdata->picon_mime) /* remove previous custom icon info */
               e_fm2_custom_file_del(buf);
             EINA_LIST_FOREACH(e_config->mime_icons, l, mi)
               {
                  if (!mi) continue;
                  if (strcmp(mi->mime, cfdata->mime)) continue;
                  if (mi->icon)
                    {
                       eina_stringshare_del(mi->icon);
                       mi->icon = NULL;
                    }
                  found = EINA_TRUE;
                  break;
               }
             if ((found == EINA_FALSE) && (cfdata->icon_type != 0))
               {
                  mi = E_NEW(E_Config_Mime_Icon, 1);
                  mi->mime = eina_stringshare_add(cfdata->mime);
                  e_config->mime_icons = eina_list_append(e_config->mime_icons, mi);
               }
             /* FIXME: modify mime info */
             if (cfdata->icon_type == 0)
               {
                  if (found == EINA_TRUE)
                    {
                       e_config->mime_icons = eina_list_remove(e_config->mime_icons, mi);
                       if (mi->mime) eina_stringshare_del(mi->mime);
                       if (mi->icon) eina_stringshare_del(mi->icon);
                       free(mi);
                    }
               }
             else if (cfdata->icon_type == 1)
               {
                  mi->icon = eina_stringshare_add("THUMB");
               }
             else if (cfdata->icon_type == 2)
               {
                  mi->icon = eina_stringshare_add(cfdata->icon);
               }
             e_config_save_queue();
             e_fm_mime_icon_cache_flush();
          }
        else /* custom for this file */
          {
             E_Fm2_Custom_File *cf, cf0;

             cf = e_fm2_custom_file_get(buf);
             if (cf)
               {
                  cf->icon.type = cfdata->icon_type;
                  if (cf->icon.icon)
                    eina_stringshare_del(cf->icon.icon);
                  cf->icon.icon = NULL;
                  if (cfdata->icon_type == 2)
                    cf->icon.icon = eina_stringshare_add(cfdata->icon);
                  if (cfdata->icon_type == 0)
                    cf->icon.valid = 0;
                  else
                    cf->icon.valid = 1;
               }
             else
               {
                  memset(&cf0, 0, sizeof(E_Fm2_Custom_File));
                  cf = &cf0;
                  cf->icon.type = cfdata->icon_type;
                  if (cfdata->icon_type == 2)
                    cf->icon.icon = cfdata->icon;
                  if (cfdata->icon_type == 0)
                    cf->icon.valid = 0;
                  else
                    cf->icon.valid = 1;
               }
             e_fm2_custom_file_set(buf, cf);
             e_fm2_custom_file_flush();
          }
        cfdata->picon_type = cfdata->icon_type;
        cfdata->picon_mime = cfdata->icon_mime;

        e_fm2_all_icons_update();
     }

   return 1; /* Apply was OK */
}

#if 0
static int
_advanced_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata)
{
   return 1; /* Apply was OK */
}

#endif

/**--GUI--**/
static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for a basic dialog */
   Evas_Object *o, *ot, *ob, *of, *oi, *win;
   E_Radio_Group *rg;
   char buf[PATH_MAX];
   const char *itype = NULL;
   int mh, row = 0;

   win = cfd->dia->win;
   snprintf(buf, sizeof(buf), "%s/%s",
            cfdata->location, cfdata->fi->file);
   o = e_widget_table_add(e_win_evas_win_get(evas), 0);

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   of = e_widget_frametable_add(evas, _("General"), 1);

   ob = e_widget_label_add(evas, _("Name:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->file), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("Location:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->location), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("Size:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->size), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("Occupied blocks on disk:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->blocks), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   if (cfdata->exif_date)
     {
        ob = e_widget_label_add(evas, _("Taken on:"));
        e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
        ob = e_widget_entry_add(win, &(cfdata->exif_date), NULL, NULL, NULL);
        e_widget_size_min_get(ob, NULL, &mh);
        e_widget_size_min_set(ob, 140, mh);
        e_widget_entry_readonly_set(ob, 1);
        e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
        row++;
     }

   ob = e_widget_label_add(evas, _("Last Accessed:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->acc_date), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("Last Modified:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->mod_date), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("Last Modified Permissions:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->pms_date), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   ob = e_widget_label_add(evas, _("File Type:"));
   e_widget_frametable_object_append(of, ob, 0, row, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(win, &(cfdata->mime), NULL, NULL, NULL);
   e_widget_size_min_get(ob, NULL, &mh);
   e_widget_size_min_set(ob, 140, mh);
   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 1, row, 1, 1, 1, 0, 1, 0);
   row++;

   e_widget_table_object_append(ot, of, 0, 0, 1, 1, 1, 1, 1, 1);
   e_widget_table_object_append(o, ot, 1, 0, 0, 1, 1, 1, 1, 1);


   of = e_widget_frametable_add(evas, _("Permissions"), 0);
   ob = e_widget_entry_add(win, &(cfdata->owner), NULL, NULL, NULL);

   e_widget_entry_readonly_set(ob, 1);
   e_widget_frametable_object_append(of, ob, 0, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("read"), &(cfdata->owner_read));
   e_widget_frametable_object_append(of, ob, 0, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("write"), &(cfdata->owner_write));
   e_widget_frametable_object_append(of, ob, 0, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("execute"), &(cfdata->owner_exec));
   e_widget_frametable_object_append(of, ob, 0, 3, 1, 1, 1, 1, 1, 1);

   ob = e_widget_label_add(evas, _("Group:"));
   e_widget_frametable_object_append(of, ob, 1, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("read"), &(cfdata->group_read));
   e_widget_frametable_object_append(of, ob, 1, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("write"), &(cfdata->group_write));
   e_widget_frametable_object_append(of, ob, 1, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("execute"), &(cfdata->group_exec));
   e_widget_frametable_object_append(of, ob, 1, 3, 1, 1, 1, 1, 1, 1);

   ob = e_widget_label_add(evas, _("Others:"));
   e_widget_frametable_object_append(of, ob, 2, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("read"), &(cfdata->others_read));
   e_widget_frametable_object_append(of, ob, 2, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("write"), &(cfdata->others_write));
   e_widget_frametable_object_append(of, ob, 2, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("execute"), &(cfdata->others_exec));
   e_widget_frametable_object_append(of, ob, 2, 3, 1, 1, 1, 1, 1, 1);

   e_widget_table_object_append(ot, of, 0, 1, 1, 1, 1, 1, 1, 1);
   e_widget_table_object_append(o, ot, 0, 0, 1, 1, 1, 1, 1, 1);


   of = e_widget_frametable_add(evas, _("Preview"), 0);

   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);
   ob = e_widget_preview_add(evas, 128, 128);
   cfdata->gui.preview = ob;
   cfdata->gui.preview_table = ot;
   evas_object_smart_callback_add(ob, "preview_update",
                                  _cb_preview_update, cfdata);
   e_widget_table_object_append(ot, ob, 0, 0, 1, 1, 0, 0, 1, 1);
   e_widget_preview_thumb_set(ob, buf,
                              "e/desktop/background", 128, 128);
   e_widget_frametable_object_append(of, ot, 0, 0, 1, 1, 1, 1, 1, 1);

   e_widget_table_object_append(o, of, 1, 0, 1, 1, 1, 1, 1, 1);

   ot = e_widget_frametable_add(evas, _("Icon"), 0);

   ob = e_widget_button_add(evas, "", NULL, _cb_icon_sel, cfdata, cfd);
   cfdata->gui.icon_wid = ob;
   oi = e_fm2_icon_get(evas,
                       cfdata->ic,
                       NULL, NULL, 0, &itype);
   e_widget_button_icon_set(ob, oi);
   e_widget_frametable_object_append(ot, ob, 0, 0, 1, 3, 1, 1, 1, 1);

   if (itype)
     {
        if ((!strcmp(itype, "THEME_ICON")) ||
            (!strcmp(itype, "DESKTOP")) ||
            (!strcmp(itype, "FILE_TYPE")))
          {
             e_widget_disabled_set(ob, 1);
             cfdata->icon_type = 0;
          }
        else if (!strcmp(itype, "THUMB"))
          {
             cfdata->icon_type = 1;
             e_widget_disabled_set(ob, 1);
          }
        else if (!strcmp(itype, "CUSTOM"))
          cfdata->icon_type = 2;
     }
   else
     cfdata->icon_type = 0;
   cfdata->picon_type = cfdata->icon_type;

   rg = e_widget_radio_group_new(&cfdata->icon_type);
   ob = e_widget_radio_add(evas, _("Default"), 0, rg);
   evas_object_smart_callback_add(ob, "changed", _cb_type, cfdata);
   e_widget_frametable_object_append(ot, ob, 1, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_radio_add(evas, _("Thumbnail"), 1, rg);
   evas_object_smart_callback_add(ob, "changed", _cb_type, cfdata);
   e_widget_frametable_object_append(ot, ob, 1, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_radio_add(evas, _("Custom"), 2, rg);
   evas_object_smart_callback_add(ob, "changed", _cb_type, cfdata);
   e_widget_frametable_object_append(ot, ob, 1, 2, 1, 1, 1, 1, 1, 1);

   cfdata->icon_mime = 1;
   if ((cfdata->fi->icon) || ((itype) && (!strcmp(itype, "DESKTOP"))))
     cfdata->icon_mime = 0;
   cfdata->picon_mime = cfdata->icon_mime;
   if (cfdata->mime)
     {
        ob = e_widget_check_add(evas, _("Use this icon for all files of this type"), &(cfdata->icon_mime));
        e_widget_frametable_object_append(ot, ob, 0, 3, 2, 1, 1, 1, 1, 1);
     }

   e_widget_table_object_append(o, ot, 0, 1, 1, 1, 1, 1, 1, 1);

   if ((cfdata->link) && ((cfdata->fi->real_link) || (cfdata->fi->broken_link)))
     {
        ot = e_widget_frametable_add(evas, _("Link Information"), 0);

        ob = e_widget_entry_add(win, &(cfdata->link), NULL, NULL, NULL);
        e_widget_frametable_object_append(ot, ob, 0, 0, 1, 1, 1, 0, 1, 0);

        if (cfdata->fi->broken_link)
          {
             ob = e_widget_label_add(evas, _("This link is broken."));
             e_widget_frametable_object_append(ot, ob, 0, 1, 1, 1, 1, 0, 1, 0);
          }

        e_widget_table_object_append(o, ot, 1, 1, 2, 1, 1, 1, 1, 1);
     }
   return o;
}

#if 0
static Evas_Object *
_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for an advanced dialog */
   Evas_Object *o;

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   return o;
}

#endif

static void
_dia_del(void *data)
{
   E_Dialog *dia = data;
   E_Config_Dialog_Data *cfdata;

   cfdata = dia->data;
   if (!cfdata) return;
   cfdata->gui.fsel = NULL;
   cfdata->gui.fsel_wid = NULL;
}

static void
_cb_icon_sel(void *data, void *data2)
{
   E_Config_Dialog_Data *cfdata;
   E_Config_Dialog *cfd;
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord w, h;

   cfdata = data;
   if (!cfdata) return;
   if (cfdata->gui.fsel) return;

   cfd = data2;
   if (!cfd) return;

   dia = e_dialog_new(NULL, "E", "_fm2_file_properties_icon_select_dialog");
   if (!dia) return;
//   if (cfdata->type == EDJ)
//     e_dialog_title_set(dia, _("Select an Edj File"));
//   else if (cfdata->type == IMG)
   e_dialog_title_set(dia, _("Select an Image"));

   dia->data = cfdata;
   o = e_widget_fsel_add(evas_object_evas_get(dia->win), "~/", "/", NULL, NULL,
                         _cb_fsel_sel, cfdata, NULL, cfdata, 1);

   cfdata->gui.fsel_wid = o;
   evas_object_show(o);
   e_widget_size_min_get(o, &w, &h);
   e_dialog_content_set(dia, o, w, h);

   e_dialog_button_add(dia, _("OK"), NULL, _cb_fsel_ok, cfdata);
   e_dialog_button_add(dia, _("Cancel"), NULL, _cb_fsel_cancel, cfdata);
   e_util_win_auto_resize_fill(dia->win);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   e_object_del_attach_func_set(E_OBJECT(dia), _dia_del);

   cfdata->gui.fsel = dia;
}

static void
_cb_type(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   if (cfdata->icon_type == 2)
     e_widget_disabled_set(cfdata->gui.icon_wid, 0);
   else
     e_widget_disabled_set(cfdata->gui.icon_wid, 1);
}

static void
_cb_preview_update(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   e_widget_table_object_repack(cfdata->gui.preview_table,
                                cfdata->gui.preview,
                                0, 0, 1, 1, 0, 0, 1, 1);
}

static void
_cb_fsel_sel(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   if (!cfdata) return;
}

static void
_cb_fsel_ok(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;
   const char *file, *ext;
   Evas_Object *icon = NULL;

   cfdata = data;
   if (!cfdata) return;

   file = e_widget_fsel_selection_path_get(cfdata->gui.fsel_wid);
   E_FREE(cfdata->icon);
   if (file) cfdata->icon = strdup(file);
   _cb_fsel_cancel(data, dia);
   ext = strrchr(cfdata->icon, '.');
   if (ext)
     {
        if (!strcasecmp(ext, ".edj"))
          {
             icon = edje_object_add(evas_object_evas_get(cfdata->gui.icon_wid));
             edje_object_file_set(icon, cfdata->file, "icon");
          }
        else
          {
             icon = e_widget_image_add_from_file(evas_object_evas_get(cfdata->gui.icon_wid), cfdata->icon, 48, 48);
          }
     }
   else
     {
        icon = e_widget_image_add_from_file(evas_object_evas_get(cfdata->gui.icon_wid), cfdata->icon, 48, 48);
     }
   if (icon) e_widget_button_icon_set(cfdata->gui.icon_wid, icon);
}

static void
_cb_fsel_cancel(void *data, E_Dialog *dia)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = data;
   e_object_del(E_OBJECT(dia));
   cfdata->gui.fsel = NULL;
}

#include "e_fm_shared_types.h"
#include "e_fm_shared_codec.h"

static Eet_Data_Descriptor *_e_volume_edd = NULL;
static Eet_Data_Descriptor *_e_storage_edd = NULL;

Eet_Data_Descriptor *
_e_volume_edd_new(void)
{
   Eet_Data_Descriptor *edd;
   Eet_Data_Descriptor_Class eddc;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof(eddc), "e_volume", sizeof(E_Volume)))
     return NULL;

//   eddc.func.str_alloc = (char *(*)(const char *)) strdup;
//   eddc.func.str_free = (void (*)(const char *)) free;

   edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(MEMBER, TYPE) EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Volume, #MEMBER, MEMBER, EET_T_##TYPE)
   DAT(type, INT);
   DAT(udi, STRING);
   DAT(uuid, STRING);
   DAT(label, STRING);
   DAT(fstype, STRING);
   DAT(size, ULONG_LONG);
   DAT(partition, CHAR);
   DAT(partition_number, INT);
   DAT(partition_label, STRING);
   DAT(mounted, CHAR);
   DAT(mount_point, STRING);
   DAT(parent, STRING);
   DAT(first_time, CHAR);
   DAT(encrypted, CHAR);
   DAT(unlocked, CHAR);
   DAT(efm_mode, UINT);
#undef DAT
   return edd;
}

Eet_Data_Descriptor *
_e_storage_edd_new(void)
{
   Eet_Data_Descriptor *edd;
   Eet_Data_Descriptor_Class eddc;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof (eddc), "e_storage", sizeof (E_Storage)))
     return NULL;

//   eddc.func.str_alloc = (char *(*)(const char *)) strdup;
//   eddc.func.str_free = (void (*)(const char *)) free;

   edd = eet_data_descriptor_stream_new(&eddc);
#define DAT(MEMBER, TYPE) EET_DATA_DESCRIPTOR_ADD_BASIC(edd, E_Storage, #MEMBER, MEMBER, EET_T_##TYPE)
   DAT(type, INT);
   DAT(udi, STRING);
   DAT(bus, STRING);
   DAT(drive_type, STRING);
   DAT(model, STRING);
   DAT(vendor, STRING);
   DAT(serial, STRING);
   DAT(removable, CHAR);
   DAT(media_available, CHAR);
   DAT(media_size, ULONG_LONG);
   DAT(requires_eject, CHAR);
   DAT(hotpluggable, CHAR);
   DAT(media_check_enabled, CHAR);
   DAT(icon.drive, STRING);
   DAT(icon.volume, STRING);
#undef DAT
   return edd;
}

void *
_e_fm_shared_codec_storage_encode(E_Storage *s, int *size)
{
   return eet_data_descriptor_encode(_e_storage_edd, s, size);
}

E_Storage *
_e_fm_shared_codec_storage_decode(void *s, int size)
{
   return eet_data_descriptor_decode(_e_storage_edd, s, size);
}

void *
_e_fm_shared_codec_volume_encode(E_Volume *v, int *size)
{
   return eet_data_descriptor_encode(_e_volume_edd, v, size);
}

E_Volume *
_e_fm_shared_codec_volume_decode(void *v, int size)
{
   return eet_data_descriptor_decode(_e_volume_edd, v, size);
}

void
_e_storage_volume_edd_init(void)
{
   _e_volume_edd = _e_volume_edd_new();
   _e_storage_edd = _e_storage_edd_new();
}

void
_e_storage_volume_edd_shutdown(void)
{
   if (_e_volume_edd)
     {
        eet_data_descriptor_free(_e_volume_edd);
        _e_volume_edd = NULL;
     }
   if (_e_storage_edd)
     {
        eet_data_descriptor_free(_e_storage_edd);
        _e_storage_edd = NULL;
     }
}

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "e_fm_shared_types.h"
#include "e_fm_shared_device.h"

void
_e_fm_shared_device_storage_free(E_Storage *s)
{
   E_Volume *v;
   EINA_LIST_FREE(s->volumes, v)
     {
        v->storage = NULL;
        _e_fm_shared_device_volume_free(v);
     }
   eina_stringshare_del(s->udi);
   eina_stringshare_del(s->bus);
   eina_stringshare_del(s->drive_type);
   eina_stringshare_del(s->model);
   eina_stringshare_del(s->vendor);
   eina_stringshare_del(s->serial);
   eina_stringshare_del(s->icon.drive);
   eina_stringshare_del(s->icon.volume);
#ifdef HAVE_UDISKS_MOUNT
   eina_stringshare_del(s->dbus_path);
#endif
#ifdef HAVE_EEZE_MOUNT
   if (s->disk) eeze_disk_free(s->disk);
#endif
   free(s);
}

void
_e_fm_shared_device_volume_free(E_Volume *v)
{
   if (v->storage)
     {
        v->storage->volumes = eina_list_remove(v->storage->volumes, v);
        v->storage = NULL;
     }
   if (v->udi) eina_stringshare_del(v->udi);
   if (v->uuid) eina_stringshare_del(v->uuid);
   if (v->label) eina_stringshare_del(v->label);
   if (v->icon) eina_stringshare_del(v->icon);
   if (v->fstype) eina_stringshare_del(v->fstype);
   if (v->partition_label) eina_stringshare_del(v->partition_label);
   if (v->mount_point) eina_stringshare_del(v->mount_point);
   if (v->parent) eina_stringshare_del(v->parent);
#ifdef HAVE_UDISKS_MOUNT
   eina_stringshare_del(v->dbus_path);
#endif
   free(v);
}

#include "e.h"

/* local subsystem functions */
static Eina_Bool _e_focus_raise_timer(void *data);

/* local subsystem globals */

/* externally accessible functions */
E_API void
e_focus_event_mouse_in(E_Client *ec)
{

   if ((e_config->focus_policy == E_FOCUS_MOUSE) ||
       (e_config->focus_policy == E_FOCUS_SLOPPY))
     {
        evas_object_focus_set(ec->frame, 1);
     }
   E_FREE_FUNC(ec->raise_timer, ecore_timer_del);
   if (e_config->use_auto_raise)
     {
        if (eina_dbl_exact(e_config->auto_raise_delay, 0.0))
          {
             if (!ec->lock_user_stacking)
               evas_object_raise(ec->frame);
          }
        else
          ec->raise_timer = ecore_timer_loop_add(e_config->auto_raise_delay, _e_focus_raise_timer, ec);
     }
}

E_API void
e_focus_event_mouse_out(E_Client *ec)
{
   if (e_config->focus_policy == E_FOCUS_MOUSE)
     {
        if (!ec->lock_focus_in)
          {
             if (ec->focused) {
               e_client_revert_focus(ec);
             }
          }
     }
   E_FREE_FUNC(ec->raise_timer, ecore_timer_del);
}

E_API void
e_focus_event_mouse_down(E_Client *ec)
{
   if (e_client_focus_policy_click(ec) ||
       e_config->always_click_to_focus)
     evas_object_focus_set(ec->frame, 1);
   if (e_config->always_click_to_raise)
     {
        if (!ec->lock_user_stacking)
          evas_object_raise(ec->frame);
     }
}

E_API void
e_focus_event_mouse_up(E_Client *ec EINA_UNUSED)
{
}

E_API void
e_focus_event_focus_in(E_Client *ec EINA_UNUSED)
{
}

E_API void
e_focus_event_focus_out(E_Client *ec EINA_UNUSED)
{
}

/* local subsystem functions */
static Eina_Bool
_e_focus_raise_timer(void *data)
{
   E_Client *ec = data;

   if (!ec->lock_user_stacking) evas_object_raise(ec->frame);
   ec->raise_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

#include "e.h"

#define E_TOK_STYLE ":style="

static Eina_Bool          _font_hash_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED);
static Eina_Hash         *_e_font_available_hash_add(Eina_Hash *font_hash, const char *full_name);
static E_Font_Properties *_e_font_fontconfig_name_parse(Eina_Hash **font_hash, E_Font_Properties *efp, const char *font);
static char _fn_buf[1024];

EINTERN int
e_font_init(void)
{
   /* all init stuff is in e_config */
   return 1;
}

EINTERN int
e_font_shutdown(void)
{
   /* e_config will do this */
   return 1;
}

E_API void
e_font_apply(void)
{
   char buf[1024];
   const Eina_List *l;
   E_Client *ec;
   E_Font_Default *efd;
   E_Font_Fallback *eff;
   int blen, len;

   /* setup edje fallback list */
   blen = sizeof(buf) - 1;
   buf[0] = 0;
   buf[blen] = 0;
   l = e_config->font_fallbacks;
   if (l)
     {
        eff = eina_list_data_get(l);
        len = strlen(eff->name);
        if (len < blen)
          {
             strcpy(buf, eff->name);
             blen -= len;
          }
        EINA_LIST_FOREACH(eina_list_next(l), l, eff)
          {
             len = 1;
             if (len < blen)
               {
                  strcat(buf, ",");
                  blen -= len;
               }
             len = strlen(eff->name);
             if (len < blen)
               {
                  strcat(buf, eff->name);
                  blen -= len;
               }
          }
        edje_fontset_append_set(buf);
     }
   else
     edje_fontset_append_set(NULL);

   /* setup edje text classes */
   EINA_LIST_FOREACH(e_config->font_defaults, l, efd)
     {
        edje_text_class_set(efd->text_class, efd->font, efd->size);
     }

   /* Update clients */
   if (!e_comp) return;
   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     e_client_frame_recalc(ec);
}

E_API Eina_List *
e_font_available_list(void)
{
   Eina_List *evas_fonts;
   Eina_List *e_fonts;
   Eina_List *l;
   const char *evas_font;

   evas_fonts = evas_font_available_list(e_comp->evas);

   e_fonts = NULL;
   EINA_LIST_FOREACH(evas_fonts, l, evas_font)
     {
        E_Font_Available *efa;

        efa = E_NEW(E_Font_Available, 1);
        efa->name = eina_stringshare_add(evas_font);
        e_fonts = eina_list_append(e_fonts, efa);
     }

   evas_font_available_list_free(e_comp->evas, evas_fonts);

   return e_fonts;
}

E_API void
e_font_available_list_free(Eina_List *available)
{
   E_Font_Available *efa;

   EINA_LIST_FREE(available, efa)
     {
        if (efa->name) eina_stringshare_del(efa->name);
        E_FREE(efa);
     }
}

E_API void
e_font_properties_free(E_Font_Properties *efp)
{
   const char *str;

   EINA_LIST_FREE(efp->styles, str)
     {
        if (str) eina_stringshare_del(str);
     }
   if (efp->name) eina_stringshare_del(efp->name);
   free(efp);
}

static Eina_Bool
_font_hash_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   E_Font_Properties *efp;

   efp = data;
   e_font_properties_free(efp);
   return 1;
}

E_API void
e_font_available_hash_free(Eina_Hash *hash)
{
   eina_hash_foreach(hash, _font_hash_free_cb, NULL);
   eina_hash_free(hash);
}

E_API E_Font_Properties *
e_font_fontconfig_name_parse(const char *font)
{
   if (!font) return NULL;
   return _e_font_fontconfig_name_parse(NULL, NULL, font);
}

static E_Font_Properties *
_e_font_fontconfig_name_parse(Eina_Hash **font_hash, E_Font_Properties *efp, const char *font)
{
   char *s1;

   s1 = strchr(font, ':');
   if (s1)
     {
        char *s2, *name, *style, *temp;
        int len;

        len = s1 - font;
        name = calloc(sizeof(char), len + 1);
        strncpy(name, font, len);

        /* Get subname (should be english)  */
        s2 = strchr(name, ',');
        if (s2)
          {
             len = s2 - name;
             temp = name;
             name = realloc(name, sizeof(char) * len + 1);
             if (!name)
               {
                  free(temp);
                  return NULL;
               }
             memset(name, 0, sizeof(char) * len + 1);
             strncpy(name, font, len);
          }

        if (strncmp(s1, E_TOK_STYLE, strlen(E_TOK_STYLE)) == 0)
          {
             style = s1 + strlen(E_TOK_STYLE);

             if (font_hash) efp = eina_hash_find(*font_hash, name);
             if (!efp)
               {
                  efp = calloc(1, sizeof(E_Font_Properties));
                  efp->name = eina_stringshare_add(name);
                  if (font_hash)
                    {
                       if (!*font_hash) *font_hash = eina_hash_string_superfast_new(NULL);
                       eina_hash_add(*font_hash, name, efp);
                    }
               }
             s2 = strchr(style, ',');
             if (s2)
               {
                  char *style_old;

                  len = s2 - style;
                  style_old = style;
                  style = calloc(sizeof(char), len + 1);
                  strncpy(style, style_old, len);
                  efp->styles = eina_list_append(efp->styles, eina_stringshare_add(style));
                  free(style);
               }
             else
               efp->styles = eina_list_append(efp->styles, eina_stringshare_add(style));
          }
        free(name);
     }
   else
     {
        if (font_hash) efp = eina_hash_find(*font_hash, font);
        if (!efp)
          {
             efp = calloc(1, sizeof(E_Font_Properties));
             efp->name = eina_stringshare_add(font);
             if (font_hash)
               {
                  if (!*font_hash) *font_hash = eina_hash_string_superfast_new(NULL);
                  eina_hash_add(*font_hash, font, efp);
               }
          }
     }
   return efp;
}

static Eina_Hash *
_e_font_available_hash_add(Eina_Hash *font_hash, const char *full_name)
{
   _e_font_fontconfig_name_parse(&font_hash, NULL, full_name);
   return font_hash;
}

E_API Eina_Hash *
e_font_available_list_parse(Eina_List *list)
{
   Eina_Hash *font_hash;
   Eina_List *next;
   void *key;

   font_hash = NULL;

   /* Populate Default Font Families */
   font_hash = _e_font_available_hash_add(font_hash, "Sans:style=Regular");
   font_hash = _e_font_available_hash_add(font_hash, "Sans:style=Bold");
   font_hash = _e_font_available_hash_add(font_hash, "Sans:style=Oblique");
   font_hash = _e_font_available_hash_add(font_hash, "Sans:style=Bold Oblique");

   font_hash = _e_font_available_hash_add(font_hash, "Serif:style=Regular");
   font_hash = _e_font_available_hash_add(font_hash, "Serif:style=Bold");
   font_hash = _e_font_available_hash_add(font_hash, "Serif:style=Oblique");
   font_hash = _e_font_available_hash_add(font_hash, "Serif:style=Bold Oblique");

   font_hash = _e_font_available_hash_add(font_hash, "Monospace:style=Regular");
   font_hash = _e_font_available_hash_add(font_hash, "Monospace:style=Bold");
   font_hash = _e_font_available_hash_add(font_hash, "Monospace:style=Oblique");
   font_hash = _e_font_available_hash_add(font_hash, "Monospace:style=Bold Oblique");

   EINA_LIST_FOREACH(list, next, key)
     font_hash = _e_font_available_hash_add(font_hash, key);

   return font_hash;
}

E_API const char *
e_font_fontconfig_name_get(const char *name, const char *style)
{
   char buf[256];

   if (!name) return NULL;
   if (!style || style[0] == 0) return eina_stringshare_add(name);
   snprintf(buf, 256, "%s"E_TOK_STYLE "%s", name, style);
   return eina_stringshare_add(buf);
}

E_API void
e_font_fallback_clear(void)
{
   E_Font_Fallback *eff;

   EINA_LIST_FREE(e_config->font_fallbacks, eff)
     {
        if (eff->name) eina_stringshare_del(eff->name);
        E_FREE(eff);
     }
}

E_API void
e_font_fallback_append(const char *font)
{
   E_Font_Fallback *eff;

   e_font_fallback_remove(font);

   eff = E_NEW(E_Font_Fallback, 1);
   eff->name = eina_stringshare_add(font);
   e_config->font_fallbacks = eina_list_append(e_config->font_fallbacks, eff);
}

E_API void
e_font_fallback_prepend(const char *font)
{
   E_Font_Fallback *eff;

   e_font_fallback_remove(font);

   eff = E_NEW(E_Font_Fallback, 1);
   eff->name = eina_stringshare_add(font);
   e_config->font_fallbacks = eina_list_prepend(e_config->font_fallbacks, eff);
}

E_API void
e_font_fallback_remove(const char *font)
{
   Eina_List *next;
   E_Font_Fallback *eff;

   EINA_LIST_FOREACH(e_config->font_fallbacks, next, eff)
     {
        if (!e_util_strcmp(eff->name, font))
          {
             e_config->font_fallbacks =
               eina_list_remove_list(e_config->font_fallbacks, next);
             if (eff->name) eina_stringshare_del(eff->name);
             E_FREE(eff);
             break;
          }
     }
}

E_API Eina_List *
e_font_fallback_list(void)
{
   return e_config->font_fallbacks;
}

E_API void
e_font_default_set(const char *text_class, const char *font, Evas_Font_Size size)
{
   E_Font_Default *efd;
   Eina_List *next;

   /* search for the text class */
   EINA_LIST_FOREACH(e_config->font_defaults, next, efd)
     {
        if (!strcmp(efd->text_class, text_class))
          {
             if (efd->font) eina_stringshare_del(efd->font);
             efd->font = eina_stringshare_add(font);
             efd->size = size;
             /* move to the front of the list */
             e_config->font_defaults =
               eina_list_remove_list(e_config->font_defaults, next);
             e_config->font_defaults =
               eina_list_prepend(e_config->font_defaults, efd);
             return;
          }
     }

   /* the text class doesn't exist */
   efd = E_NEW(E_Font_Default, 1);
   efd->text_class = eina_stringshare_add(text_class);
   efd->font = eina_stringshare_add(font);
   efd->size = size;

   e_config->font_defaults = eina_list_prepend(e_config->font_defaults, efd);
}

/*
 * returns a pointer to the data, return null if nothing if found.
 */
E_API E_Font_Default *
e_font_default_get(const char *text_class)
{
   E_Font_Default *efd = NULL, *defd = NULL;
   Eina_List *next;

   /* search for the text class */
   EINA_LIST_FOREACH(e_config->font_defaults, next, efd)
     {
        if (!strcmp(efd->text_class, text_class))
          {
             /* move to the front of the list */
             e_config->font_defaults =
               eina_list_remove_list(e_config->font_defaults, next);
             e_config->font_defaults =
               eina_list_prepend(e_config->font_defaults, efd);
             return efd;
          }
        if (!strcmp(efd->text_class, "default")) defd = efd;
     }
   if (!defd) defd = efd;
   return defd;
}

E_API void
e_font_default_remove(const char *text_class)
{
   E_Font_Default *efd;
   Eina_List *next;

   /* search for the text class */
   EINA_LIST_FOREACH(e_config->font_defaults, next, efd)
     {
        if (!e_util_strcmp(efd->text_class, text_class))
          {
             e_config->font_defaults =
               eina_list_remove_list(e_config->font_defaults, next);
             if (efd->text_class) eina_stringshare_del(efd->text_class);
             if (efd->font) eina_stringshare_del(efd->font);
             E_FREE(efd);
             edje_text_class_del(text_class);
             return;
          }
     }
}

E_API Eina_List *
e_font_default_list(void)
{
   return e_config->font_defaults;
}

/* return the default font name with fallbacks, font size is returned
 * in size_ret. This function is needed when all hell breaks loose and
 * we need a font name and size.
 */
E_API const char *
e_font_default_string_get(const char *text_class, Evas_Font_Size *size_ret)
{
   E_Font_Default *efd;
   Eina_List *next;
   E_Font_Fallback *eff;
   int blen, len;

   _fn_buf[0] = 0;
   efd = e_font_default_get(text_class);
   if (!efd)
     {
        if (size_ret) *size_ret = 0;
        return "";
     }
   blen = sizeof(_fn_buf) - 1;

   len = strlen(efd->font);
   if (len < blen)
     {
        strcpy(_fn_buf, efd->font);
        blen -= len;
     }

   EINA_LIST_FOREACH(e_config->font_fallbacks, next, eff)
     {
        len = 1;
        if (len < blen)
          {
             strcat(_fn_buf, ",");
             blen -= len;
          }
        len = strlen(eff->name);
        if (len < blen)
          {
             strcat(_fn_buf, eff->name);
             blen -= len;
          }
     }

   if (size_ret) *size_ret = efd->size;
   return _fn_buf;
}

#include "e.h"

/*
 * TODO: gadcon client ordering on drop
 */

#define E_LAYOUT_ITEM_DRAG_RESIST_LEVEL 10
static void                     _e_gadcon_event_populate(E_Gadcon *gc);
static Eina_Bool                _e_gadcon_client_populate(E_Gadcon *gc, const E_Gadcon_Client_Class *cc, E_Config_Gadcon_Client *cf_gcc);
static void                     _e_gadcon_client_unpopulate(E_Gadcon_Client *gcc);
static void                     _e_gadcon_free(E_Gadcon *gc);
static void                     _e_gadcon_client_free(E_Gadcon_Client *gcc);

static void                     _e_gadcon_moveresize_handle(E_Gadcon_Client *gcc);
static Eina_Bool                _e_gadcon_cb_client_scroll_timer(void *data);
static void                     _e_gadcon_client_scroll_state_update(E_Gadcon_Client *gcc);
static Eina_Bool                _e_gadcon_cb_client_scroll_animator(void *data);
static void                     _e_gadcon_cb_client_frame_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_client_frame_moveresize(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_client_save(E_Gadcon_Client *gcc);
static void                     _e_gadcon_client_drag_begin(E_Gadcon_Client *gcc, int x, int y);
static void                     _e_gadcon_client_inject(E_Gadcon *gc, E_Gadcon_Client *gcc, int x, int y);

static void                     _e_gadcon_cb_min_size_request(void *data, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_size_request(void *data, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_hide(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_show(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_moveresize(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_parent_resize_cb(E_Gadcon *gc, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void                     _e_gadcon_cb_client_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_client_mouse_in(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_client_mouse_out(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_client_move(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_cb_client_resize(void *data, Evas *evas, Evas_Object *obj, void *event_info);

static void                     _e_gadcon_client_move_start(E_Gadcon_Client *gcc);
static void                     _e_gadcon_client_move_stop(E_Gadcon_Client *gcc);
static void                     _e_gadcon_client_move_go(E_Gadcon_Client *gcc);

static void                     _e_gadcon_cb_signal_move_start(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_move_stop(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_move_go(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_left_start(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_left_stop(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_left_go(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_right_start(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_right_stop(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_signal_resize_right_go(void *data, Evas_Object *obj, const char *emission, const char *source);
static void                     _e_gadcon_cb_dnd_enter(void *data, const char *type, void *event);
static void                     _e_gadcon_cb_dnd_move(void *data, const char *type, void *event);
static void                     _e_gadcon_cb_dnd_leave(void *data, const char *type, void *event);
static void                     _e_gadcon_cb_dnd_drop(void *data, const char *type, void *event);

static int                      _e_gadcon_client_class_feature_check(const E_Gadcon_Client_Class *cc, const char *name, void *feature);
static void                     _e_gadcon_client_cb_menu_post(void *data, E_Menu *m);
static void                     _e_gadcon_client_cb_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_client_cb_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_client_cb_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_client_cb_menu_style_plain(void *data, E_Menu *m, E_Menu_Item *mi);
static void                     _e_gadcon_client_cb_menu_style_inset(void *data, E_Menu *m, E_Menu_Item *mi);
static void                     _e_gadcon_client_cb_menu_autoscroll(void *data, E_Menu *m, E_Menu_Item *mi);
/*static void _e_gadcon_client_cb_menu_resizable(void *data, E_Menu *m, E_Menu_Item *mi);*/
static void                     _e_gadcon_client_cb_menu_edit(void *data, E_Menu *m, E_Menu_Item *mi);
static void                     _e_gadcon_client_cb_menu_remove(void *data, E_Menu *m, E_Menu_Item *mi);
static void                     _e_gadcon_client_cb_menu_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void                     _e_gadcon_client_delfn(void *d, void *o);
static void                     _e_gadcon_client_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                     _e_gadcon_client_event_free(void *d, void *e);
static Evas_Object             *e_gadcon_layout_add(Evas *evas);
static void                     e_gadcon_layout_orientation_set(Evas_Object *obj, int horizontal);
static int                      e_gadcon_layout_orientation_get(Evas_Object *obj);
static void                     e_gadcon_layout_freeze(Evas_Object *obj);
static void                     e_gadcon_layout_thaw(Evas_Object *obj);
static void                     e_gadcon_layout_min_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h);
static void                     e_gadcon_layout_asked_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h);
static int                      e_gadcon_layout_pack(Evas_Object *obj, Evas_Object *child);
static void                     e_gadcon_layout_pack_size_set(Evas_Object *obj, int size);
static void                     e_gadcon_layout_pack_request_set(Evas_Object *obj, int pos, int size);
static void                     e_gadcon_layout_pack_options_set(Evas_Object *obj, E_Gadcon_Client *gcc);
static void                     e_gadcon_layout_pack_min_size_set(Evas_Object *obj, int w, int h);
static void                     e_gadcon_layout_pack_aspect_set(Evas_Object *obj, int w, int h);
static void                     e_gadcon_layout_pack_aspect_pad_set(Evas_Object *obj, int w, int h);
static void                     e_gadcon_layout_unpack(Evas_Object *obj);
static void                     _e_gadcon_provider_populate_request(E_Gadcon *gc, const E_Gadcon_Client_Class *cc);
static void                     _e_gadcon_provider_populate_unrequest(const E_Gadcon_Client_Class *cc);
static void                     _e_gadcon_provider_populate_job(void *data);
static void                     _e_gadcon_event_populate_free(void *data EINA_UNUSED, void *event);
static void                     _e_gadcon_custom_populate_job(void *data);

static int                      _e_gadcon_location_change(E_Gadcon_Client *gcc, E_Gadcon_Location *src, E_Gadcon_Location *dst);

typedef struct _E_Smart_Data            E_Smart_Data;
typedef struct _E_Layout_Item_Container E_Layout_Item_Container;

static void                     _e_gadcon_client_current_position_sync(E_Gadcon_Client *gcc);
static void                     _e_gadcon_layout_smart_sync_clients(E_Gadcon *gc);
static void                     _e_gadcon_layout_smart_gadcon_position_shrinked_mode(E_Smart_Data *sd);
static void                     _e_gadcon_layout_smart_gadcons_asked_position_set(E_Smart_Data *sd);
static Eina_List               *_e_gadcon_layout_smart_gadcons_wrap(E_Smart_Data *sd);
static void                     _e_gadcon_layout_smart_gadcons_position(E_Smart_Data *sd, Eina_List **list);
static void                     _e_gadcon_layout_smart_gadcons_position_static(E_Smart_Data *sd, Eina_List **list);
static E_Layout_Item_Container *_e_gadcon_layout_smart_containers_position_adjust(E_Smart_Data *sd, E_Layout_Item_Container *lc, E_Layout_Item_Container *lc2);
static void                     _e_gadcon_layout_smart_position_items_inside_container(E_Smart_Data *sd, E_Layout_Item_Container *lc);
static void                     _e_gadcon_layout_smart_containers_merge(E_Smart_Data *sd, E_Layout_Item_Container *lc, E_Layout_Item_Container *lc2);
static void                     _e_gadcon_layout_smart_restore_gadcons_position_before_move(E_Smart_Data *sd, E_Layout_Item_Container **lc_moving, E_Layout_Item_Container *lc_back, Eina_List **con_list);

typedef enum _E_Gadcon_Layout_Item_State
{
   E_LAYOUT_ITEM_STATE_NONE,
   E_LAYOUT_ITEM_STATE_POS_INC,
   E_LAYOUT_ITEM_STATE_POS_DEC,
   E_LAYOUT_ITEM_STATE_SIZE_MIN_END_INC,
   E_LAYOUT_ITEM_STATE_SIZE_MIN_END_DEC,
   E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC,
   E_LAYOUT_ITEM_STATE_SIZE_MAX_END_DEC,
} E_Gadcon_Layout_Item_State;

typedef enum _E_Gadcon_Layout_Item_Flags
{
   E_GADCON_LAYOUT_ITEM_LOCK_NONE = 0x00000000,
   E_GADCON_LAYOUT_ITEM_LOCK_POSITION = 0x00000001,
   E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE = 0x00000002
} E_Gadcon_Layout_Item_Flags;

typedef enum _E_Layout_Item_Container_State
{
   E_LAYOUT_ITEM_CONTAINER_STATE_NONE,
   E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC,
   E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC,
   E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_INC,
   E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_DEC,
   E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_INC,
   E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_DEC,
   E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED
} E_Layout_Item_Container_State;

struct _E_Layout_Item_Container
{
   int                           pos, size, prev_pos, prev_size;

   struct
   {
      int min_seq, max_seq;
   } state_info;

   E_Smart_Data                 *sd;
   Eina_List                    *items;

   E_Layout_Item_Container_State state;
};

#define LC_FREE(__lc)             \
  if (__lc->items)                \
    eina_list_free(__lc->items);  \
  E_FREE(__lc)

#define E_LAYOUT_ITEM_CONTAINER_STATE_SET(__con_state, __bi_state) \
  if (__bi_state == E_LAYOUT_ITEM_STATE_NONE)                      \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_NONE;              \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_POS_INC)              \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC;           \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_POS_DEC)              \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC;           \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_SIZE_MIN_END_INC)     \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_INC;  \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_SIZE_MIN_END_DEC)     \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_DEC;  \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC)     \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_INC;  \
  else if (__bi_state == E_LAYOUT_ITEM_STATE_SIZE_MAX_END_DEC)     \
    __con_state = E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_DEC

#define LC_OVERLAP(__lc, __lc2)                                                        \
  ((((__lc2)->pos >= (__lc)->pos) && ((__lc2)->pos < ((__lc)->pos + (__lc)->size))) || \
   (((__lc)->pos >= (__lc2)->pos) && ((__lc)->pos < ((__lc2)->pos + (__lc2)->size))))

#define E_LAYOUT_ITEM_CONTAINER_SIZE_CHANGE_BY(__lc, __bi, __increase) \
  {                                                                    \
     if (__increase)                                                   \
       __lc->size += __bi->w;                                          \
     else                                                              \
       __lc->size -= __bi->w;                                          \
  }

/********************/
E_API int E_EVENT_GADCON_CLIENT_CLASS_ADD = -1;
E_API int E_EVENT_GADCON_CLIENT_CLASS_DEL = -1;
E_API int E_EVENT_GADCON_CLIENT_ADD = -1;
E_API int E_EVENT_GADCON_CLIENT_DEL = -1;
E_API int E_EVENT_GADCON_POPULATE = -1;

static Eina_Hash *providers = NULL;
static Eina_List *providers_list = NULL;
static Eina_List *gadcons = NULL;
static Eina_List *dummies = NULL;
static Ecore_Job *populate_job = NULL;
static Eina_List *custom_populate_requests = NULL;
static Ecore_Job *custom_populate_job = NULL;
static Eina_List *gadcon_locations = NULL;
static Ecore_Event_Handler *_module_init_end_handler = NULL;
static Eina_Bool _modules_loaded = EINA_FALSE;

static inline void
_eina_list_free(Eina_List *l)
{
   eina_list_free(l);
}

static int
_e_gadcon_provider_list_sort_cb(E_Gadcon_Client_Class *a, E_Gadcon_Client_Class *b)
{
   return strcasecmp(a->name, b->name);
}

static Eina_Bool
_module_init_end_cb(void *d EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   _modules_loaded = EINA_TRUE;
   return ECORE_CALLBACK_RENEW;
}

/* externally accessible functions */
EINTERN int
e_gadcon_init(void)
{
   E_EVENT_GADCON_CLIENT_CLASS_ADD = ecore_event_type_new();
   E_EVENT_GADCON_CLIENT_CLASS_DEL = ecore_event_type_new();
   E_EVENT_GADCON_CLIENT_ADD = ecore_event_type_new();
   E_EVENT_GADCON_CLIENT_DEL = ecore_event_type_new();
   E_EVENT_GADCON_POPULATE = ecore_event_type_new();
   _module_init_end_handler = ecore_event_handler_add(E_EVENT_MODULE_INIT_END, _module_init_end_cb, NULL);
   return 1;
}

EINTERN int
e_gadcon_shutdown(void)
{
   if (populate_job)
     {
        ecore_job_del(populate_job);
        populate_job = NULL;
     }
   custom_populate_requests = eina_list_free(custom_populate_requests);
   if (custom_populate_job)
     {
        ecore_job_del(custom_populate_job);
        custom_populate_job = NULL;
     }
   _modules_loaded = EINA_FALSE;
   if (_module_init_end_handler)
     ecore_event_handler_del(_module_init_end_handler);
   _module_init_end_handler = NULL;
   E_LIST_FOREACH(gadcons, e_gadcon_unpopulate);

   return 1;
}

/**
 * Registers a new gadget class in e
 *
 * @param cc version of gadcon, name and callbacks to use
 * for handling creation and destroying a gadget
 */
E_API void
e_gadcon_provider_register(const E_Gadcon_Client_Class *cc)
{
   E_Gadcon *gc;
   Eina_List *l, *ll, *lll;
   E_Config_Gadcon_Client *cf_gcc;

   EINA_SAFETY_ON_NULL_RETURN(cc->name);
   if (!providers) providers = eina_hash_string_superfast_new(NULL);
   eina_hash_direct_add(providers, cc->name, cc);
   EINA_LIST_FOREACH(gadcons, l, gc)
     {
        e_gadcon_layout_freeze(gc->o_container);
        if (gc->awaiting_classes)
          {
             ll = eina_hash_find(gc->awaiting_classes, cc->name);
             EINA_LIST_FOREACH(ll, lll, cf_gcc)
               _e_gadcon_client_populate(gc, cc, cf_gcc);
          }
        e_gadcon_layout_thaw(gc->o_container);
     }
   providers_list = eina_list_sorted_insert(providers_list, (Eina_Compare_Cb)_e_gadcon_provider_list_sort_cb, cc);
   {
      E_Event_Gadcon_Client_Class_Add *ev;
      ev = E_NEW(E_Event_Gadcon_Client_Class_Add, 1);
      ev->cc = cc;
      ecore_event_add(E_EVENT_GADCON_CLIENT_CLASS_ADD, ev, NULL, NULL);
   }
}

/**
 * unregisters a gadget class in e
 *
 * @param cc the gadget class that was used to register the gadget
 */
E_API void
e_gadcon_provider_unregister(const E_Gadcon_Client_Class *cc)
{
   Eina_List *l, *ll, *dlist = NULL;
   E_Gadcon *gc;
   E_Gadcon_Client *gcc;

   _e_gadcon_provider_populate_unrequest(cc);

   EINA_LIST_FOREACH(gadcons, l, gc)
     {
        EINA_LIST_FOREACH(gc->clients, ll, gcc)
          {
             if (gcc->client_class == cc)
               dlist = eina_list_append(dlist, gcc);
          }
        gc->populated_classes = eina_list_remove(gc->populated_classes, cc);
     }
   EINA_LIST_FREE(dlist, gcc)
     {
        e_gadcon_client_queue(gcc->gadcon, gcc->cf);
        gcc->hidden = 0;
        e_gadcon_client_hide(gcc);
        e_object_del(E_OBJECT(gcc));
     }

   eina_hash_del(providers, cc->name, cc);
   providers_list = eina_list_remove(providers_list, cc);
   {
      E_Event_Gadcon_Client_Class_Add *ev;
      ev = E_NEW(E_Event_Gadcon_Client_Class_Add, 1);
      ev->cc = cc;
      ecore_event_add(E_EVENT_GADCON_CLIENT_CLASS_DEL, ev, NULL, NULL);
   }
}

E_API void
e_gadcon_client_queue(E_Gadcon *gc, E_Config_Gadcon_Client *cf_gcc)
{
   Eina_List *l;
   if (!gc->awaiting_classes)
     gc->awaiting_classes = eina_hash_string_superfast_new((Eina_Free_Cb)_eina_list_free);
   l = eina_hash_find(gc->awaiting_classes, cf_gcc->name);
   if (eina_list_data_find(l, cf_gcc)) return;
   l = eina_list_append(l, cf_gcc);
   eina_hash_set(gc->awaiting_classes, cf_gcc->name, l);
}

E_API Eina_List *
e_gadcon_provider_list(void)
{
   return providers_list;
}

E_API void
e_gadcon_custom_new(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);

   gadcons = eina_list_append(gadcons, gc);

   if (!custom_populate_job)
     {
        custom_populate_job =
          ecore_job_add(_e_gadcon_custom_populate_job, NULL);
     }
   if (!eina_list_data_find(custom_populate_requests, gc))
     custom_populate_requests = eina_list_append(custom_populate_requests, gc);
   gc->custom = EINA_TRUE;
}

E_API void
e_gadcon_custom_del(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);

   if (eina_list_data_find(custom_populate_requests, gc))
     custom_populate_requests = eina_list_remove(custom_populate_requests, gc);

   gadcons = eina_list_remove(gadcons, gc);
}

E_API void
e_gadcon_custom_populate_request(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);

   if (!gc->custom) return;
   if (!custom_populate_job)
     {
        custom_populate_job =
          ecore_job_add(_e_gadcon_custom_populate_job, NULL);
     }
   if (!eina_list_data_find(custom_populate_requests, gc))
     custom_populate_requests = eina_list_append(custom_populate_requests, gc);
}

E_API E_Gadcon *
e_gadcon_dummy_new(int id)
{
   E_Gadcon *gc;

   gc = E_OBJECT_ALLOC(E_Gadcon, E_GADCON_DUMMY_TYPE, _e_gadcon_free);
   if (!gc) return NULL;

   gc->id = id;
   gc->layout_policy = E_GADCON_LAYOUT_POLICY_PANEL;
   gc->location = NULL;
   gc->dummy = 1;

   gc->orient = E_GADCON_ORIENT_HORIZ;
   dummies = eina_list_append(dummies, gc);
   return gc;
}

E_API void
e_gadcon_config_del(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->cfg_delete = 1;
}

E_API void
e_gadcon_drop_handler_add(E_Gadcon *gc, E_Gadcon_DND_Cb enter, E_Gadcon_DND_Cb leave, E_Gadcon_DND_Cb move, E_Gadcon_DND_Cb drop, int x, int y, int w, int h)
{
   const char *drop_types[] = { "enlightenment/gadcon_client" };

   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->drop_handler) return;
   gc->drop_handler =
     e_drop_handler_add(E_OBJECT(gc), NULL, gc,
                        _e_gadcon_cb_dnd_enter, _e_gadcon_cb_dnd_move,
                        _e_gadcon_cb_dnd_leave, _e_gadcon_cb_dnd_drop,
                        drop_types, 1, x, y, w, h);
   gc->dnd_enter_cb = enter;
   gc->dnd_leave_cb = leave;
   gc->dnd_move_cb = move;
   gc->dnd_drop_cb = drop;
}

E_API E_Gadcon *
e_gadcon_swallowed_new(const char *name, int id, Evas_Object *obj, const char *swallow_name)
{
   E_Gadcon *gc;
   E_Config_Gadcon *cf_gc;
   Eina_List *l;
   Evas_Coord x, y, w, h;

   gc = E_OBJECT_ALLOC(E_Gadcon, E_GADCON_TYPE, _e_gadcon_free);
   if (!gc) return NULL;

   gc->name = eina_stringshare_add(name);
   gc->id = id;
   gc->layout_policy = E_GADCON_LAYOUT_POLICY_PANEL;
   gc->location = NULL;

   gc->edje.o_parent = obj;
   gc->edje.swallow_name = eina_stringshare_add(swallow_name);

   gc->orient = E_GADCON_ORIENT_HORIZ;
   gc->evas = evas_object_evas_get(obj);
   gc->o_container = e_gadcon_layout_add(gc->evas);
   evas_object_geometry_get(gc->o_container, &x, &y, &w, &h);
   e_gadcon_drop_handler_add(gc, NULL, NULL, NULL, NULL, x, y, w, h);
   evas_object_event_callback_add(gc->o_container, EVAS_CALLBACK_MOVE,
                                  _e_gadcon_cb_moveresize, gc);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE,
                                  (Evas_Object_Event_Cb)_e_gadcon_parent_resize_cb, gc);
   evas_object_event_callback_add(gc->o_container, EVAS_CALLBACK_RESIZE,
                                  _e_gadcon_cb_moveresize, gc);
   evas_object_event_callback_add(gc->o_container, EVAS_CALLBACK_SHOW,
                                  _e_gadcon_cb_show, gc);
   evas_object_event_callback_add(gc->o_container, EVAS_CALLBACK_HIDE,
                                  _e_gadcon_cb_hide, gc);
   evas_object_smart_callback_add(gc->o_container, "size_request",
                                  _e_gadcon_cb_size_request, gc);
   evas_object_smart_callback_add(gc->o_container, "min_size_request",
                                  _e_gadcon_cb_min_size_request, gc);
   evas_object_show(gc->o_container);
   edje_object_part_swallow(gc->edje.o_parent, gc->edje.swallow_name,
                            gc->o_container);
   gadcons = eina_list_append(gadcons, gc);

   EINA_LIST_FOREACH(e_config->gadcons, l, cf_gc)
     {
        if ((!strcmp(cf_gc->name, gc->name)) && (cf_gc->id == gc->id))
          {
             gc->cf = cf_gc;
             break;
          }
     }
   if (!gc->cf)
     {
        gc->cf = E_NEW(E_Config_Gadcon, 1);
        gc->cf->name = eina_stringshare_add(gc->name);
        gc->cf->id = gc->id;
        e_config->gadcons = eina_list_append(e_config->gadcons, gc->cf);
        e_config_save_queue();
     }
   return gc;
}

E_API void
e_gadcon_swallowed_min_size_set(E_Gadcon *gc, Evas_Coord w, Evas_Coord h)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->edje.o_parent)
     {
        evas_object_size_hint_min_set(gc->o_container, w, h);
        edje_object_part_swallow(gc->edje.o_parent, gc->edje.swallow_name,
                                 gc->o_container);
     }
}

E_API void
e_gadcon_min_size_request_callback_set(E_Gadcon *gc, void (*func)(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->min_size_request.func = func;
   gc->min_size_request.data = data;
}

E_API void
e_gadcon_size_request_callback_set(E_Gadcon *gc, void (*func)(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->resize_request.func = func;
   gc->resize_request.data = data;
}

E_API void
e_gadcon_frame_request_callback_set(E_Gadcon *gc, Evas_Object *(*func)(void *data, E_Gadcon_Client * gcc, const char *style), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->frame_request.func = func;
   gc->frame_request.data = data;
}

E_API void
e_gadcon_populate_callback_set(E_Gadcon *gc, void (*func)(void *data, E_Gadcon *gc, const E_Gadcon_Client_Class *cc), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->populate_class.func = func;
   gc->populate_class.data = data;
}

E_API void
e_gadcon_layout_policy_set(E_Gadcon *gc, E_Gadcon_Layout_Policy layout_policy)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->layout_policy == layout_policy) return;
   gc->layout_policy = layout_policy;
   /* FIXME: delete container obj, re-pack all clients */
}

E_API Eina_Bool
e_gadcon_populate(E_Gadcon *gc)
{
   Eina_List *l;
   E_Config_Gadcon_Client *cf_gcc;
   E_Gadcon_Client_Class *cc;
   int x = 0;

   E_OBJECT_CHECK_RETURN(gc, EINA_FALSE);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, EINA_FALSE);
   e_gadcon_layout_freeze(gc->o_container);
   EINA_LIST_FOREACH(gc->cf->clients, l, cf_gcc)
     {
        if (!cf_gcc->name) continue;
        cc = eina_hash_find(providers, cf_gcc->name);
        if (cc)
          {
             if (!eina_list_data_find(gc->populated_classes, cc))
               {
                  _e_gadcon_provider_populate_request(gc, cc);
                  cc = NULL;
               }
          }
        if (cc)
          _e_gadcon_client_populate(gc, cc, cf_gcc);
        else
          e_gadcon_client_queue(gc, cf_gcc);
     }
   if (!gc->toolbar)
     {
        EINA_LIST_FREE(gc->populate_requests, cc)
          {
             char buf[256];

             e_main_ts(cc->name);
             if (gc->populate_class.func)
               gc->populate_class.func(gc->populate_class.data, gc, cc);
             else
               e_gadcon_populate_class(gc, cc);
             if (!eina_list_data_find(gc->populated_classes, cc))
               {
                  gc->populated_classes = eina_list_append(gc->populated_classes, cc);
                  if (gc->cf)
                    {
                       Eina_List *ll;

                       if (!gc->awaiting_classes) continue;
                       ll = eina_hash_set(gc->awaiting_classes, cc->name, NULL);
                       EINA_LIST_FREE(ll, cf_gcc)
                         _e_gadcon_client_populate(gc, cc, cf_gcc);
                    }
               }
             snprintf(buf, sizeof(buf), "%s Done", cc->name);
             e_main_ts(buf);
             x++;
          }
        if (x && _modules_loaded) _e_gadcon_event_populate(gc);
          e_gadcon_layout_thaw(gc->o_container);
     }
   else
     {
        e_gadcon_layout_thaw(gc->o_container);
        if (gc->populated_classes && (!gc->populate_requests))
          _e_gadcon_event_populate(gc);
     }
   return EINA_TRUE;
}

E_API void
e_gadcon_unpopulate(E_Gadcon *gc)
{
   E_Gadcon_Client *gcc;

   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   /* Be careful, e_object_del does remove gcc from gc->clients */
   if (gc->o_container) e_gadcon_layout_freeze(gc->o_container);
   while (gc->clients)
     {
        gcc = eina_list_data_get(gc->clients);
        _e_gadcon_client_unpopulate(gcc);
        /* FIXME: this seems to happen under some rare and as-yet
         * undetermined circumstances in gadman
         */
        if (gcc != eina_list_data_get(gc->clients)) continue;
        CRI("DANGLING GADCON CLIENT %p! THIS IS A BUG!!!", gcc);
        e_object_unref(E_OBJECT(gcc));
     }
   if (gc->awaiting_classes)
     eina_hash_free(gc->awaiting_classes);
   gc->awaiting_classes = NULL;
   if (gc->o_container && (!stopping) && (!e_object_is_del(E_OBJECT(gc))))
     e_gadcon_layout_thaw(gc->o_container);
}

E_API void
e_gadcon_repopulate(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->o_container) e_gadcon_layout_freeze(gc->o_container);
   e_gadcon_unpopulate(gc);
   e_gadcon_populate(gc);
   if (gc->o_container) e_gadcon_layout_thaw(gc->o_container);
}

E_API void
e_gadcon_populate_class(E_Gadcon *gc, const E_Gadcon_Client_Class *cc)
{
   Eina_List *l;
   E_Config_Gadcon_Client *cf_gcc;

   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   e_gadcon_layout_freeze(gc->o_container);
   EINA_LIST_FOREACH(gc->cf->clients, l, cf_gcc)
     {
        if (cf_gcc->name && (!strcmp(cf_gcc->name, cc->name)) &&
            (cf_gcc->id) && (cf_gcc->style))
          _e_gadcon_client_populate(gc, cc, cf_gcc);
     }
   e_gadcon_layout_thaw(gc->o_container);
}

E_API void
e_gadcon_orient(E_Gadcon *gc, E_Gadcon_Orient orient)
{
   Eina_List *l;
   E_Gadcon_Client *gcc;
   int horiz = 0;

   E_OBJECT_CHECK(gc);
   E_OBJECT_IF_NOT_TYPE(gc, E_GADCON_DUMMY_TYPE)
     {
        E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
     }
   if (gc->orient == orient) return;
   gc->orient = orient;
   if (gc->dummy) return;
   e_gadcon_layout_freeze(gc->o_container);
   switch (gc->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        horiz = 1;
        break;

      case E_GADCON_ORIENT_VERT:
      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_CORNER_RT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_RB:
        horiz = 0;
        break;

      default:
        break;
     }
   e_gadcon_layout_orientation_set(gc->o_container, horiz);
   EINA_LIST_FOREACH(gc->clients, l, gcc)
     {
        elm_box_horizontal_set(gcc->o_box, horiz);
        if (gcc->client_class->func.orient)
          gcc->client_class->func.orient(gcc, gc->orient);
     }
   e_gadcon_layout_thaw(gc->o_container);
}

E_API void
e_gadcon_edit_begin(E_Gadcon *gc)
{
   Eina_List *l;
   E_Gadcon_Client *gcc;

   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   e_gadcon_layout_freeze(gc->o_container);
   e_gadcon_locked_set(gc, 1);
   gc->editing = 1;
   EINA_LIST_FOREACH(gc->clients, l, gcc)
     e_gadcon_client_edit_begin(gcc);
   e_gadcon_layout_thaw(gc->o_container);
}

E_API void
e_gadcon_edit_end(E_Gadcon *gc)
{
   Eina_List *l;
   E_Gadcon_Client *gcc;

   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   e_gadcon_layout_freeze(gc->o_container);
   gc->editing = 0;
   EINA_LIST_FOREACH(gc->clients, l, gcc)
     e_gadcon_client_edit_end(gcc);
   e_gadcon_layout_thaw(gc->o_container);
   e_gadcon_locked_set(gc, 0);
}

E_API void
e_gadcon_all_edit_begin(void)
{
   Eina_List *l;
   E_Gadcon *gc;

   EINA_LIST_FOREACH(gadcons, l, gc)
     e_gadcon_edit_begin(gc);
}

E_API void
e_gadcon_all_edit_end(void)
{
   Eina_List *l;
   E_Gadcon *gc;

   EINA_LIST_FOREACH(gadcons, l, gc)
     e_gadcon_edit_end(gc);
}

E_API void
e_gadcon_zone_set(E_Gadcon *gc, E_Zone *zone)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->zone = zone;
   if (gc->cf) gc->cf->zone = zone->num;
}

E_API E_Zone *
e_gadcon_zone_get(E_Gadcon *gc)
{
   E_OBJECT_CHECK_RETURN(gc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, NULL);
   if (gc->zone) return gc->zone;
   if (!gc->toolbar) return NULL;
   return e_win_client_get(gc->toolbar->fwin)->zone;
}

E_API void
e_gadcon_ecore_evas_set(E_Gadcon *gc, Ecore_Evas *ee)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->ecore_evas = ee;
}

E_API int
e_gadcon_canvas_zone_geometry_get(E_Gadcon *gc, int *x, int *y, int *w, int *h)
{
   E_OBJECT_CHECK_RETURN(gc, 0);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, 0);
   if (!gc->ecore_evas) return 0;
   ecore_evas_geometry_get(gc->ecore_evas, x, y, w, h);
   *x = 0;
   *y = 0;
// so much relies on this down here to have been broken... ie return comp-relative coords.
//   if (gc->zone)
//     {
//	if (x) *x = *x - gc->zone->x;
//	if (y) *y = *y - gc->zone->y;
//     }
   return 1;
}

E_API void
e_gadcon_util_menu_attach_func_set(E_Gadcon *gc, void (*func)(void *data, E_Gadcon_Client *gcc, E_Menu *menu), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->menu_attach.func = func;
   gc->menu_attach.data = data;
}

E_API void
e_gadcon_util_lock_func_set(E_Gadcon *gc, void (*func)(void *data, int lock), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->locked_set.func = func;
   gc->locked_set.data = data;
}

E_API void
e_gadcon_util_urgent_show_func_set(E_Gadcon *gc, void (*func)(void *data), void *data)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->urgent_show.func = func;
   gc->urgent_show.data = data;
}

E_API void
e_gadcon_dnd_window_set(E_Gadcon *gc, Ecore_X_Window win)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->dnd_win = win;
}

E_API Ecore_X_Window
e_gadcon_dnd_window_get(E_Gadcon *gc)
{
   E_OBJECT_CHECK_RETURN(gc, 0);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, 0);
   return gc->dnd_win;
}

E_API void
e_gadcon_xdnd_window_set(E_Gadcon *gc, Ecore_X_Window win)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->xdnd_win = win;
}

E_API Ecore_X_Window
e_gadcon_xdnd_window_get(E_Gadcon *gc)
{
   E_OBJECT_CHECK_RETURN(gc, 0);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, 0);
   return gc->xdnd_win;
}

E_API void
e_gadcon_name_set(E_Gadcon *gc, const char *name)
{
   if (!gc) return;
   if (!name) return;
   if (gc->name == name) return;
   eina_stringshare_replace(&gc->name, name);
   eina_stringshare_replace(&gc->cf->name, name);
}

E_API void
e_gadcon_shelf_set(E_Gadcon *gc, E_Shelf *shelf)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->shelf = shelf;
}

E_API E_Shelf *
e_gadcon_shelf_get(E_Gadcon *gc)
{
   E_OBJECT_CHECK_RETURN(gc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, NULL);
   return gc->shelf;
}

E_API void
e_gadcon_toolbar_set(E_Gadcon *gc, E_Toolbar *toolbar)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   gc->toolbar = toolbar;
}

E_API E_Toolbar *
e_gadcon_toolbar_get(E_Gadcon *gc)
{
   E_OBJECT_CHECK_RETURN(gc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, NULL);
   return gc->toolbar;
}

E_API E_Config_Gadcon_Client *
e_gadcon_client_config_new(E_Gadcon *gc, const char *name)
{
   E_Gadcon_Client_Class *cc;
   E_Config_Gadcon_Client *cf_gcc;

   E_OBJECT_CHECK_RETURN(gc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, NULL);
   if (!name) return NULL;
   cc = eina_hash_find(providers, name);
   if (!cc) return NULL;
   if (!_e_gadcon_client_class_feature_check(cc, "id_new", cc->func.id_new))
     return NULL;

   cf_gcc = E_NEW(E_Config_Gadcon_Client, 1);
   if (!cf_gcc) return NULL;
   cf_gcc->id = eina_stringshare_add(cc->func.id_new(cc));
   if (!cf_gcc->id)
     {
        free(cf_gcc);
        return NULL;
     }
   cf_gcc->name = eina_stringshare_add(name);
   if (gc->zone)
     cf_gcc->geom.res = gc->zone->w;
   else if (gc->o_container)
     {
        int w, h;
        evas_object_geometry_get(gc->o_container, NULL, NULL, &w, &h);
        switch (gc->orient)
          {
           case E_GADCON_ORIENT_VERT:
           case E_GADCON_ORIENT_LEFT:
           case E_GADCON_ORIENT_RIGHT:
           case E_GADCON_ORIENT_CORNER_LT:
           case E_GADCON_ORIENT_CORNER_RT:
           case E_GADCON_ORIENT_CORNER_LB:
           case E_GADCON_ORIENT_CORNER_RB:
             cf_gcc->geom.res = h;
             break;

           default:
             cf_gcc->geom.res = w;
          }
     }
   else
     cf_gcc->geom.res = 800;
   cf_gcc->geom.size = 80;
   cf_gcc->geom.pos = cf_gcc->geom.res - cf_gcc->geom.size;
   cf_gcc->style = NULL;
   cf_gcc->autoscroll = 0;
   cf_gcc->resizable = 0;
   gc->cf->clients = eina_list_append(gc->cf->clients, cf_gcc);
   e_config_save_queue();
   return cf_gcc;
}

E_API void
e_gadcon_client_config_del(E_Config_Gadcon *cf_gc, E_Config_Gadcon_Client *cf_gcc)
{
   E_Gadcon *gc;
   Eina_List *l, *ll;

   if (!cf_gcc) return;
   EINA_LIST_FOREACH(gadcons, l, gc)
     {
        if (!gc->awaiting_classes) continue;
        ll = eina_hash_find(gc->awaiting_classes, cf_gcc->name);
        eina_hash_set(gc->awaiting_classes, cf_gcc->name, eina_list_remove(ll, cf_gcc));
     }

   eina_stringshare_del(cf_gcc->name);
   eina_stringshare_del(cf_gcc->id);
   eina_stringshare_del(cf_gcc->style);
   if (cf_gc) cf_gc->clients = eina_list_remove(cf_gc->clients, cf_gcc);
   free(cf_gcc);
}

E_API E_Gadcon_Client *
e_gadcon_client_find(E_Gadcon *gc, E_Config_Gadcon_Client *cf_gcc)
{
   E_Gadcon_Client *gcc;
   Eina_List *l, *ll;

   if (!cf_gcc) return NULL;
   if (gc)
     {
        EINA_LIST_FOREACH(gc->clients, ll, gcc)
          if (gcc->cf == cf_gcc) return gcc;
        return NULL;
     }
   EINA_LIST_FOREACH(gadcons, l, gc)
     EINA_LIST_FOREACH(gc->clients, ll, gcc)
       if (gcc->cf == cf_gcc) return gcc;
   return NULL;
}

static void
_e_gadcon_client_box_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc = data;
   gcc->o_box = NULL;
}

static void
_e_gadcon_client_box_hints_changed(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc = data;
   if (gcc->autoscroll)
     _e_gadcon_client_scroll_state_update(gcc);
}

static void
_e_gadcon_client_frame_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc = data;
   gcc->o_frame = NULL;
}

E_API void
e_gadcon_drag_finished_cb(E_Drag *drag, int dropped)
{
   E_Gadcon_Client *gcc;

   gcc = drag->data;
   gcc->drag.drag = NULL;
   if (!dropped)
     {
        /* free client config */
        e_gadcon_client_config_del(NULL, gcc->cf);
        gcc->cf = NULL;
        /* delete the gadcon client */
        /* TODO: Clean up module config too? */
        e_object_del(E_OBJECT(gcc));
     }
   e_gadcon_client_drag_set(NULL);
   gcc->gadcon->new_gcc = NULL;
   e_object_unref(E_OBJECT(gcc));
}

/**
 * Creates a new gadget
 *
 * @param gc gadcon pointer
 * @param name to use for gadget
 * @param id assigned to gadget
 * @param style to for gadget
 * @param base_obj the evas object that will show up in the shelf or gadget module
 * @return returns pointer to created gadget, on failure returns null
 */
E_API E_Gadcon_Client *
e_gadcon_client_new(E_Gadcon *gc, const char *name, const char *id EINA_UNUSED, const char *style, Evas_Object *base_obj)
{
   E_Gadcon_Client *gcc;

   E_OBJECT_CHECK_RETURN(gc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gc, E_GADCON_TYPE, NULL);
   gcc = E_OBJECT_ALLOC(E_Gadcon_Client, E_GADCON_CLIENT_TYPE,
                        _e_gadcon_client_free);
   if (!gcc) return NULL;
   e_object_delfn_add(E_OBJECT(gcc), _e_gadcon_client_delfn, NULL);
   gcc->name = eina_stringshare_add(name);
   gcc->gadcon = gc;
   gcc->o_base = base_obj;
   if (gc->clients)
     gcc->id = E_GADCON_CLIENT(eina_list_last_data_get(gc->clients))->id + 1;
   gc->clients = eina_list_append(gc->clients, gcc);
   /* This must only be unique during runtime */
   if (gcc->o_base)
     evas_object_event_callback_add(gcc->o_base, EVAS_CALLBACK_DEL,
                                    _e_gadcon_client_del_hook, gcc);
   if ((gc->frame_request.func) && (style))
     {
        gcc->o_frame = gc->frame_request.func(gc->frame_request.data, gcc, style);
        gcc->style = eina_stringshare_add(style);
        if (gcc->o_frame)
          {
             edje_object_size_min_calc(gcc->o_frame, &(gcc->pad.w), &(gcc->pad.h));
             gcc->o_box = elm_box_add(e_win_evas_object_win_get(gcc->gadcon->o_container));
             evas_object_event_callback_add(gcc->o_box, EVAS_CALLBACK_DEL, _e_gadcon_client_box_del, gcc);
             evas_object_event_callback_add(gcc->o_box, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _e_gadcon_client_box_hints_changed, gcc);
             switch (gcc->gadcon->orient)
               {
                case E_GADCON_ORIENT_FLOAT:
                case E_GADCON_ORIENT_HORIZ:
                case E_GADCON_ORIENT_TOP:
                case E_GADCON_ORIENT_BOTTOM:
                case E_GADCON_ORIENT_CORNER_TL:
                case E_GADCON_ORIENT_CORNER_TR:
                case E_GADCON_ORIENT_CORNER_BL:
                case E_GADCON_ORIENT_CORNER_BR:
                  elm_box_horizontal_set(gcc->o_box, 1);
                  break;

                case E_GADCON_ORIENT_VERT:
                case E_GADCON_ORIENT_LEFT:
                case E_GADCON_ORIENT_RIGHT:
                case E_GADCON_ORIENT_CORNER_LT:
                case E_GADCON_ORIENT_CORNER_RT:
                case E_GADCON_ORIENT_CORNER_LB:
                case E_GADCON_ORIENT_CORNER_RB:
                  elm_box_horizontal_set(gcc->o_box, 0);
                  break;

                default:
                  break;
               }
             evas_object_event_callback_add(gcc->o_box, EVAS_CALLBACK_MOVE,
                                            _e_gadcon_cb_client_frame_moveresize, gcc);
             evas_object_event_callback_add(gcc->o_box, EVAS_CALLBACK_RESIZE,
                                            _e_gadcon_cb_client_frame_moveresize, gcc);
             evas_object_event_callback_add(gcc->o_frame, EVAS_CALLBACK_MOUSE_MOVE,
                                            _e_gadcon_cb_client_frame_mouse_move, gcc);
             if (gcc->o_base)
               {
                  E_EXPAND(gcc->o_base);
                  elm_box_pack_end(gcc->o_box, gcc->o_base);
               }
             edje_object_part_swallow(gcc->o_frame, gc->edje.swallow_name, gcc->o_box);
             evas_object_show(gcc->o_box);
             evas_object_show(gcc->o_frame);
          }
     }
   if (gcc->o_frame)
     {
        e_gadcon_layout_pack(gc->o_container, gcc->o_frame);
        evas_object_event_callback_add(gcc->o_frame, EVAS_CALLBACK_DEL, _e_gadcon_client_frame_del, gcc);
     }
   else if (gcc->o_base)
     e_gadcon_layout_pack(gc->o_container, gcc->o_base);
   if (gcc->o_base) evas_object_show(gcc->o_base);
   {
      E_Event_Gadcon_Client_Add *ev;

      ev = E_NEW(E_Event_Gadcon_Client_Add, 1);
      ev->gcc = gcc;
      e_object_ref(E_OBJECT(gcc));
      ecore_event_add(E_EVENT_GADCON_CLIENT_ADD, ev, _e_gadcon_client_event_free, NULL);
   }
   return gcc;
}

E_API void
e_gadcon_client_edit_begin(E_Gadcon_Client *gcc)
{
   Evas_Coord x, y, w, h;
   Evas_Object *base;
   int layer;

   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   if (gcc->o_control) return;

   base = gcc->o_frame ?: gcc->o_base;
   if (!base) return;
   layer = evas_object_layer_get(base) + 1;
   evas_object_geometry_get(base, &x, &y, &w, &h);

   gcc->o_control = edje_object_add(gcc->gadcon->evas);
   /* FIXME: should probably be in gadget theme or something */
   evas_object_layer_set(gcc->o_control, layer);
   e_gadcon_locked_set(gcc->gadcon, 1);
   gcc->gadcon->editing = 1;

   evas_object_move(gcc->o_control, x, y);
   evas_object_resize(gcc->o_control, w, h);
   e_theme_edje_object_set(gcc->o_control, "base/theme/gadman",
                           "e/gadman/control");

   if ((gcc->autoscroll) /* || (gcc->resizable)*/)
     {
        if (elm_box_horizontal_get(gcc->o_box))
          edje_object_signal_emit(gcc->o_control, "e,state,hsize,on", "e");
        else
          edje_object_signal_emit(gcc->o_control, "e,state,vsize,on", "e");
     }
   else
     {
        edje_object_signal_emit(gcc->o_control, "e,state,hsize,off", "e");
        edje_object_signal_emit(gcc->o_control, "e,state,vsize,off", "e");
     }
   edje_object_signal_emit(gcc->o_control, "e,state,move,on", "e");

   gcc->o_event = evas_object_rectangle_add(gcc->gadcon->evas);
   evas_object_color_set(gcc->o_event, 0, 0, 0, 0);
   evas_object_repeat_events_set(gcc->o_event, 1);
   evas_object_layer_set(gcc->o_event, layer);
   evas_object_move(gcc->o_event, x, y);
   evas_object_resize(gcc->o_event, w, h);

   edje_object_signal_callback_add(gcc->o_control, "e,action,move,start", "",
                                   _e_gadcon_cb_signal_move_start, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,move,stop", "",
                                   _e_gadcon_cb_signal_move_stop, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,move,go", "",
                                   _e_gadcon_cb_signal_move_go, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,left,start", "",
                                   _e_gadcon_cb_signal_resize_left_start, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,left,stop", "",
                                   _e_gadcon_cb_signal_resize_left_stop, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,left,go", "",
                                   _e_gadcon_cb_signal_resize_left_go, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,right,start", "",
                                   _e_gadcon_cb_signal_resize_right_start, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,right,stop", "",
                                   _e_gadcon_cb_signal_resize_right_stop, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,right,go", "",
                                   _e_gadcon_cb_signal_resize_right_go, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,up,start", "",
                                   _e_gadcon_cb_signal_resize_left_start, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,up,stop", "",
                                   _e_gadcon_cb_signal_resize_left_stop, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,up,go", "",
                                   _e_gadcon_cb_signal_resize_left_go, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,down,start", "",
                                   _e_gadcon_cb_signal_resize_right_start, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,down,stop", "",
                                   _e_gadcon_cb_signal_resize_right_stop, gcc);
   edje_object_signal_callback_add(gcc->o_control, "e,action,resize,down,go", "",
                                   _e_gadcon_cb_signal_resize_right_go, gcc);

   evas_object_event_callback_add(gcc->o_event, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_gadcon_cb_client_mouse_down, gcc);
   evas_object_event_callback_add(gcc->o_event, EVAS_CALLBACK_MOUSE_IN,
                                  _e_gadcon_cb_client_mouse_in, gcc);
   evas_object_event_callback_add(gcc->o_event, EVAS_CALLBACK_MOUSE_OUT,
                                  _e_gadcon_cb_client_mouse_out, gcc);

   evas_object_event_callback_add(base, EVAS_CALLBACK_MOVE,
                                  _e_gadcon_cb_client_move, gcc);
   evas_object_event_callback_add(base, EVAS_CALLBACK_RESIZE,
                                  _e_gadcon_cb_client_resize, gcc);

   evas_object_show(gcc->o_event);
   evas_object_show(gcc->o_control);
}

E_API void
e_gadcon_client_edit_end(E_Gadcon_Client *gcc)
{
   Eina_List *l = NULL;
   E_Gadcon_Client *client = NULL;

   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (gcc->o_frame)
     {
        evas_object_event_callback_del(gcc->o_frame, EVAS_CALLBACK_MOVE,
                                       _e_gadcon_cb_client_move);
        evas_object_event_callback_del(gcc->o_frame, EVAS_CALLBACK_RESIZE,
                                       _e_gadcon_cb_client_resize);
     }
   else if (gcc->o_base)
     {
        evas_object_event_callback_del(gcc->o_base, EVAS_CALLBACK_MOVE,
                                       _e_gadcon_cb_client_move);
        evas_object_event_callback_del(gcc->o_base, EVAS_CALLBACK_RESIZE,
                                       _e_gadcon_cb_client_resize);
     }

   if (gcc->moving)
     {
        gcc->moving = 0;
        if (gcc->cf) _e_gadcon_client_save(gcc);
     }
   if (gcc->o_event) evas_object_del(gcc->o_event);
   gcc->o_event = NULL;
   if (gcc->o_control) evas_object_del(gcc->o_control);
   gcc->o_control = NULL;

   e_gadcon_locked_set(gcc->gadcon, 0);
   EINA_LIST_FOREACH(gcc->gadcon->clients, l, client)
     {
        if (!client) continue;
        if (client->o_control) return;
     }
   gcc->gadcon->editing = 0;
}

E_API void
e_gadcon_client_show(E_Gadcon_Client *gcc)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (!gcc->hidden) return;
   if (gcc->o_box) evas_object_show(gcc->o_box);
   if (gcc->o_frame) evas_object_show(gcc->o_frame);
   if (gcc->o_base) evas_object_show(gcc->o_base);
   if (gcc->o_control) evas_object_show(gcc->o_control);
   if (gcc->o_event) evas_object_show(gcc->o_event);
   if (gcc->o_frame)
     {
        e_gadcon_layout_pack(gcc->gadcon->o_container, gcc->o_frame);
        e_gadcon_layout_pack_options_set(gcc->o_frame, gcc);
     }
   else if (gcc->o_base)
     {
        e_gadcon_layout_pack(gcc->gadcon->o_container, gcc->o_base);
        e_gadcon_layout_pack_options_set(gcc->o_base, gcc);
     }
   gcc->hidden = 0;
}

E_API void
e_gadcon_client_hide(E_Gadcon_Client *gcc)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (gcc->hidden) return;
   if (gcc->o_box) evas_object_hide(gcc->o_box);
   if (gcc->o_frame) evas_object_hide(gcc->o_frame);
   if (gcc->o_base) evas_object_hide(gcc->o_base);
   if (gcc->o_control) evas_object_hide(gcc->o_control);
   if (gcc->o_event) evas_object_hide(gcc->o_event);
   if (gcc->o_frame)
     e_gadcon_layout_unpack(gcc->o_frame);
   else if (gcc->o_base)
     e_gadcon_layout_unpack(gcc->o_base);
   gcc->hidden = 1;
}

E_API void
e_gadcon_client_size_request(E_Gadcon_Client *gcc, Evas_Coord w, Evas_Coord h)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   switch (gcc->gadcon->orient)
     {
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_BOTTOM:
        if (gcc->o_frame)
          e_gadcon_layout_pack_size_set(gcc->o_frame, w + gcc->pad.w);
        else if (gcc->o_base)
          e_gadcon_layout_pack_size_set(gcc->o_base, w);
        break;

      case E_GADCON_ORIENT_VERT:
      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_RIGHT:
        if (gcc->o_frame)
          e_gadcon_layout_pack_size_set(gcc->o_frame, h + gcc->pad.h);
        else if (gcc->o_base)
          e_gadcon_layout_pack_size_set(gcc->o_base, h);
        break;

      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_CORNER_RT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_RB:
      default:
        break;
     }
}

E_API void
e_gadcon_client_min_size_set(E_Gadcon_Client *gcc, Evas_Coord w, Evas_Coord h)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   gcc->min.w = w;
   gcc->min.h = h;
/*   if (!gcc->resizable)*/
   {
      if (gcc->o_frame)
        e_gadcon_layout_pack_min_size_set(gcc->o_frame, w + gcc->pad.w,
                                          h + gcc->pad.h);
      else if (gcc->o_base)
        e_gadcon_layout_pack_min_size_set(gcc->o_base, w, h);
   }
   _e_gadcon_moveresize_handle(gcc);
}

E_API void
e_gadcon_client_aspect_set(E_Gadcon_Client *gcc, int w, int h)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   gcc->aspect.w = w;
   gcc->aspect.h = h;
//   if ((!gcc->autoscroll)/* && (!gcc->resizable)*/)
   {
      if (gcc->o_frame)
        {
           e_gadcon_layout_pack_aspect_pad_set(gcc->o_frame, gcc->pad.w,
                                               gcc->pad.h);
           e_gadcon_layout_pack_aspect_set(gcc->o_frame, w, h);
        }
      else if (gcc->o_base)
        e_gadcon_layout_pack_aspect_set(gcc->o_base, w, h);
   }
   _e_gadcon_moveresize_handle(gcc);
}

E_API void
e_gadcon_client_autoscroll_toggle_disabled_set(E_Gadcon_Client *gcc, Eina_Bool disable)
{
   disable = !!disable;
   if (gcc->autoscroll_disabled == disable) return;
   gcc->autoscroll_disabled = disable;
   if (disable) e_gadcon_client_autoscroll_set(gcc, 1);
}

E_API void
e_gadcon_client_autoscroll_set(E_Gadcon_Client *gcc, int autoscroll)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (gcc->autoscroll_disabled && (!autoscroll))
     {
        e_util_dialog_show(_("Gadget error"), _("%s does not support disabling autoscrolling"), gcc->name);
        return;
     }
   gcc->autoscroll = autoscroll;
   gcc->autoscroll_set = 1;
/*
   if (gcc->autoscroll)
     {
        if (gcc->o_frame)
          {
             e_gadcon_layout_pack_aspect_pad_set(gcc->o_frame, gcc->pad.w,
                                                 gcc->pad.h);
             e_gadcon_layout_pack_aspect_set(gcc->o_frame, 0, 0);
             e_gadcon_layout_pack_min_size_set(gcc->o_frame, 0, 0);
          }
        else if (gcc->o_base)
          {
             e_gadcon_layout_pack_aspect_set(gcc->o_base, 0, 0);
             e_gadcon_layout_pack_min_size_set(gcc->o_base, 0, 0);
          }
     }
   else
 */
   {
      if (gcc->o_frame)
        {
           e_gadcon_layout_pack_aspect_pad_set(gcc->o_frame, gcc->pad.w,
                                               gcc->pad.h);
           e_gadcon_layout_pack_aspect_set(gcc->o_frame, gcc->aspect.w,
                                           gcc->aspect.h);
           e_gadcon_layout_pack_min_size_set(gcc->o_frame, gcc->min.w,
                                             gcc->min.h);
        }
      else if (gcc->o_base)
        {
           e_gadcon_layout_pack_min_size_set(gcc->o_base, gcc->min.w,
                                             gcc->min.h);
           e_gadcon_layout_pack_aspect_set(gcc->o_base, gcc->aspect.w,
                                           gcc->aspect.h);
        }
   }
   if (gcc->autoscroll)
     _e_gadcon_client_scroll_state_update(gcc);
}

E_API void
e_gadcon_client_resizable_set(E_Gadcon_Client *gcc EINA_UNUSED, int resizable EINA_UNUSED)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
/*
   gcc->resizable = resizable;
   if (gcc->resizable)
     {
        if (gcc->o_frame)
          {
             e_gadcon_layout_pack_aspect_pad_set(gcc->o_frame, gcc->pad.w,
                                                 gcc->pad.h);
             e_gadcon_layout_pack_aspect_set(gcc->o_frame, 0, 0);
             e_gadcon_layout_pack_min_size_set(gcc->o_frame, 0, 0);
          }
        else if (gcc->o_base)
          {
             e_gadcon_layout_pack_min_size_set(gcc->o_base, 0, 0);
             e_gadcon_layout_pack_aspect_set(gcc->o_base, 0, 0);
          }
     }
   else
     {
        if (gcc->o_frame)
          {
             e_gadcon_layout_pack_aspect_pad_set(gcc->o_frame, gcc->pad.w,
                                                 gcc->pad.h);
             e_gadcon_layout_pack_aspect_set(gcc->o_frame, gcc->aspect.w,
                                             gcc->aspect.h);
             e_gadcon_layout_pack_min_size_set(gcc->o_frame, gcc->min.w,
                                               gcc->min.h);
          }
        else if (gcc->o_base)
          {
             e_gadcon_layout_pack_min_size_set(gcc->o_base, gcc->min.w,
                                               gcc->min.h);
             e_gadcon_layout_pack_aspect_set(gcc->o_base, gcc->aspect.w,
                                             gcc->aspect.h);
          }
     }
 */
}

E_API int
e_gadcon_client_geometry_get(E_Gadcon_Client *gcc, int *x, int *y, int *w, int *h)
{
   int gx = 0, gy = 0;

   E_OBJECT_CHECK_RETURN(gcc, 0);
   E_OBJECT_TYPE_CHECK_RETURN(gcc, E_GADCON_CLIENT_TYPE, 0);
   if (!e_gadcon_canvas_zone_geometry_get(gcc->gadcon, &gx, &gy, NULL, NULL))
     return 0;
   if (gcc->o_base) evas_object_geometry_get(gcc->o_base, x, y, w, h);
   if (x) *x += gx;
   if (y) *y += gy;
   return 1;
}

E_API int
e_gadcon_client_viewport_geometry_get(E_Gadcon_Client *gcc, int *x, int *y, int *w, int *h)
{
   E_OBJECT_CHECK_RETURN(gcc, 0);
   E_OBJECT_TYPE_CHECK_RETURN(gcc, E_GADCON_CLIENT_TYPE, 0);

   if (gcc->o_box) evas_object_geometry_get(gcc->o_base, x, y, w, h);
   else if (gcc->o_base)
     evas_object_geometry_get(gcc->o_base, x, y, w, h);
   else
     {
        if (x) *x = 0;
        if (y) *y = 0;
        if (w) *w = 0;
        if (h) *h = 0;
     }
   return 1;
}

E_API E_Zone *
e_gadcon_client_zone_get(E_Gadcon_Client *gcc)
{
   E_OBJECT_CHECK_RETURN(gcc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gcc, E_GADCON_CLIENT_TYPE, NULL);
   return e_gadcon_zone_get(gcc->gadcon);
}

static Eina_Bool
_e_gadcon_client_populate(E_Gadcon *gc, const E_Gadcon_Client_Class *cc, E_Config_Gadcon_Client *cf_gcc)
{
   E_Gadcon_Client *gcc;

   if (!eina_list_data_find(gc->populated_classes, cc))
     {
        _e_gadcon_provider_populate_request(gc, cc);
        return EINA_TRUE;
     }

   if ((!cf_gcc->id) &&
       (_e_gadcon_client_class_feature_check(cc, "id_new", cc->func.id_new)))
     cf_gcc->id = eina_stringshare_add(cc->func.id_new(cc));

   if (!cf_gcc->style)
     gcc = cc->func.init(gc, cf_gcc->name, cf_gcc->id,
                         cc->default_style);
   else
     gcc = cc->func.init(gc, cf_gcc->name, cf_gcc->id,
                         cf_gcc->style);

   if (!gcc) return EINA_FALSE;
   gcc->cf = cf_gcc;
   gcc->client_class = cc;
   gcc->config.pos = cf_gcc->geom.pos;
   gcc->config.size = cf_gcc->geom.size;
   gcc->config.res = cf_gcc->geom.res;
   gcc->state_info.seq = cf_gcc->state_info.seq;
   gcc->state_info.flags = cf_gcc->state_info.flags;
   gcc->config.pos_x = gcc->cf->geom.pos_x;
   gcc->config.pos_y = gcc->cf->geom.pos_y;
   gcc->config.size_w = gcc->cf->geom.size_w;
   gcc->config.size_h = gcc->cf->geom.size_h;
   gcc->cf->resizable = 0;

   eina_stringshare_replace(&gcc->style, cf_gcc->style);
   if (gcc->o_frame)
     e_gadcon_layout_pack_options_set(gcc->o_frame, gcc);
   else if (gcc->o_base)
     e_gadcon_layout_pack_options_set(gcc->o_base, gcc);

   if (!gcc->autoscroll_set)
     e_gadcon_client_autoscroll_set(gcc, cf_gcc->autoscroll);
   //		  e_gadcon_client_resizable_set(gcc, cf_gcc->resizable);
   if (gcc->client_class->func.orient)
     gcc->client_class->func.orient(gcc, gc->orient);

   e_config_save_queue();
   if (gc->editing) e_gadcon_client_edit_begin(gcc);
   if (gc->instant_edit)
     e_gadcon_client_util_menu_attach(gcc);
   return EINA_TRUE;
}

static void
_e_gadcon_client_unpopulate(E_Gadcon_Client *gcc)
{
   if (gcc->menu)
     {
        if (gcc->gadcon->shelf && (gcc->menu == gcc->gadcon->shelf->menu)) gcc->gadcon->shelf->menu = NULL;
        e_menu_post_deactivate_callback_set(gcc->menu, NULL, NULL);
        e_menu_deactivate(gcc->menu);
        e_object_del(E_OBJECT(gcc->menu));
        gcc->menu = NULL;
     }
   e_object_del(E_OBJECT(gcc));
}

static void
_e_gadcon_client_change_gadcon(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Gadcon_Location *src, *dst;
   E_Gadcon_Client *gcc;

   gcc = data;
   src = gcc->gadcon->location;
   dst = e_object_data_get(E_OBJECT(mi));
   _e_gadcon_location_change(gcc, src, dst);
}

static void
_e_gadcon_add_locations_menu_for_site(E_Menu *m, E_Gadcon_Client *gcc, E_Gadcon_Site site, int *count)
{
   E_Menu_Item *mi;
   const Eina_List *l;
   E_Gadcon_Location *loc;
   int k = *count;

   EINA_LIST_FOREACH(gadcon_locations, l, loc)
     {
        if (loc->site == site)
          {
             if (loc == gcc->gadcon->location) continue;
             if (k)
               {
                  k = 0;
                  mi = e_menu_item_new(m);
                  e_menu_item_separator_set(mi, 1);
                  (*count) = 0;
               }
             mi = e_menu_item_new(m);
             e_menu_item_label_set(mi, loc->name);
             e_object_data_set(E_OBJECT(mi), loc);
             e_menu_item_callback_set(mi, _e_gadcon_client_change_gadcon, gcc);
             if (loc->icon_name)
               e_util_menu_item_theme_icon_set(mi, loc->icon_name);
             (*count)++;
          }
     }
}

static void
_e_gadcon_gadget_move_to_pre_cb(void *data, E_Menu *m)
{
   E_Gadcon_Client *gcc;
   int n = 0;

   gcc = data;
   e_menu_pre_activate_callback_set(m, NULL, NULL);

   if (!gcc->client_class->func.is_site || gcc->client_class->func.is_site(E_GADCON_SITE_SHELF))
     _e_gadcon_add_locations_menu_for_site(m, gcc, E_GADCON_SITE_SHELF, &n);
   if (!gcc->client_class->func.is_site || gcc->client_class->func.is_site(E_GADCON_SITE_DESKTOP))
     _e_gadcon_add_locations_menu_for_site(m, gcc, E_GADCON_SITE_DESKTOP, &n);
   if (!gcc->client_class->func.is_site || gcc->client_class->func.is_site(E_GADCON_SITE_TOOLBAR))
     _e_gadcon_add_locations_menu_for_site(m, gcc, E_GADCON_SITE_TOOLBAR, &n);
   //if (!gcc->client_class->func.is_site || gcc->client_class->func.is_site(E_GADCON_SITE_EFM_TOOLBAR))
     //_e_gadcon_add_locations_menu_for_site(m, gcc, E_GADCON_SITE_EFM_TOOLBAR, &n);
   _e_gadcon_add_locations_menu_for_site(m, gcc, E_GADCON_SITE_UNKNOWN, &n);
}

E_API void
e_gadcon_client_add_location_menu(E_Gadcon_Client *gcc, E_Menu *menu)
{
   E_Menu *mn;
   E_Menu_Item *mi;

   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (gcc->gadcon->location)
     {
        mn = e_menu_new();
        mi = e_menu_item_new(menu);
        e_menu_item_label_set(mi, _("Move to"));
        e_util_menu_item_theme_icon_set(mi, "transform-move");
        e_menu_item_submenu_set(mi, mn);
        e_menu_pre_activate_callback_set(mn, _e_gadcon_gadget_move_to_pre_cb, gcc);
        e_object_unref(E_OBJECT(mn));
     }
}

E_API E_Menu *
e_gadcon_client_menu_set(E_Gadcon_Client *gcc, E_Menu *m)
{
   E_Menu *ret;

   E_OBJECT_CHECK_RETURN(gcc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gcc, E_GADCON_CLIENT_TYPE, NULL);

   ret = gcc->menu;
   if (ret)
     e_menu_post_deactivate_callback_set(ret, NULL, NULL);
   gcc->menu = m;
   if (gcc->gadcon->shelf) gcc->gadcon->shelf->menu = m;
   if (m)
     e_menu_post_deactivate_callback_set(m, _e_gadcon_client_cb_menu_post, gcc);
   return ret;
}

E_API E_Menu *
e_gadcon_client_util_menu_items_append(E_Gadcon_Client *gcc, E_Menu *menu_gadget, int flags EINA_UNUSED)
{
   E_Menu *mo, *menu_main = NULL;
   E_Menu_Item *mi;
   char buf[256];

   E_OBJECT_CHECK_RETURN(gcc, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(gcc, E_GADCON_CLIENT_TYPE, NULL);

   if (e_config->menu_gadcon_client_toplevel)
     menu_main = menu_gadget;
   else
     menu_main = e_menu_new();
   e_menu_post_deactivate_callback_set(menu_main, _e_gadcon_client_cb_menu_post, gcc);
   gcc->menu = menu_main;

   if (gcc->gadcon->shelf)
     {
        if (e_menu_item_nth(menu_gadget, 0))
          {
             mi = e_menu_item_new(menu_gadget);
             e_menu_item_separator_set(mi, 1);
          }
/*
        if (!gcc->o_control)
          {
             mi = e_menu_item_new(menu_gadget);
             e_menu_item_label_set(mi, _("Move"));
             e_util_menu_item_theme_icon_set(mi, "transform-scale");
             e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_edit, gcc);
          }
        mi = e_menu_item_new(menu_gadget);
        e_menu_item_label_set(mi, _("Resizable"));
        e_util_menu_item_theme_icon_set(mi, "transform-scale");
        e_menu_item_check_set(mi, 1);
        if (gcc->resizable) e_menu_item_toggle_set(mi, 1);
        e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_resizable, gcc);
 */
        if (!gcc->autoscroll_disabled)
          {
             mi = e_menu_item_new(menu_gadget);
             e_menu_item_label_set(mi, _("Automatically scroll contents"));
             e_util_menu_item_theme_icon_set(mi, "transform-move");
             e_menu_item_check_set(mi, 1);
             if (gcc->autoscroll) e_menu_item_toggle_set(mi, 1);
             e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_autoscroll, gcc);
          }

        if (gcc->gadcon->shelf)
          {
             mo = e_menu_new();
             gcc->gadcon->shelf->menu = menu_main;

             mi = e_menu_item_new(mo);
             e_menu_item_label_set(mi, _("Plain"));
             e_util_menu_item_theme_icon_set(mi, "enlightenment/plain");
             e_menu_item_radio_group_set(mi, 1);
             e_menu_item_radio_set(mi, 1);
             if (!e_util_strcmp(gcc->style, E_GADCON_CLIENT_STYLE_PLAIN))
               e_menu_item_toggle_set(mi, 1);
             else if ((!gcc->style) &&
                      (!e_util_strcmp(gcc->client_class->default_style, E_GADCON_CLIENT_STYLE_PLAIN)))
               e_menu_item_toggle_set(mi, 1);
             e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_style_plain, gcc);

             mi = e_menu_item_new(mo);
             e_menu_item_label_set(mi, _("Inset"));
             e_util_menu_item_theme_icon_set(mi, "enlightenment/inset");
             e_menu_item_radio_group_set(mi, 1);
             e_menu_item_radio_set(mi, 1);
             if (!e_util_strcmp(gcc->style, E_GADCON_CLIENT_STYLE_INSET))
               e_menu_item_toggle_set(mi, 1);
             else if ((!gcc->style) &&
                      (!e_util_strcmp(gcc->client_class->default_style, E_GADCON_CLIENT_STYLE_INSET)))
               e_menu_item_toggle_set(mi, 1);
             e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_style_inset, gcc);

             mi = e_menu_item_new(menu_gadget);
             e_menu_item_label_set(mi, _("Look"));
             e_util_menu_item_theme_icon_set(mi, "preferences-look");
             e_menu_item_submenu_set(mi, mo);
             e_object_unref(E_OBJECT(mo));
          }

        mi = e_menu_item_new(menu_gadget);
        e_menu_item_separator_set(mi, 1);

        e_gadcon_client_add_location_menu(gcc, menu_gadget);

        mi = e_menu_item_new(menu_gadget);
        e_menu_item_label_set(mi, _("Remove"));
        e_util_menu_item_theme_icon_set(mi, "list-remove");
        e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_remove, gcc);
     }

   if ((!e_config->menu_gadcon_client_toplevel) && (!gcc->gadcon->toolbar))
     {
        mi = e_menu_item_new(menu_main);
        if (gcc->client_class->func.label)
          snprintf(buf, sizeof(buf), "%s",
                   gcc->client_class->func.label((E_Gadcon_Client_Class *)gcc->client_class));
        else
          snprintf(buf, sizeof(buf), "%s", gcc->name);

        e_menu_item_label_set(mi, _(buf));
        e_menu_item_realize_callback_set(mi, _e_gadcon_client_cb_menu_pre, gcc);
        e_menu_item_submenu_set(mi, menu_gadget);
        e_object_unref(E_OBJECT(menu_gadget));
     }

   if (gcc->gadcon->menu_attach.func)
     {
        if ((gcc->gadcon->shelf) || (gcc->gadcon->toolbar))
          {
             if (e_config->menu_gadcon_client_toplevel)
               {
                  mi = e_menu_item_new(menu_main);
                  e_menu_item_separator_set(mi, 1);
               }
             gcc->gadcon->menu_attach.func(gcc->gadcon->menu_attach.data, gcc, menu_main);
          }
        else
          gcc->gadcon->menu_attach.func(gcc->gadcon->menu_attach.data, gcc, menu_gadget);
     }

   return menu_main;
}

E_API void
e_gadcon_client_util_menu_attach(E_Gadcon_Client *gcc)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   if (gcc->o_frame)
     {
        evas_object_event_callback_del_full(gcc->o_frame, EVAS_CALLBACK_MOUSE_DOWN,
                                            _e_gadcon_client_cb_mouse_down, gcc);
        evas_object_event_callback_del_full(gcc->o_frame, EVAS_CALLBACK_MOUSE_UP,
                                            _e_gadcon_client_cb_mouse_up, gcc);
        evas_object_event_callback_del_full(gcc->o_frame, EVAS_CALLBACK_MOUSE_MOVE,
                                            _e_gadcon_client_cb_mouse_move, gcc);
        evas_object_event_callback_add(gcc->o_frame, EVAS_CALLBACK_MOUSE_DOWN,
                                       _e_gadcon_client_cb_mouse_down, gcc);
        evas_object_event_callback_add(gcc->o_frame, EVAS_CALLBACK_MOUSE_UP,
                                       _e_gadcon_client_cb_mouse_up, gcc);
        evas_object_event_callback_add(gcc->o_frame, EVAS_CALLBACK_MOUSE_MOVE,
                                       _e_gadcon_client_cb_mouse_move, gcc);
     }
   else if (gcc->o_base)
     {
        evas_object_event_callback_del_full(gcc->o_base, EVAS_CALLBACK_MOUSE_DOWN,
                                            _e_gadcon_client_cb_mouse_down, gcc);
        evas_object_event_callback_del_full(gcc->o_base, EVAS_CALLBACK_MOUSE_UP,
                                            _e_gadcon_client_cb_mouse_up, gcc);
        evas_object_event_callback_del_full(gcc->o_base, EVAS_CALLBACK_MOUSE_MOVE,
                                            _e_gadcon_client_cb_mouse_move, gcc);
        evas_object_event_callback_add(gcc->o_base, EVAS_CALLBACK_MOUSE_DOWN,
                                       _e_gadcon_client_cb_mouse_down, gcc);
        evas_object_event_callback_add(gcc->o_base, EVAS_CALLBACK_MOUSE_UP,
                                       _e_gadcon_client_cb_mouse_up, gcc);
        evas_object_event_callback_add(gcc->o_base, EVAS_CALLBACK_MOUSE_MOVE,
                                       _e_gadcon_client_cb_mouse_move, gcc);
     }
}

E_API void
e_gadcon_locked_set(E_Gadcon *gc, int lock)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->locked_set.func)
     gc->locked_set.func(gc->locked_set.data, lock);
}

E_API void
e_gadcon_urgent_show(E_Gadcon *gc)
{
   E_OBJECT_CHECK(gc);
   E_OBJECT_TYPE_CHECK(gc, E_GADCON_TYPE);
   if (gc->urgent_show.func)
     gc->urgent_show.func(gc->urgent_show.data);
}

/*
 * NOTE: x & y are relative to the o_box of the gadcon.
 */
E_API void
e_gadcon_client_autoscroll_update(E_Gadcon_Client *gcc, Evas_Coord x, Evas_Coord y)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);

   if (gcc->autoscroll)
     {
        Evas_Coord w, h;
        double d;

        /* TODO: When using gadman there is no o_box! */
        evas_object_geometry_get(gcc->o_box, NULL, NULL, &w, &h);
        if (elm_box_horizontal_get(gcc->o_box))
          {
             if (w > 1) d = (double)x / (double)(w - 1);
             else d = 0;
          }
        else
          {
             if (h > 1) d = (double)y / (double)(h - 1);
             else d = 0;
          }
        if (d < 0.0) d = 0.0;
        else if (d > 1.0)
          d = 1.0;
        if (!gcc->scroll_timer)
          gcc->scroll_timer =
            ecore_timer_loop_add(0.01, _e_gadcon_cb_client_scroll_timer, gcc);
        if (!gcc->scroll_animator)
          gcc->scroll_animator =
            ecore_animator_add(_e_gadcon_cb_client_scroll_animator, gcc);
        gcc->scroll_wanted = d;
     }
}

E_API void
e_gadcon_client_autoscroll_cb_set(E_Gadcon_Client *gcc, void (*func)(void *data), void *data)
{
   E_OBJECT_CHECK(gcc);
   E_OBJECT_TYPE_CHECK(gcc, E_GADCON_CLIENT_TYPE);
   gcc->scroll_cb.func = func;
   gcc->scroll_cb.data = data;
}

E_API Eina_Bool
e_gadcon_site_is_shelf(E_Gadcon_Site site)
{
   return site == E_GADCON_SITE_SHELF;
}

E_API Eina_Bool
e_gadcon_site_is_desktop(E_Gadcon_Site site)
{
   return site == E_GADCON_SITE_DESKTOP;
}

E_API Eina_Bool
e_gadcon_site_is_efm_toolbar(E_Gadcon_Site site)
{
   return site == E_GADCON_SITE_EFM_TOOLBAR;
}

E_API Eina_Bool
e_gadcon_site_is_any_toolbar(E_Gadcon_Site site)
{
   switch (site)
     {
      // there should be all toolbar sities identifiers
      case E_GADCON_SITE_TOOLBAR:
      case E_GADCON_SITE_EFM_TOOLBAR:
        return EINA_TRUE;

      default:
        return EINA_FALSE;
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_gadcon_site_is_not_toolbar(E_Gadcon_Site site)
{
   switch (site)
     {
      // there should be all toolbar sities identifiers
      case E_GADCON_SITE_TOOLBAR:
      case E_GADCON_SITE_EFM_TOOLBAR:
        return EINA_FALSE;

      default:
        return EINA_TRUE;
     }
   return EINA_TRUE;
}

E_API void
e_gadcon_client_drag_set(E_Gadcon_Client *gcc)
{
   Eina_List *l;
   E_Gadcon *gc;

   EINA_LIST_FOREACH(gadcons, l, gc)
     gc->drag_gcc = gcc;
}

/* local subsystem functions */
static void
_e_gadcon_free(E_Gadcon *gc)
{
   if (gc->dummy)
     dummies = eina_list_remove(dummies, gc);
   else
     {
        e_gadcon_unpopulate(gc);
        gadcons = eina_list_remove(gadcons, gc);
     }
   eina_list_free(gc->populated_classes);
   if (custom_populate_requests)
     custom_populate_requests = eina_list_remove(custom_populate_requests, gc);
   if (gc->o_container) evas_object_del(gc->o_container);
   eina_stringshare_del(gc->name);
   eina_stringshare_del(gc->edje.swallow_name);
   if (gc->edje.o_parent)
     evas_object_event_callback_del_full(gc->edje.o_parent, EVAS_CALLBACK_RESIZE,
                                   (Evas_Object_Event_Cb)_e_gadcon_parent_resize_cb, gc);
   if (gc->config_dialog) e_object_del(E_OBJECT(gc->config_dialog));
   if (gc->drop_handler) e_drop_handler_del(gc->drop_handler);
   if (gc->cfg_delete)
     {
        eina_stringshare_del(gc->cf->name);
        e_config->gadcons = eina_list_remove(e_config->gadcons, gc->cf);
        free(gc->cf);
        e_config_save_queue();
     }
   free(gc);
}

static void
_e_gadcon_client_event_free(void *d EINA_UNUSED, void *e)
{
   E_Event_Gadcon_Client_Del *ev = e;

   e_object_unref(E_OBJECT(ev->gcc));
   free(ev);
}

static void
_e_gadcon_event_populate(E_Gadcon *gc)
{
   E_Event_Gadcon_Populate *ev;

   ev = E_NEW(E_Event_Gadcon_Populate, 1);
   e_object_ref(E_OBJECT(gc));
   ev->gc = gc;
   ecore_event_add(E_EVENT_GADCON_POPULATE, ev, _e_gadcon_event_populate_free, NULL);
}

static void
_e_gadcon_client_delfn(void *d EINA_UNUSED, void *o)
{
   E_Gadcon_Client *gcc = o;
   E_Event_Gadcon_Client_Add *ev;

   if (gcc->instant_edit_timer)
     {
        ecore_timer_del(gcc->instant_edit_timer);
        gcc->instant_edit_timer = NULL;
     }
   if (gcc->o_base)
     evas_object_event_callback_del(gcc->o_base, EVAS_CALLBACK_DEL,
                                    _e_gadcon_client_del_hook);
   if (gcc->menu)
     {
        if (gcc->gadcon->shelf && (gcc->menu == gcc->gadcon->shelf->menu)) gcc->gadcon->shelf->menu = NULL;
        e_menu_post_deactivate_callback_set(gcc->menu, NULL, NULL);
        e_menu_deactivate(gcc->menu);
        e_object_del(E_OBJECT(gcc->menu));
        gcc->menu = NULL;
     }
   e_gadcon_client_edit_end(gcc);
   gcc->client_class->func.shutdown(gcc);
   if ((gcc->client_class->func.id_del) && (gcc->cf))
     gcc->client_class->func.id_del((E_Gadcon_Client_Class *)gcc->client_class,
                                    gcc->cf->id);
   if (gcc->drag.drag)
     e_object_del(E_OBJECT(gcc->drag.drag));
   gcc->gadcon->clients = eina_list_remove(gcc->gadcon->clients, gcc);
   E_FREE_FUNC(gcc->scroll_timer, ecore_timer_del);
   E_FREE_FUNC(gcc->scroll_animator, ecore_animator_del);
   E_FREE_FUNC(gcc->o_box, evas_object_del);
   E_FREE_FUNC(gcc->o_frame, evas_object_del);
   e_object_ref(E_OBJECT(gcc));
   ev = E_NEW(E_Event_Gadcon_Client_Del, 1);
   ev->gcc = gcc;
   ecore_event_add(E_EVENT_GADCON_CLIENT_DEL, ev, _e_gadcon_client_event_free, NULL);
}

static void
_e_gadcon_client_free(E_Gadcon_Client *gcc)
{
   if (gcc->o_box) evas_object_del(gcc->o_box);
   if (gcc->o_frame) evas_object_del(gcc->o_frame);
   eina_stringshare_del(gcc->name);
   eina_stringshare_del(gcc->style);
   if (gcc->menu)
     {
        e_menu_deactivate(gcc->menu);
        e_object_del(E_OBJECT(gcc->menu));
     }
   free(gcc);
}

static void
_e_gadcon_moveresize_handle(E_Gadcon_Client *gcc)
{
   int w, h, mw, mh;

   if (!gcc->o_base) return;
   evas_object_geometry_get(gcc->o_box, NULL, NULL, &w, &h);
   if (gcc->gadcon->edje.o_parent)
     evas_object_geometry_get(gcc->gadcon->edje.o_parent, NULL, NULL, &mw, &mh);
   else
     mw = w, mh = h;
   w = MIN(w, mw);
   h = MIN(h, mh);
/*
   if (gcc->resizable)
     {
        if (elm_box_horizontal_get(gcc->o_box))
          {
             if ((gcc->aspect.w > 0) && (gcc->aspect.h > 0))
               w = (h * gcc->aspect.w) / gcc->aspect.h;
          }
        else
          {
             if ((gcc->aspect.w > 0) && (gcc->aspect.h > 0))
               h = (w * gcc->aspect.h) / gcc->aspect.w;
          }
     }
 */
   if (gcc->autoscroll)
     {
        if (elm_box_horizontal_get(gcc->o_box))
          {
             if ((gcc->aspect.w > 0) && (gcc->aspect.h > 0))
               {
                  w = (h * gcc->aspect.w) / gcc->aspect.h; // ZZZZ
//                  w = gcc->min.w;
               }
             else
               {
                  w = gcc->min.w;
               }
          }
        else
          {
             if ((gcc->aspect.w > 0) && (gcc->aspect.h > 0))
               {
                  h = (w * gcc->aspect.h) / gcc->aspect.w; // ZZZZ
//                  h = gcc->min.h;
               }
             else
               {
                  h = gcc->min.h;
               }
          }
     }
   evas_object_size_hint_min_set(gcc->o_base, w, h);
   evas_object_size_hint_max_set(gcc->o_base, mw, mh);
   evas_object_size_hint_min_set(gcc->o_box, 0, 0);
   _e_gadcon_client_scroll_state_update(gcc);
}

static void
_e_gadcon_parent_resize_cb(E_Gadcon *gc, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Eina_List *l;
   E_Gadcon_Client *gcc;

   EINA_LIST_FOREACH(gc->clients, l, gcc)
     _e_gadcon_moveresize_handle(gcc);
}

static Eina_Bool
_e_gadcon_cb_client_scroll_timer(void *data)
{
   E_Gadcon_Client *gcc;
   double d, v;

   gcc = data;
   d = gcc->scroll_wanted - gcc->scroll_pos;
   if (d < 0) d = -d;
   if (d < 0.001)
     {
        gcc->scroll_pos = gcc->scroll_wanted;
        gcc->scroll_timer = NULL;
        return ECORE_CALLBACK_CANCEL;
     }
   v = 0.05;
   gcc->scroll_pos = (gcc->scroll_pos * (1.0 - v)) + (gcc->scroll_wanted * v);
   return ECORE_CALLBACK_RENEW;
}

static void
_e_gadcon_client_scroll_state_update(E_Gadcon_Client *gcc)
{
   Evas_Coord box_w, box_h, base_w, base_h;

   if (gcc->o_frame)
     {
        evas_object_geometry_get(gcc->o_box, NULL, NULL, &box_w, &box_h);
        evas_object_geometry_get(gcc->o_base, NULL, NULL, &base_w, &base_h);

        if (((elm_box_horizontal_get(gcc->o_box)) && (box_w >= base_w)) ||
            ((!elm_box_horizontal_get(gcc->o_box)) && (box_h >= base_h)))
          {
             if (gcc->pscrollstate != 0)
               {
                  edje_object_signal_emit(gcc->o_frame, "e,state,scroll,none", "e");
                  gcc->pscrollstate = 0;
               }
          }
        else if (gcc->scroll_pos <= 0.01)
          {
             if (gcc->pscrollstate != 1)
               {
                  edje_object_signal_emit(gcc->o_frame, "e,state,scroll,begin", "e");
                  gcc->pscrollstate = 1;
               }
          }
        else if (gcc->scroll_pos >= 0.99)
          {
             if (gcc->pscrollstate != 3)
               {
                  edje_object_signal_emit(gcc->o_frame, "e,state,scroll,end", "e");
                  gcc->pscrollstate = 3;
               }
          }
        else
          {
             if (gcc->pscrollstate != 2)
               {
                  edje_object_signal_emit(gcc->o_frame, "e,state,scroll,middle", "e");
                  gcc->pscrollstate = 2;
               }
          }
     }
}

static Eina_Bool
_e_gadcon_cb_client_scroll_animator(void *data)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   if (elm_box_horizontal_get(gcc->o_box))
     elm_box_align_set(gcc->o_box, 1.0 - gcc->scroll_pos, 0.5);
   else
     elm_box_align_set(gcc->o_box, 0.5, 1.0 - gcc->scroll_pos);
   if (!gcc->scroll_timer)
     {
        gcc->scroll_animator = NULL;
        return ECORE_CALLBACK_CANCEL;
     }

   _e_gadcon_client_scroll_state_update(gcc);

   if (gcc->scroll_cb.func)
     gcc->scroll_cb.func(gcc->scroll_cb.data);

   return ECORE_CALLBACK_RENEW;
}

static void
_e_gadcon_cb_client_frame_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Gadcon_Client *gcc;
   Evas_Coord x, y;

   ev = event_info;
   gcc = data;
   evas_object_geometry_get(gcc->o_box, &x, &y, NULL, NULL);
   e_gadcon_client_autoscroll_update(gcc, ev->cur.output.x - x,
                                     ev->cur.output.y - y);
}

static void
_e_gadcon_cb_client_frame_moveresize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   _e_gadcon_moveresize_handle(gcc);
}

static void
_e_gadcon_client_save(E_Gadcon_Client *gcc)
{
   gcc->cf->geom.pos = gcc->config.pos;
   gcc->cf->geom.size = gcc->config.size;
   gcc->cf->geom.res = gcc->config.res;
   gcc->cf->geom.pos_x = gcc->config.pos_x;
   gcc->cf->geom.pos_y = gcc->config.pos_y;
   gcc->cf->geom.size_w = gcc->config.size_w;
   gcc->cf->geom.size_h = gcc->config.size_h;
   gcc->cf->state_info.seq = gcc->state_info.seq;
   gcc->cf->state_info.flags = gcc->state_info.flags;
   gcc->cf->autoscroll = gcc->autoscroll;
   eina_stringshare_replace(&gcc->cf->style, gcc->style);
/*   gcc->cf->resizable = gcc->resizable;*/
   gcc->cf->resizable = 0;
   e_config_save_queue();
}

static void
_e_gadcon_client_drag_begin(E_Gadcon_Client *gcc, int x, int y)
{
   E_Drag *drag;
   Evas_Object *o = NULL;
   Evas_Coord w = 0, h = 0;
   E_Zone *zone;
   const char *drag_types[] = { "enlightenment/gadcon_client" };

   zone = e_gadcon_zone_get(gcc->gadcon);
   if (gcc->gadcon->drag_gcc) return;
   if (!zone) return;

   e_gadcon_client_drag_set(gcc);

   e_object_ref(E_OBJECT(gcc));
   /* Remove this config from the current gadcon */
   gcc->gadcon->cf->clients =
     eina_list_remove(gcc->gadcon->cf->clients, gcc->cf);
   gcc->state_info.state = E_LAYOUT_ITEM_STATE_NONE;
   gcc->state_info.resist = 0;

   if (!e_drop_inside(gcc->gadcon->drop_handler, x, y))
     e_gadcon_client_hide(gcc);

   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);

   gcc->drag.drag = drag = e_drag_new(x, y,
                                      drag_types, 1, gcc, -1, NULL,
                                      e_gadcon_drag_finished_cb);
   if (!drag) return;
   drag->button_mask = evas_pointer_button_down_mask_get(e_comp->evas);

   o = gcc->client_class->func.icon((E_Gadcon_Client_Class *)gcc->client_class,
                                    e_drag_evas_get(drag));
   if (o)
     evas_object_geometry_get(o, NULL, NULL, &w, &h);
   else
     {
        /* FIXME: fallback icon for drag */
        o = evas_object_rectangle_add(e_drag_evas_get(drag));
        evas_object_color_set(o, 255, 255, 255, 100);
     }
   if (w < 10)
     w = h = 50;
   e_drag_object_set(drag, o);
   e_drag_resize(drag, w, h);
   e_drag_start(drag, x + w / 2, y + h / 2);
   e_drag_hide(drag);
}

static void
_e_gadcon_client_inject(E_Gadcon *gc, E_Gadcon_Client *gcc, int x, int y)
{
   Eina_List *l;
   E_Gadcon_Client *gcc2;
   Evas_Coord cx = 0, cy = 0, cw = 0, ch = 0;
   int i;

   /* Check if the gadcon client is in place */
   if (!gcc->hidden)
     {
        if (gcc->o_frame)
          evas_object_geometry_get(gcc->o_frame, &cx, &cy, &cw, &ch);
        else if (gcc->o_base)
          evas_object_geometry_get(gcc->o_base, &cx, &cy, &cw, &ch);
        else return;  /* make clang happy */

        if (E_INSIDE(x, y, cx, cy, cw, ch)) return;
     }

   /* If x, y is not inside any gadcon client, seq will be 0 and it's position
    * will later be used for placement. */
   gcc->state_info.seq = 0;
   for (i = 0; i < 2; i++)
     {
        /* two passes:
         * - find sequence position to inject
         * - update other gadget sequences
         */
        EINA_LIST_FOREACH(gc->clients, l, gcc2)
          {
             if (gcc == gcc2) continue;
             if (i == 1)
               {
                  /* on second pass, increment all sequence numbers which are
                   * >= the injected gadget's sequence to get ordering right
                   */
                  if (gcc2->state_info.seq >= gcc->state_info.seq)
                    gcc2->state_info.seq++;
                  continue;
               }
             if (gcc2->hidden) continue;
             if (gcc2->o_frame)
               evas_object_geometry_get(gcc2->o_frame, &cx, &cy, &cw, &ch);
             else if (gcc2->o_base)
               evas_object_geometry_get(gcc2->o_base, &cx, &cy, &cw, &ch);
             else return;  /* make clang happy */
             if (e_gadcon_layout_orientation_get(gc->o_container))
               {
                  /* inside left half of gadget */
                  if (E_INSIDE(x, y, cx, cy, cw / 2, ch))
                    {
                       /* place before */
                       gcc->state_info.seq = gcc2->state_info.seq;
                       break;
                    }
                  /* inside right half of gadget */
                  else if (E_INSIDE(x, y, cx + cw / 2, cy, cw / 2, ch))
                    {
                       /* place after */
                       gcc->state_info.seq = gcc2->state_info.seq + 1;
                       break;
                    }
               }
             else
               {
                  /* top half of gadget */
                  if (E_INSIDE(x, y, cx, cy, cw, ch / 2))
                    {
                       /* place before */
                       gcc->state_info.seq = gcc2->state_info.seq;
                       break;
                    }
                  /* bottom half of gadget */
                  else if (E_INSIDE(x, y, cx, cy + ch / 2, cw, ch / 2))
                    {
                       /* place after */
                       gcc->state_info.seq = gcc2->state_info.seq + 1;
                       break;
                    }
               }
          }
     }
}

static void
_e_gadcon_cb_min_size_request(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon *gc;

   gc = data;
   if (gc->min_size_request.func)
     {
        Evas_Coord w = 0, h = 0;

        e_gadcon_layout_min_size_get(gc->o_container, &w, &h);
        gc->min_size_request.func(gc->min_size_request.data, gc, w, h);
     }
}

static void
_e_gadcon_cb_size_request(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon *gc;

   gc = data;
   if (gc->resize_request.func)
     {
        Evas_Coord w = 0, h = 0;

        e_gadcon_layout_asked_size_get(gc->o_container, &w, &h);
        gc->resize_request.func(gc->resize_request.data, gc, w, h);
     }
}

static void
_e_gadcon_cb_show(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon *gc = data;
   Eina_List *l;
   E_Gadcon_Client *gcc;

   EINA_LIST_FOREACH(gc->clients, l, gcc)
     evas_object_show(gcc->o_base ?: gcc->o_frame);
}

static void
_e_gadcon_cb_hide(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon *gc = data;
   Eina_List *l;
   E_Gadcon_Client *gcc;

   EINA_LIST_FOREACH(gc->clients, l, gcc)
     evas_object_hide(gcc->o_base ?: gcc->o_frame);
}

static void
_e_gadcon_cb_moveresize(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon *gc;
   Evas_Coord x, y, w, h;

   gc = data;
   evas_object_geometry_get(gc->o_container, &x, &y, &w, &h);
   if (gc->drop_handler)
     e_drop_handler_geometry_set(gc->drop_handler, x, y, w, h);
}

static void
_e_gadcon_cb_client_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Gadcon_Client *gcc;

   gcc = data;
   ev = event_info;
   if (ev->button == 3)
     {
        E_Zone *zone;
        E_Menu *mn;
        E_Menu_Item *mi;
        int cx, cy;

        zone = e_zone_current_get();

        e_gadcon_locked_set(gcc->gadcon, 1);
        mn = e_menu_new();
        e_menu_post_deactivate_callback_set(mn, _e_gadcon_client_cb_menu_post,
                                            gcc);
        gcc->menu = mn;

        mi = e_menu_item_new(mn);
        e_menu_item_label_set(mi, _("Stop moving"));
        e_util_menu_item_theme_icon_set(mi, "enlightenment/edit");
        e_menu_item_callback_set(mi, _e_gadcon_client_cb_menu_edit, gcc);

        if (gcc->gadcon->menu_attach.func)
          {
             mi = e_menu_item_new(mn);
             e_menu_item_separator_set(mi, 1);

             gcc->gadcon->menu_attach.func(gcc->gadcon->menu_attach.data,
                                           gcc, mn);
             if (gcc->gadcon->shelf) e_shelf_locked_set(gcc->gadcon->shelf, 0);
          }

        if (gcc->gadcon->toolbar)
          ecore_evas_pointer_xy_get(e_comp->ee, &cx, &cy);
        else
          {
             e_gadcon_canvas_zone_geometry_get(gcc->gadcon, &cx, &cy, NULL, NULL);
             cx = cx + ev->output.x;
             cy = cy + ev->output.y;
          }
        e_menu_activate_mouse(mn, zone, cx, cy, 1, 1,
                              E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
     }
}

static void
_e_gadcon_cb_client_mouse_in(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   edje_object_signal_emit(gcc->o_control, "e,state,focused", "e");
}

static void
_e_gadcon_cb_client_mouse_out(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   edje_object_signal_emit(gcc->o_control, "e,state,unfocused", "e");
}

static void
_e_gadcon_cb_client_move(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   Evas_Coord x, y;

   gcc = data;
   evas_object_geometry_get(obj, &x, &y, NULL, NULL);
   if (gcc->o_control) evas_object_move(gcc->o_control, x, y);
   if (gcc->o_event) evas_object_move(gcc->o_event, x, y);
}

static void
_e_gadcon_cb_client_resize(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   Evas_Coord w, h;

   gcc = data;
   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   if (gcc->o_control) evas_object_resize(gcc->o_control, w, h);
   if (gcc->o_event) evas_object_resize(gcc->o_event, w, h);
   _e_gadcon_client_scroll_state_update(gcc);
}

static void
_e_gadcon_client_move_start(E_Gadcon_Client *gcc)
{
   int x, y, gy, gx;

   evas_object_raise(gcc->o_event);
   evas_object_stack_below(gcc->o_control, gcc->o_event);
   gcc->moving = 1;
   evas_object_geometry_get(gcc->gadcon->o_container, &gx, &gy, NULL, NULL);
   if (gcc->gadcon->toolbar)
     evas_pointer_canvas_xy_get(gcc->gadcon->evas, &gcc->dx, &gcc->dy);
   else
     {
        ecore_evas_pointer_xy_get(e_comp->ee, &gcc->dx, &gcc->dy);
        gcc->dx -= gx;
        gcc->dy -= gy;
     }

   if (gcc->o_frame)
     evas_object_geometry_get(gcc->o_frame, &x, &y, NULL, NULL);
   else if (gcc->o_base)
     evas_object_geometry_get(gcc->o_base, &x, &y, NULL, NULL);
   else
     return;

   /* using drag pos to calc offset between pointer and gcc pos */
   gcc->drag.x = x - (gcc->dx + gx);
   gcc->drag.y = y - (gcc->dy + gy);

   gcc->state_info.resist = 0;
}

static void
_e_gadcon_client_move_stop(E_Gadcon_Client *gcc)
{
   gcc->moving = 0;
   gcc->state_info.state = E_LAYOUT_ITEM_STATE_NONE;
   gcc->state_info.resist = 0;
   _e_gadcon_layout_smart_sync_clients(gcc->gadcon);
}

static void
_e_gadcon_client_move_go(E_Gadcon_Client *gcc)
{
   Evas_Coord x, y, w, h;
   int cx, cy;
   int gx, gy, gw, gh;
   int changes = 0;

   if (!gcc->moving) return;
   if (gcc->gadcon->toolbar)
     evas_pointer_canvas_xy_get(gcc->gadcon->evas, &cx, &cy);
   else
     ecore_evas_pointer_xy_get(e_comp->ee, &cx, &cy);

   evas_object_geometry_get(gcc->gadcon->o_container, &gx, &gy, &gw, &gh);

   cx -= gx;
   cy -= gy;

   x = cx - gcc->dx;
   y = cy - gcc->dy;

   gcc->state_info.flags = E_GADCON_LAYOUT_ITEM_LOCK_POSITION | E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
   _e_gadcon_client_current_position_sync(gcc);

   if ((cy + e_config->drag_resist < 0 || cy - e_config->drag_resist >= gh) ||
       (cx + e_config->drag_resist < 0 || cx - e_config->drag_resist > gw))
     {
        _e_gadcon_client_drag_begin(gcc, cx, cy);
        return;
     }

   if (gcc->o_frame)
     evas_object_geometry_get(gcc->o_frame, NULL, NULL, &w, &h);
   else if (gcc->o_base)
     evas_object_geometry_get(gcc->o_base, NULL, NULL, &w, &h);
   else
     return;  /* make clang happy */

   if (e_gadcon_layout_orientation_get(gcc->gadcon->o_container))
     {
        /* DRAG RIGHT */
        if (x > 0 && (cx + gcc->drag.x > gcc->config.pos))
          {
             if (gcc->state_info.state != E_LAYOUT_ITEM_STATE_POS_INC)
               gcc->state_info.resist = 0;
             gcc->state_info.state = E_LAYOUT_ITEM_STATE_POS_INC;
             changes = 1;
          }
        /* DRAG LEFT */
        else if (x < 0 && (cx + gcc->drag.x < gcc->config.pos))
          {
             if (gcc->state_info.state != E_LAYOUT_ITEM_STATE_POS_DEC)
               gcc->state_info.resist = 0;
             gcc->state_info.state = E_LAYOUT_ITEM_STATE_POS_DEC;
             changes = 1;
          }

        if (changes)
          {
             if (gcc->o_frame)
               e_gadcon_layout_pack_request_set(gcc->o_frame, cx + gcc->drag.x, w);
             else if (gcc->o_base)
               e_gadcon_layout_pack_request_set(gcc->o_base, cx + gcc->drag.x, w);

             gcc->config.size = w;
             evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
             gcc->config.res = w;
          }
     }
   else
     {
        /* DRAG DOWN */
        if (y > 0 && (cy + gcc->drag.y > gcc->config.pos))
          {
             if (gcc->state_info.state != E_LAYOUT_ITEM_STATE_POS_INC)
               gcc->state_info.resist = 0;
             gcc->state_info.state = E_LAYOUT_ITEM_STATE_POS_INC;
             changes = 1;
          }
        /* DRAG UP */
        else if (y < 0 && (cy + gcc->drag.y < gcc->config.pos))
          {
             if (gcc->state_info.state != E_LAYOUT_ITEM_STATE_POS_DEC)
               gcc->state_info.resist = 0;
             gcc->state_info.state = E_LAYOUT_ITEM_STATE_POS_DEC;
             changes = 1;
          }

        if (changes)
          {
             if (gcc->o_frame)
               e_gadcon_layout_pack_request_set(gcc->o_frame, cy + gcc->drag.y, h);
             else if (gcc->o_base)
               e_gadcon_layout_pack_request_set(gcc->o_base, cy + gcc->drag.y, h);

             gcc->config.size = h;
             evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
             gcc->config.res = h;
          }
     }

   gcc->dx += x;
   gcc->dy += y;
}

static void
_e_gadcon_cb_signal_move_start(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadcon_client_move_start(data);
}

static void
_e_gadcon_cb_signal_move_stop(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadcon_client_move_stop(data);
}

static void
_e_gadcon_cb_signal_move_go(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadcon_client_move_go(data);
}

static void
_e_gadcon_client_resize_start(E_Gadcon_Client *gcc)
{
   evas_object_raise(gcc->o_event);
   evas_object_stack_below(gcc->o_control, gcc->o_event);
   gcc->resizing = 1;
   evas_pointer_canvas_xy_get(gcc->gadcon->evas, &gcc->dx, &gcc->dy);
}

static void
_e_gadconclient_resize_stop(E_Gadcon_Client *gcc)
{
   gcc->resizing = 0;
   gcc->state_info.state = E_LAYOUT_ITEM_STATE_NONE;
   _e_gadcon_layout_smart_sync_clients(gcc->gadcon);
   _e_gadcon_client_save(gcc);
}

static void
_e_gadcon_cb_signal_resize_left_start(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadcon_client_resize_start(data);
}

static void
_e_gadcon_cb_signal_resize_left_stop(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadconclient_resize_stop(data);
}

static void
_e_gadcon_cb_signal_resize_left_go(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   Evas_Coord x, y, w, h;

   gcc = data;
   if (!gcc->resizing) return;
   evas_pointer_canvas_xy_get(gcc->gadcon->evas, &x, &y);
   x = x - gcc->dx;
   y = y - gcc->dy;

   gcc->state_info.flags = E_GADCON_LAYOUT_ITEM_LOCK_POSITION |
     E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;

   if (gcc->o_frame)
     evas_object_geometry_get(gcc->o_frame, NULL, NULL, &w, &h);
   else if (gcc->o_base)
     evas_object_geometry_get(gcc->o_base, NULL, NULL, &w, &h);
   else return;  /* make clang happy */

   _e_gadcon_client_current_position_sync(gcc);

   if (e_gadcon_layout_orientation_get(gcc->gadcon->o_container))
     {
        if (x > 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MIN_END_INC;
        else if (x < 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MIN_END_DEC;
     }
   else
     {
        if (y > 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MIN_END_INC;
        else if (y < 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MIN_END_DEC;
     }

   if (e_gadcon_layout_orientation_get(gcc->gadcon->o_container))
     {
        if (gcc->o_frame)
          e_gadcon_layout_pack_request_set(gcc->o_frame, gcc->config.pos + x, w - x);
        else if (gcc->o_base)
          e_gadcon_layout_pack_request_set(gcc->o_base, gcc->config.pos + x, w - x);
        evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
        gcc->config.res = w;
     }
   else
     {
        if (gcc->o_frame)
          e_gadcon_layout_pack_request_set(gcc->o_frame, gcc->config.pos + y, h - y);
        else if (gcc->o_base)
          e_gadcon_layout_pack_request_set(gcc->o_base, gcc->config.pos + y, h - y);
        evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
        gcc->config.res = h;
     }
   gcc->dx += x;
   gcc->dy += y;
}

static void
_e_gadcon_cb_signal_resize_right_start(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadcon_client_resize_start(data);
}

static void
_e_gadcon_cb_signal_resize_right_stop(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_gadconclient_resize_stop(data);
}

static void
_e_gadcon_cb_signal_resize_right_go(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   Evas_Coord x, y, w, h;

   gcc = data;
   if (!gcc->resizing) return;
   evas_pointer_canvas_xy_get(gcc->gadcon->evas, &x, &y);
   x = x - gcc->dx;
   y = y - gcc->dy;

   gcc->state_info.flags = E_GADCON_LAYOUT_ITEM_LOCK_POSITION |
     E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;

   if (gcc->o_frame)
     evas_object_geometry_get(gcc->o_frame, NULL, NULL, &w, &h);
   else if (gcc->o_base)
     evas_object_geometry_get(gcc->o_base, NULL, NULL, &w, &h);
   else return;  /* make clang happy */

   _e_gadcon_client_current_position_sync(gcc);

   if (e_gadcon_layout_orientation_get(gcc->gadcon->o_container))
     {
        if (x > 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC;
        else if (x < 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MAX_END_DEC;
     }
   else
     {
        if (y > 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC;
        else if (y < 0)
          gcc->state_info.state = E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC;
     }

   if (e_gadcon_layout_orientation_get(gcc->gadcon->o_container))
     {
        if (gcc->o_frame)
          e_gadcon_layout_pack_request_set(gcc->o_frame, gcc->config.pos, w + x);
        else if (gcc->o_base)
          e_gadcon_layout_pack_request_set(gcc->o_base, gcc->config.pos, w + x);
        evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
        gcc->config.res = w;
     }
   else
     {
        if (gcc->o_frame)
          e_gadcon_layout_pack_request_set(gcc->o_frame, gcc->config.pos, h + y);
        else if (gcc->o_base)
          e_gadcon_layout_pack_request_set(gcc->o_base, gcc->config.pos, h + y);
        evas_object_geometry_get(gcc->gadcon->o_container, NULL, NULL, &w, &h);
        gcc->config.res = h;
     }
   gcc->dx += x;
   gcc->dy += y;
}

static void
_e_gadcon_cb_dnd_enter(void *data, const char *type EINA_UNUSED, void *event)
{
   E_Event_Dnd_Enter *ev;
   E_Gadcon *gc;
   E_Gadcon_Client *gcc;

   ev = event;
   gc = data;
   //INF("DND ENTER");
   gcc = gc->drag_gcc;
   if (!gcc) return;  // dnd from efm or something
   if ((!gcc->hidden) && (gcc->gadcon == gc))
     {
        if (gc->dnd_enter_cb) gc->dnd_enter_cb(gc, gc->drag_gcc);
        e_drag_hide(gc->drag_gcc->drag.drag);
        return;
     }
   if (gcc->gadcon != gc)
     e_gadcon_client_hide(gc->drag_gcc);
   else if (e_gadcon_site_is_desktop(gcc->gadcon->location->site))
     {
        e_gadcon_client_show(gc->drag_gcc);
        e_drag_hide(gc->drag_gcc->drag.drag);
        if (gc->dnd_enter_cb) gc->dnd_enter_cb(gc, gc->drag_gcc);
        return;
     }
   e_gadcon_layout_freeze(gc->o_container);

   if (gc->new_gcc)
     {
        e_object_del(E_OBJECT(gc->new_gcc));
        gc->new_gcc = NULL;
     }

   while (ev->data)
     {
        /* Create a new gadcon to show where the gadcon will end up */
        E_Gadcon_Client_Class *cc;

        gcc = ev->data;
        cc = eina_hash_find(providers, gcc->name);
        if (!cc) break;
        if (!gcc->style)
          gc->new_gcc = cc->func.init(gc, gcc->name, gcc->cf->id,
                                      cc->default_style);
        else
          gc->new_gcc = cc->func.init(gc, gcc->name, gcc->cf->id,
                                      gcc->style);

        if (!gc->new_gcc) break;
        gc->new_gcc->cf = gcc->cf;
        gc->new_gcc->client_class = cc;
        gc->new_gcc->config.pos = gcc->config.pos;
        gc->new_gcc->config.size = gcc->config.size;
        gc->new_gcc->config.res = gcc->config.res;
        gc->new_gcc->state_info.seq = gcc->state_info.seq;
        gc->new_gcc->state_info.flags = gcc->state_info.flags;
        gc->new_gcc->config.pos_x = gcc->config.pos_x;
        gc->new_gcc->config.pos_y = gcc->config.pos_y;
        gc->new_gcc->config.size_w = gcc->config.size_w;
        gc->new_gcc->config.size_h = gcc->config.size_h;
        if (gc->new_gcc->client_class->func.orient)
          {
             if (gc->orient == E_GADCON_ORIENT_FLOAT)
               {
                  gc->new_gcc->client_class->func.orient(gc->new_gcc, gcc->gadcon->orient);
                  gc->new_gcc->cf->orient = gcc->gadcon->orient;
               }
             else
               {
                  gc->new_gcc->client_class->func.orient(gc->new_gcc, gc->orient);
                  gc->new_gcc->cf->orient = gc->orient;
               }
          }
        if (gc->new_gcc->o_frame)
          e_gadcon_layout_pack_options_set(gc->new_gcc->o_frame, gc->new_gcc);
        else if (gc->new_gcc->o_base)
          {
             e_gadcon_layout_pack_options_set(gc->new_gcc->o_base, gc->new_gcc);
             if (!gc->o_container)
               {
                  int w, h, gw, gh;

                  w = gc->zone->w;
                  h = gc->zone->h;
                  if ((!EINA_DBL_NONZERO(gc->new_gcc->config.pos_x)) && (!EINA_DBL_NONZERO(gc->new_gcc->config.pos_y)))
                    {
                       gc->new_gcc->config.pos_x = (double)ev->x / (double)w;
                       gc->new_gcc->config.pos_y = (double)ev->y / (double)h;
                    }
                  if ((!EINA_DBL_NONZERO(gc->new_gcc->config.size_w)) && (!EINA_DBL_NONZERO(gc->new_gcc->config.size_h)))
                    {
                       evas_object_geometry_get(gcc->o_frame ? : gcc->o_base, NULL, NULL, &gw, &gh);
                       gc->new_gcc->config.size_w = (double)gw / (double)w;
                       gc->new_gcc->config.size_h = (double)gh / (double)h;
                    }
                  else
                    {
                       gw = gc->new_gcc->config.size_w * w;
                       gh = gc->new_gcc->config.size_h * h;
                    }
                  evas_object_resize(gc->new_gcc->o_base, gw, gh);
                  evas_object_move(gc->new_gcc->o_base, ev->x, ev->y);
               }
          }

        e_drag_hide(gc->drag_gcc->drag.drag);
        e_gadcon_client_edit_begin(gc->new_gcc);
        e_gadcon_client_autoscroll_set(gc->new_gcc, gcc->autoscroll);
/*		  e_gadcon_client_resizable_set(gc->new_gcc, gcc->resizable);*/
        gc->new_gcc->state_info.resist = 1;
        if (gc->instant_edit)
          e_gadcon_client_util_menu_attach(gc->new_gcc);
        break;
     }
   e_gadcon_layout_thaw(gc->o_container);
   if (gc->dnd_enter_cb) gc->dnd_enter_cb(gc, gc->drag_gcc);
}

static void
_e_gadcon_cb_dnd_move(void *data, const char *type EINA_UNUSED, void *event)
{
   E_Event_Dnd_Move *ev;
   E_Gadcon *gc;
   E_Gadcon_Client *gcc = NULL;
   int dx = 0, dy = 0;
   Evas_Object *o;

   ev = event;
   gc = data;
   //INF("DND (%d,%d)", ev->x, ev->y);
   gcc = gc->new_gcc ? : gc->drag_gcc;
   if (!gcc) return;

   if (gcc->state_info.resist > 0)
     {
        gcc->state_info.resist--;
        return;
     }
   e_gadcon_layout_freeze(gc->o_container);

   if (e_gadcon_layout_orientation_get(gc->o_container))
     gcc->config.pos = ev->x - gcc->config.size / 2;
   else
     gcc->config.pos = ev->y - gcc->config.size / 2;
   if (!gcc->gadcon->o_container)
     {
        int w, h;
        w = gc->zone->w;
        h = gc->zone->h;
        gcc->config.pos_x = (double)ev->x / (double)w;
        gcc->config.pos_y = (double)ev->y / (double)h;
        if (gcc->o_frame) evas_object_move(gcc->o_frame, ev->x, ev->y);
        else if (gcc->o_base)
          evas_object_move(gcc->o_base, ev->x, ev->y);
     }
   _e_gadcon_client_inject(gc, gcc, ev->x + dx, ev->y + dy);

   o = gcc->o_frame ? gcc->o_frame : gcc->o_base;
   if (o)
     e_gadcon_layout_pack_request_set(o, gcc->config.pos,
                                      gcc->config.size);
   e_gadcon_layout_thaw(gc->o_container);
   if (gc->dnd_move_cb) gc->dnd_move_cb(gc, gcc);
}

static void
_e_gadcon_cb_dnd_leave(void *data, const char *type EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Gadcon *gc;

   gc = data;
   //INF("DND LEAVE");

   /* If we exit the starting container hide the gadcon visual */
   if (gc->drag_gcc->gadcon == gc) e_gadcon_client_hide(gc->drag_gcc);

   /* Delete temporary object */
   if (!gc->new_gcc)
     {
        if (gc->dnd_leave_cb) gc->dnd_leave_cb(gc, gc->drag_gcc);
        return;
     }

   //INF("DELETING new_gcc");
   e_object_del(E_OBJECT(gc->new_gcc));
   gc->new_gcc = NULL;
   e_drag_show(gc->drag_gcc->drag.drag);
   if (gc->dnd_leave_cb) gc->dnd_leave_cb(gc, gc->drag_gcc);
}

static void
_e_gadcon_cb_dnd_drop(void *data, const char *type EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Gadcon *gc;
   E_Gadcon_Client *gcc = NULL;

   gc = data;
   //INF("DND DROP");
   gc->cf->clients = eina_list_append(gc->cf->clients, gc->drag_gcc->cf);
   if (!gc->new_gcc)
     {
        /* using drag_gcc, so do things a bit differently
         * this only happens with gadman dnd, broken on shelves
         */
        _e_gadcon_client_save(gc->drag_gcc);
        e_gadcon_client_show(gc->drag_gcc);
        if (gc->dnd_drop_cb) gc->dnd_drop_cb(gc, gc->drag_gcc);
        return;
     }

   gcc = gc->new_gcc;

   if (!gc->o_container)
     {
        /* FIXME: gadman sucks and should probably use a regular gadcon layout, but it doesn't
         * so we need to repop here
         */
        gcc->cf = gc->drag_gcc->cf;
        _e_gadcon_client_save(gc->new_gcc);
        e_object_del(E_OBJECT(gc->new_gcc));
        gc->new_gcc = NULL;
        /* this little hack prevents the dead gcc from overwriting our config */
        gc->drag_gcc->moving = 0;
        e_gadcon_custom_populate_request(gc);
        e_config_save_queue();
        if (gc->dnd_drop_cb) gc->dnd_drop_cb(gc, gc->drag_gcc);
        goto cleanup;
     }
   if (gc->editing) e_gadcon_client_edit_begin(gc->new_gcc);
   gc->new_gcc = NULL;
   e_config_save_queue();
   if (gc->dnd_drop_cb) gc->dnd_drop_cb(gc, gc->drag_gcc);

cleanup:
   /* still has refcount from drag */
   e_object_del(E_OBJECT(gc->drag_gcc));
   gc->drag_gcc = NULL;
}

static int
_e_gadcon_client_class_feature_check(const E_Gadcon_Client_Class *cc, const char *name, void *feature)
{
   if (!feature)
     {
        e_util_dialog_show(_("Insufficient gadcon support"),
                           _("Module %s needs to support %s"),
                           cc->name, name);
        return 0;
     }
   return 1;
}

static void
_e_gadcon_client_cb_menu_post(void *data, E_Menu *m)
{
   E_Gadcon_Client *gcc;

   if (!(gcc = data)) return;
   if (gcc->gadcon) e_gadcon_locked_set(gcc->gadcon, 0);
   if (gcc->menu != m)
     {
        e_object_del(E_OBJECT(m));
        return;
     }
   if (gcc->gadcon && gcc->gadcon->shelf && (gcc->menu == gcc->gadcon->shelf->menu)) gcc->gadcon->shelf->menu = NULL;
   e_object_del(E_OBJECT(gcc->menu));
   gcc->menu = NULL;
}

static Eina_Bool
_e_gadcon_client_cb_instant_edit_timer(void *data)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   e_gadcon_client_edit_begin(gcc);
   _e_gadcon_client_move_start(gcc);
   gcc->instant_edit_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_gadcon_client_cb_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Gadcon_Client *gcc;

   ev = event_info;
   gcc = data;
   if (gcc->menu) return;
   if (ev->button == 3)
     {
        E_Menu *m;
        E_Zone *zone;
        int cx, cy, cw, ch;

        e_gadcon_locked_set(gcc->gadcon, 1);
        m = e_menu_new();

        m = e_gadcon_client_util_menu_items_append(gcc, m, 0);
        e_menu_post_deactivate_callback_set(m, _e_gadcon_client_cb_menu_post,
                                            gcc);
        gcc->menu = m;
        if (gcc->gadcon->shelf) gcc->gadcon->shelf->menu = m;

        e_gadcon_canvas_zone_geometry_get(gcc->gadcon, &cx, &cy, &cw, &ch);
        zone = gcc->gadcon->zone;
        if (!zone) zone = e_zone_current_get();
        e_menu_activate_mouse(m, zone,
                              cx + ev->output.x,
                              cy + ev->output.y, 1, 1,
                              E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
     }
   else if (ev->button == 2)
     {
        if (gcc->instant_edit_timer)
          ecore_timer_del(gcc->instant_edit_timer);
        gcc->instant_edit_timer = NULL;
        e_gadcon_client_edit_begin(gcc);
        _e_gadcon_client_move_start(gcc);
     }
   else if (ev->button == 1)
     {
        if ((!gcc->o_control) && (gcc->gadcon->instant_edit))
          {
             if (gcc->instant_edit_timer)
               ecore_timer_del(gcc->instant_edit_timer);
             gcc->instant_edit_timer =
               ecore_timer_loop_add(1.0, _e_gadcon_client_cb_instant_edit_timer,
                               gcc);
          }
     }
}

static void
_e_gadcon_client_cb_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Up *ev;
   E_Gadcon_Client *gcc;

   ev = event_info;
   gcc = data;
   if ((ev->button == 1) && (gcc->gadcon->instant_edit))
     {
        if (gcc->instant_edit_timer)
          {
             ecore_timer_del(gcc->instant_edit_timer);
             gcc->instant_edit_timer = NULL;
          }
        if (gcc->o_control)
          {
             _e_gadcon_client_move_stop(gcc);
             e_gadcon_client_edit_end(gcc);
          }
     }
   else if (ev->button == 2)
     {
        if (gcc->o_control)
          {
             _e_gadcon_client_move_stop(gcc);
             e_gadcon_client_edit_end(gcc);
          }
     }
}

static void
_e_gadcon_client_cb_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   if ((gcc->gadcon->instant_edit))
     {
        if (gcc->o_control) _e_gadcon_client_move_go(gcc);
     }
}

static void
_e_gadcon_client_cb_menu_style_plain(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   E_Gadcon *gc;

   gcc = data;
   gc = gcc->gadcon;
   eina_stringshare_replace(&gcc->style, E_GADCON_CLIENT_STYLE_PLAIN);
   _e_gadcon_client_save(gcc);
   e_gadcon_unpopulate(gc);
   e_gadcon_populate(gc);
}

static void
_e_gadcon_client_cb_menu_style_inset(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Gadcon_Client *gcc;
   E_Gadcon *gc;

   gcc = data;
   gc = gcc->gadcon;
   eina_stringshare_replace(&gcc->style, E_GADCON_CLIENT_STYLE_INSET);
   _e_gadcon_client_save(gcc);
   e_gadcon_unpopulate(gc);
   e_gadcon_populate(gc);
}

static void
_e_gadcon_client_cb_menu_autoscroll(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   e_gadcon_layout_freeze(gcc->gadcon->o_container);
   if (gcc->autoscroll) gcc->autoscroll = 0;
   else gcc->autoscroll = 1;
   e_gadcon_client_autoscroll_set(gcc, gcc->autoscroll);
   _e_gadcon_client_save(gcc);
   e_gadcon_layout_thaw(gcc->gadcon->o_container);
   if (gcc->autoscroll)
     _e_gadcon_client_scroll_state_update(gcc);
}

/*
   static void
   _e_gadcon_client_cb_menu_resizable(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
   {
   E_Gadcon_Client *gcc;

   gcc = data;
   e_gadcon_layout_freeze(gcc->gadcon->o_container);
   if (gcc->resizable) gcc->resizable = 0;
   else gcc->resizable = 1;
   e_gadcon_client_resizable_set(gcc, gcc->resizable);
   _e_gadcon_client_save(gcc);
   e_gadcon_layout_thaw(gcc->gadcon->o_container);
   }
 */
static void
_e_gadcon_client_cb_menu_edit(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   if (gcc->o_control)
     e_gadcon_client_edit_end(gcc);
   else
     e_gadcon_client_edit_begin(gcc);
}

static void
_e_gadcon_client_cb_menu_remove(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Gadcon *gc;
   E_Gadcon_Client *gcc;

   gcc = data;
   gc = gcc->gadcon;

   e_gadcon_client_config_del(gc->cf, gcc->cf);
   gcc->cf = NULL;
   e_object_del(E_OBJECT(gcc));
   if (!gc->custom)
     {
        e_gadcon_unpopulate(gc);
        e_gadcon_populate(gc);
     }
   e_config_save_queue();
}

static void
_e_gadcon_client_cb_menu_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Gadcon_Client *gcc;

   if (!(gcc = data)) return;
   if (gcc->client_class->func.icon)
     {
        // e menu ASSUMES... EXPECTS the icon to be an.... e_icon!
        // if it's not, spankies for whoever wrote the icon callback!
        mi->icon_object =
          gcc->client_class->func.icon((E_Gadcon_Client_Class *)gcc->client_class,
                                       mi->menu->evas);
     }
   else
     e_util_menu_item_theme_icon_set(mi, "preferences-gadget");  // FIXME: Needs icon in theme
}

static void
_e_gadcon_client_del_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Gadcon_Client *gcc;

   gcc = data;
   gcc->o_base = NULL;
   e_object_del(E_OBJECT(gcc));
}

/* a smart object JUST for gadcon */

typedef struct _E_Gadcon_Layout_Item E_Gadcon_Layout_Item;

struct _E_Smart_Data
{
   Evas_Coord    x, y, w, h;
   Evas_Object  *obj, *clip;
   Eina_List    *items;
   Ecore_Job    *job;
   int           frozen;
   Evas_Coord    minw, minh, req;
   unsigned char horizontal E_BITFIELD;
   unsigned char doing_config E_BITFIELD;
   unsigned char redo_config E_BITFIELD;
};

struct _E_Gadcon_Layout_Item
{
   E_Smart_Data    *sd;
   struct
   {
      int pos, size, size2, res, prev_pos, prev_size;
   } ask;
   int              hookp;
   struct
   {
      int w, h;
   } min, aspect, aspect_pad;

   E_Gadcon_Client *gcc;

   Evas_Coord       x, y, w, h;
   Evas_Object     *obj;
   unsigned char    can_move E_BITFIELD;
};

/* local subsystem functions */
static E_Gadcon_Layout_Item *_e_gadcon_layout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj);
static void                  _e_gadcon_layout_smart_disown(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_item_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void                  _e_gadcon_layout_smart_reconfigure(E_Smart_Data *sd);
static void                  _e_gadcon_layout_smart_init(void);
static void                  _e_gadcon_layout_smart_add(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_del(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void                  _e_gadcon_layout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void                  _e_gadcon_layout_smart_show(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_hide(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void                  _e_gadcon_layout_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void                  _e_gadcon_layout_smart_clip_unset(Evas_Object *obj);
static void                  _e_gadcon_layout_smart_min_cur_size_calc(E_Smart_Data *sd, int *min, int *mino, int *cur);
static void                  _e_gadcon_layout_smart_gadcons_width_adjust(E_Smart_Data *sd, int min, int cur);
static int                   _e_gadcon_layout_smart_sort_by_sequence_number_cb(const void *d1, const void *d2);
static int                   _e_gadcon_layout_smart_sort_by_position_cb(const void *d1, const void *d2);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
static Evas_Object *
e_gadcon_layout_add(Evas *evas)
{
   _e_gadcon_layout_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

static void
e_gadcon_layout_orientation_set(Evas_Object *obj, int horizontal)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (((sd->horizontal) && (horizontal)) ||
       ((!sd->horizontal) && (!horizontal))) return;
   sd->horizontal = horizontal;
   _e_gadcon_layout_smart_reconfigure(sd);
}

static int
e_gadcon_layout_orientation_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return 0;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   return sd->horizontal;
}

static void
e_gadcon_layout_freeze(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   sd->frozen++;
}

static void
e_gadcon_layout_thaw(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   sd->frozen--;
   _e_gadcon_layout_smart_reconfigure(sd);
}

static void
e_gadcon_layout_min_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   E_Smart_Data *sd;
/*
   Eina_List *l;
   Evas_Object *obj;
   Evas_Coord tw = 0, th = 0;
 */
   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->horizontal)
     {
        if (w) *w = sd->minw;
        if (h) *h = sd->minh;
     }
   else
     {
        if (w) *w = sd->minh;
        if (h) *h = sd->minw;
     }

/*
   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        E_Gadcon_Layout_Item *bi;

        bi = evas_object_data_get(obj, "e_gadcon_layout_data");
        if (sd->horizontal)
          {
             tw += bi->min.w;
             if (bi->min.h > th) th = bi->min.h;
          }
        else
          {
             th += bi->min.h;
             if (bi->min.w > tw) tw = bi->min.w;
          }
     }
   if (w) *w = tw;
   if (h) *h = th;
 */
}

static void
e_gadcon_layout_asked_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   E_Smart_Data *sd;
   Evas_Coord tw = 0, th = 0;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->horizontal)
     tw = sd->req;
   else
     th = sd->req;
/*
   Evas_Object *obj;
   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        E_Gadcon_Layout_Item *bi;

        bi = evas_object_data_get(obj, "e_gadcon_layout_data");
        if (sd->horizontal)
          {
             tw += bi->ask.size;
          }
        else
          {
             th += bi->ask.size;
          }
     }
 */
   if (w) *w = tw;
   if (h) *h = th;
}

static int
e_gadcon_layout_pack(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;

   if (!obj) return 0;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return 0;
   _e_gadcon_layout_smart_adopt(sd, child);
   sd->items = eina_list_prepend(sd->items, child);
   _e_gadcon_layout_smart_reconfigure(sd);
   return 0;
}

static void
e_gadcon_layout_pack_size_set(Evas_Object *obj, int size)
{
   /*
    * FIXME:
    * simplify this function until the is redone
    * _e_gadcon_layout_smart_gadcons_asked_position_set(E_Smart_Data *sd)
    */
   E_Gadcon_Layout_Item *bi;
   int pos;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi) return;
   pos = bi->ask.pos + (bi->ask.size / 2);
   if (pos < (bi->ask.res / 3))
     {
        /* hooked to start */
        bi->ask.size = size;
     }
   else if (pos > ((2 * bi->ask.res) / 3))
     {
        /* hooked to end */
        bi->ask.pos = (bi->ask.pos + bi->ask.size) - size;
        bi->ask.size = size;
     }
   else
     {
        /* hooked to middle */
        if ((bi->ask.pos <= (bi->ask.res / 2)) &&
            ((bi->ask.pos + bi->ask.size) > (bi->ask.res / 2)))
          {
             /* straddles middle */
             if (bi->ask.res > 2)
               bi->ask.pos = (bi->ask.res / 2) +
                 (((bi->ask.pos + (bi->ask.size / 2) -
                    (bi->ask.res / 2)) *
                   (bi->ask.res / 2)) /
                  (bi->ask.res / 2)) - (bi->ask.size / 2);
             else
               bi->x = bi->ask.res / 2;
             bi->ask.size = size;
          }
        else
          {
             if (pos < (bi->ask.res / 2))
               {
                  bi->ask.pos = (bi->ask.pos + bi->ask.size) - size;
                  bi->ask.size = size;
               }
             else
               bi->ask.size = size;
          }
        bi->ask.size = size;
     }
   _e_gadcon_layout_smart_reconfigure(bi->sd);
}

/* called when a users moves/resizes the gadcon client explicitly */
static void
e_gadcon_layout_pack_request_set(Evas_Object *obj, int pos, int size)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi) return;

   bi->ask.res = bi->sd->w;
   if (pos < 0) pos = 0;
   if ((bi->ask.res - pos) < size) pos = bi->ask.res - size;
   bi->ask.size = size;
   bi->ask.pos = pos;
   _e_gadcon_layout_smart_reconfigure(bi->sd);
}

/* called when restoring config from saved config */
static void
e_gadcon_layout_pack_options_set(Evas_Object *obj, E_Gadcon_Client *gcc)
{
   int ok, seq;
   Eina_List *l;
   Evas_Object *item;
   E_Gadcon_Layout_Item *bi, *bi2;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi) return;
   bi->ask.res = gcc->config.res;
   bi->ask.size = gcc->config.size;
   bi->ask.pos = gcc->config.pos;
   bi->gcc = gcc;

   ok = 0;
   if (!gcc->state_info.seq) ok = 1;

   seq = 1;
   EINA_LIST_FOREACH(bi->sd->items, l, item)
     {
        bi2 = evas_object_data_get(item, "e_gadcon_layout_data");
        if (bi == bi2) continue;
        if (bi->gcc->id == bi2->gcc->id) continue;
        if (bi->gcc->state_info.seq == bi2->gcc->state_info.seq)
          ok = 1;

        if (bi2->gcc->state_info.seq > seq)
          seq = bi2->gcc->state_info.seq;
     }

   if (ok)
     {
        gcc->state_info.seq = seq + 1;
        gcc->state_info.want_save = 1;
        gcc->state_info.flags = E_GADCON_LAYOUT_ITEM_LOCK_NONE;
     }
   _e_gadcon_layout_smart_reconfigure(bi->sd);
}

static void
e_gadcon_layout_pack_min_size_set(Evas_Object *obj, int w, int h)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi)
     {
        evas_object_size_hint_min_set(obj, w, h);
        evas_object_size_hint_min_set(obj, w, h);
        return;
     }
   if (bi->sd->horizontal)
     {
        bi->min.w = w;
        bi->min.h = h;
     }
   else
     {
        bi->min.w = h;
        bi->min.h = w;
     }

   _e_gadcon_layout_smart_reconfigure(bi->sd);
}

static void
e_gadcon_layout_pack_aspect_set(Evas_Object *obj, int w, int h)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi)
     {
        evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_BOTH, w, h);
        return;
     }
   if (bi->sd->horizontal)
     {
        bi->aspect.w = w;
        bi->aspect.h = h;
     }
   else
     {
        bi->aspect.w = h;
        bi->aspect.h = w;
     }

   _e_gadcon_layout_smart_reconfigure(bi->sd);
}

static void
e_gadcon_layout_pack_aspect_pad_set(Evas_Object *obj, int w, int h)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi) return;
   if (bi->sd->horizontal)
     {
        bi->aspect_pad.w = w;
        bi->aspect_pad.h = h;
     }
   else
     {
        bi->aspect_pad.w = h;
        bi->aspect_pad.h = w;
     }
}

static void
e_gadcon_layout_unpack(Evas_Object *obj)
{
   E_Gadcon_Layout_Item *bi;
   E_Smart_Data *sd;

   if (!obj) return;
   bi = evas_object_data_get(obj, "e_gadcon_layout_data");
   if (!bi) return;
   sd = bi->sd;
   if (!sd) return;
   sd->items = eina_list_remove(sd->items, obj);
   _e_gadcon_layout_smart_disown(obj);
   if (!stopping)
     _e_gadcon_layout_smart_reconfigure(sd);
}

/* local subsystem functions */
static E_Gadcon_Layout_Item *
_e_gadcon_layout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return NULL;
   bi = E_NEW(E_Gadcon_Layout_Item, 1);
   if (!bi) return NULL;
   bi->sd = sd;
   bi->obj = obj;
   /* defaults */
   evas_object_clip_set(obj, sd->clip);
   evas_object_smart_member_add(obj, bi->sd->obj);
   evas_object_data_set(obj, "e_gadcon_layout_data", bi);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
                                  _e_gadcon_layout_smart_item_del_hook, NULL);
   if ((!evas_object_visible_get(sd->clip)) &&
       (evas_object_visible_get(sd->obj)))
     evas_object_show(sd->clip);
   return bi;
}

static void
_e_gadcon_layout_smart_disown(Evas_Object *obj)
{
   E_Gadcon_Layout_Item *bi;

   if (!obj) return;
   bi = evas_object_data_del(obj, "e_gadcon_layout_data");
   if (!bi) return;
   if (!bi->sd->items)
     {
        if (evas_object_visible_get(bi->sd->clip))
          evas_object_hide(bi->sd->clip);
     }
   evas_object_event_callback_del(obj, EVAS_CALLBACK_DEL,
                                  _e_gadcon_layout_smart_item_del_hook);
   evas_object_smart_member_del(obj);
   evas_object_clip_unset(obj);
   free(bi);
}

static void
_e_gadcon_layout_smart_item_del_hook(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   if (!obj) return;
   e_gadcon_layout_unpack(obj);
}

static void
_e_gadcon_layout_smart_reconfigure(E_Smart_Data *sd)
{
   Evas_Coord xx, yy;
   Eina_List *l;
   Evas_Object *obj;
   int min, mino, cur;
   Eina_List *list = NULL;
   E_Gadcon_Layout_Item *bi;
   E_Layout_Item_Container *lc;
   int i, set_prev_pos = 0;
   static int recurse = 0;

   if (sd->frozen) return;
   if (sd->doing_config)
     {
        sd->redo_config = 1;
        return;
     }

   recurse++;
   min = mino = cur = 0;

   _e_gadcon_layout_smart_min_cur_size_calc(sd, &min, &mino, &cur);

   if ((sd->minw != min) || (sd->minh != mino))
     {
        sd->minw = min;
        sd->minh = mino;
        evas_object_smart_callback_call(sd->obj, "min_size_request", NULL);
     }

   if (sd->req != cur)
     {
        if (cur >= sd->minw)
          {
             sd->req = cur;
             evas_object_smart_callback_call(sd->obj, "size_request", NULL);
          }
        else
          {
             sd->req = sd->minw;
          }
     }
   if (recurse == 1) _e_gadcon_layout_smart_gadcons_width_adjust(sd, min, cur);

   if (sd->w <= sd->req)
     {
        _e_gadcon_layout_smart_gadcon_position_shrinked_mode(sd);
        set_prev_pos = 0;
     }
   else
     {
        _e_gadcon_layout_smart_gadcons_asked_position_set(sd);

        list = _e_gadcon_layout_smart_gadcons_wrap(sd);

        _e_gadcon_layout_smart_gadcons_position(sd, &list);

        EINA_LIST_FREE(list, lc)
          LC_FREE(lc);

        set_prev_pos = 1;
     }

   sd->items = eina_list_sort(sd->items, eina_list_count(sd->items),
                              _e_gadcon_layout_smart_sort_by_position_cb);
   i = 1;
   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        bi = evas_object_data_get(obj, "e_gadcon_layout_data");
        if (bi->gcc->gadcon->editing) bi->gcc->state_info.seq = i;

        if (set_prev_pos)
          {
             bi->ask.prev_pos = bi->x;
             bi->ask.prev_size = bi->w;
          }

        if ((bi->x == bi->ask.pos) &&
            (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION))
          bi->gcc->state_info.flags |= E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;

        if ((bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION) &&
            (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE))
          {
             if (bi->x != bi->ask.pos)
               bi->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
          }
        i++;
     }

   EINA_LIST_FOREACH(sd->items, l, obj)
     {
        bi = evas_object_data_get(obj, "e_gadcon_layout_data");
        if (!bi) continue;

        bi->h = sd->h;
        xx = sd->x + bi->x;
        yy = sd->y; // + ((sd->h - bi->h) / 2);

        if (sd->horizontal)
          {
             evas_object_move(obj, xx, yy);
             evas_object_resize(obj, bi->w, bi->h);
          }
        else
          {
             evas_object_move(obj, yy, xx);
             evas_object_resize(obj, bi->h, bi->w);
          }
        if ((recurse == 1) && bi->gcc->state_info.want_save)
          {
             _e_gadcon_client_save(bi->gcc);
             bi->gcc->state_info.want_save = 0;
          }
     }
   sd->doing_config = 0;
   if (sd->redo_config)
     {
        _e_gadcon_layout_smart_reconfigure(sd);
        sd->redo_config = 0;
     }

   if ((sd->minw != min) || (sd->minh != mino))
     {
        sd->minw = min;
        sd->minh = mino;
        evas_object_smart_callback_call(sd->obj, "min_size_request", NULL);
     }

   if (sd->req != cur)
     {
        if (cur >= sd->minw)
          {
             sd->req = cur;
             evas_object_smart_callback_call(sd->obj, "size_request", NULL);
          }
     }
   recurse--;
}

static void
_e_gadcon_layout_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         "e_gadcon_layout",
         EVAS_SMART_CLASS_VERSION,
         _e_gadcon_layout_smart_add,
         _e_gadcon_layout_smart_del,
         _e_gadcon_layout_smart_move,
         _e_gadcon_layout_smart_resize,
         _e_gadcon_layout_smart_show,
         _e_gadcon_layout_smart_hide,
         _e_gadcon_layout_smart_color_set,
         _e_gadcon_layout_smart_clip_set,
         _e_gadcon_layout_smart_clip_unset,
         NULL, NULL, NULL, NULL, NULL, NULL, NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

static void
_e_gadcon_layout_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   sd->obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->clip = evas_object_rectangle_add(evas_object_evas_get(obj));
   sd->horizontal = 1;
   evas_object_smart_member_add(sd->clip, obj);
   evas_object_move(sd->clip, -100005, -100005);
   evas_object_resize(sd->clip, 200010, 200010);
   evas_object_color_set(sd->clip, 255, 255, 255, 255);
   evas_object_smart_data_set(obj, sd);
}

static void
_e_gadcon_layout_smart_del(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   while (sd->items)
     {
        Evas_Object *child;

        child = eina_list_data_get(sd->items);
        e_gadcon_layout_unpack(child);
     }
   if (sd->job) ecore_job_del(sd->job);
   sd->job = NULL;
   evas_object_del(sd->clip);
   free(sd);
   evas_object_smart_data_set(obj, NULL);
}

static void
_e_gadcon_layout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((x == sd->x) && (y == sd->y)) return;
   {
      Eina_List *l;
      Evas_Object *item;
      Evas_Coord dx, dy;

      if (sd->horizontal)
        {
           dx = x - sd->x;
           dy = y - sd->y;
        }
      else
        {
           dx = x - sd->y;
           dy = y - sd->x;
        }

      EINA_LIST_FOREACH(sd->items, l, item)
        {
           Evas_Coord ox, oy;

           evas_object_geometry_get(item, &ox, &oy, NULL, NULL);
           evas_object_move(item, ox + dx, oy + dy);
        }
   }

   if (sd->horizontal)
     {
        sd->x = x;
        sd->y = y;
     }
   else
     {
        sd->x = y;
        sd->y = x;
     }
}

static void
_resize_job_cb(void *data)
{
   E_Smart_Data *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   sd->job = NULL;
   _e_gadcon_layout_smart_reconfigure(sd);
}

static void
_e_gadcon_layout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((w == sd->w) && (h == sd->h)) return;
   if (sd->horizontal)
     {
        sd->w = w;
        sd->h = h;
     }
   else
     {
        sd->w = h;
        sd->h = w;
     }

   if (sd->job) ecore_job_del(sd->job);
   sd->job = ecore_job_add(_resize_job_cb, obj);
// done in job...
//   _e_gadcon_layout_smart_reconfigure(sd);
}

static void
_e_gadcon_layout_smart_show(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->items) evas_object_show(sd->clip);
}

static void
_e_gadcon_layout_smart_hide(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_hide(sd->clip);
}

static void
_e_gadcon_layout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_color_set(sd->clip, r, g, b, a);
}

static void
_e_gadcon_layout_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_set(sd->clip, clip);
}

static void
_e_gadcon_layout_smart_clip_unset(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!obj) return;
   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_unset(sd->clip);
}

/*
 * @min - the minimum width required by all the gadcons
 * @cur - the current width required by all the gadcons
 * @mino - the smalest width/height among all the objects
 */
static void
_e_gadcon_layout_smart_min_cur_size_calc(E_Smart_Data *sd, int *min, int *mino, int *cur)
{
   E_Gadcon_Layout_Item *bi;
   Eina_List *l;
   Evas_Object *item;

   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        bi->ask.size2 = bi->ask.size;

        if ((bi->aspect.w > 0) && (bi->aspect.h > 0))
          {
             bi->ask.size2 =
               (((sd->h - bi->aspect_pad.h) * bi->aspect.w) / bi->aspect.h) + bi->aspect_pad.w;

             if (bi->ask.size2 > bi->min.w)
               {
                  *min += bi->ask.size2;
                  *cur += bi->ask.size2;
               }
             else
               {
                  *min += bi->min.w;
                  *cur += bi->min.w;
               }
          }
        else
          {
             bi->ask.size2 = bi->ask.size = bi->min.w;
             *min += bi->min.w;
             if (bi->min.h > *mino) *mino = bi->min.h;
             if (bi->ask.size < bi->min.w)
               *cur += bi->min.w;
             else
               *cur += bi->ask.size;
          }
     }
}

static int
_e_gadcon_layout_smart_width_smart_sort_reverse_cb(const void *d1, const void *d2)
{
   const E_Gadcon_Layout_Item *bi, *bi2;

   bi = evas_object_data_get(d1, "e_gadcon_layout_data");
   bi2 = evas_object_data_get(d2, "e_gadcon_layout_data");

   if (bi->ask.size2 > bi->min.w)
     {
        if (bi2->ask.size2 > bi2->min.w)
          {
             if (bi->ask.size2 < bi2->ask.size2)
               return 1;
             else
               return -1;
          }
        else
          {
             if (bi->ask.size2 == bi2->ask.size2)
               return -1;
             else
               {
                  if (bi->ask.size2 < bi2->ask.size2)
                    return 1;
                  else
                    return -1;
               }
          }
     }
   else
     {
        if (bi2->ask.size2 > bi2->min.w)
          {
             if (bi->ask.size2 == bi2->ask.size2)
               return 1;
             else
               {
                  if (bi->ask.size2 < bi2->ask.size2)
                    return 1;
                  else
                    return -1;
               }
          }
        else
          {
             if (bi->ask.size2 < bi2->ask.size2)
               return 1;
             else if (bi->ask.size2 > bi2->ask.size2)
               return -1;
          }
     }

   return 0;
}

static void
_e_gadcon_layout_smart_gadcons_width_adjust(E_Smart_Data *sd, int min, int cur)
{
   E_Gadcon_Layout_Item *bi = NULL;
   Eina_List *l, *l2;
   Evas_Object *item;
   int needed = 0;
   int need = 0;
   int max_size, autosize = 0;

   if (sd->w < cur)
     {
        if (sd->w < min) max_size = min;
        else max_size = cur;
        need = max_size - sd->w;
     }
   else
     return;

   sd->items = eina_list_sort(sd->items, eina_list_count(sd->items),
                              _e_gadcon_layout_smart_width_smart_sort_reverse_cb);
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        if (bi->gcc->autoscroll) autosize += bi->ask.size2;
     }

   if (autosize < 1) autosize = 1;
   while (need > 0)
     {
        needed = need;
        EINA_LIST_REVERSE_FOREACH(sd->items, l2, item)
          {
             if (need <= 0) break;
             bi = evas_object_data_get(item, "e_gadcon_layout_data");
             if (bi->gcc->autoscroll)
               {
                  int reduce_by;

                  reduce_by = (need * bi->ask.size2) / autosize;
                  if (reduce_by < 1) reduce_by = 1;
                  if (bi->ask.size2 - reduce_by > 8)
                    {
                       bi->ask.size2 -= reduce_by;
                       need -= reduce_by;
                    }
                  else
                    {
                       need -= bi->ask.size2 - 8;
                       bi->ask.size2 = 8;
                    }
               }
          }
        /* If the 'needed' size change didn't get modified (no gadget has autoscroll)
           then we must break or we end up in an infinite loop */
        if (need == needed) break;
     }
}

static int
_e_gadcon_layout_smart_sort_by_sequence_number_cb(const void *d1, const void *d2)
{
   const E_Gadcon_Layout_Item *bi, *bi2;

   bi = evas_object_data_get(d1, "e_gadcon_layout_data");
   bi2 = evas_object_data_get(d2, "e_gadcon_layout_data");

   if ((!bi->gcc->state_info.seq) && (!bi2->gcc->state_info.seq)) return 0;
   else if (!bi->gcc->state_info.seq)
     return 1;
   else if (!bi2->gcc->state_info.seq)
     return -1;
   return bi->gcc->state_info.seq - bi2->gcc->state_info.seq;
}

static int
_e_gadcon_layout_smart_sort_by_position_cb(const void *d1, const void *d2)
{
   const E_Gadcon_Layout_Item *bi, *bi2;

   bi = evas_object_data_get(d1, "e_gadcon_layout_data");
   bi2 = evas_object_data_get(d2, "e_gadcon_layout_data");

   return bi->x - bi2->x;
}

static int
_e_gadcon_layout_smart_containers_sort_cb(const void *d1, const void *d2)
{
   const E_Layout_Item_Container *lc, *lc2;

   lc = d1;
   lc2 = d2;
   if (lc->pos < lc2->pos) return -1;
   else if (lc->pos > lc2->pos)
     return 1;
   return 0;
}

static int
_e_gadcon_layout_smart_seq_sort_cb(const void *d1, const void *d2)
{
   const E_Gadcon_Layout_Item *bi, *bi2;

   bi = d1;
   bi2 = d2;
   return bi->gcc->state_info.seq - bi2->gcc->state_info.seq;
}

static void
_e_gadcon_layout_smart_sync_clients(E_Gadcon *gc)
{
   E_Gadcon_Client *gcc;
   Eina_List *l;

   EINA_LIST_FOREACH(gc->clients, l, gcc)
     {
        _e_gadcon_client_save(gcc);
     }
}

static void
_e_gadcon_client_current_position_sync(E_Gadcon_Client *gcc)
{
   E_Gadcon_Layout_Item *bi;
   Evas_Object *o;

   o = gcc->o_frame ? gcc->o_frame : gcc->o_base;
   if (o)
     {
        bi = evas_object_data_get(o, "e_gadcon_layout_data");
        if (!bi) return;
     }
   else return;  /* make clang happy */

   gcc->state_info.prev_pos = gcc->config.pos;
   gcc->state_info.prev_size = gcc->config.size;
   gcc->config.pos = bi->x;
}

static void
_e_gadcon_layout_smart_gadcon_position_shrinked_mode(E_Smart_Data *sd)
{
   Eina_List *l;
   Evas_Object *item;
   E_Gadcon_Layout_Item *bi, *bi2;
   void *tp;
   int pos = 0;

   sd->items = eina_list_sort(sd->items, eina_list_count(sd->items),
                              _e_gadcon_layout_smart_sort_by_sequence_number_cb);
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        if (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_POS_INC)
          {
             if (bi->gcc->state_info.resist <= E_LAYOUT_ITEM_DRAG_RESIST_LEVEL)
               {
                  bi->gcc->state_info.resist++;
                  bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
               }
             else
               {
                  bi->gcc->state_info.resist = 0;
                  if (eina_list_next(l))
                    {
                       tp = eina_list_data_get(eina_list_next(l));
                       l->next->data = eina_list_data_get(l);
                       l->data = tp;

                       bi2 = evas_object_data_get(tp, "e_gadcon_layout_data");

                       if (bi2->x + bi2->w / 2 > bi->ask.pos + bi->w)
                         {
                            bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
                            return;
                         }

                       bi->gcc->config.pos = bi->ask.pos = bi2->ask.pos;
                       bi->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
                       bi->gcc->state_info.want_save = 1;
                       bi2->gcc->state_info.want_save = 1;
                       break;
                    }
                  else
                    bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
               }
          }
        else if (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_POS_DEC)
          {
             if (bi->gcc->state_info.resist <= E_LAYOUT_ITEM_DRAG_RESIST_LEVEL)
               {
                  bi->gcc->state_info.resist++;
                  bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
               }
             else
               {
                  bi->gcc->state_info.resist = 0;
                  if (eina_list_prev(l))
                    {
                       tp = eina_list_data_get(eina_list_prev(l));
                       l->prev->data = eina_list_data_get(l);

                       l->data = tp;
                       bi2 = evas_object_data_get(tp, "e_gadcon_layout_data");

                       if (bi->ask.pos > bi2->x + bi2->w / 2)
                         {
                            bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
                            return;
                         }

                       bi->gcc->config.pos = bi->ask.pos = bi2->ask.pos;
                       bi->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
                       bi->gcc->state_info.want_save = 1;
                       bi2->gcc->state_info.want_save = 1;
                       break;
                    }
                  else
                    bi->gcc->config.pos = bi->ask.pos = bi->gcc->state_info.prev_pos;
               }
          }
        else if ((bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_SIZE_MIN_END_INC) ||
                 (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_SIZE_MAX_END_DEC) ||
                 (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_SIZE_MIN_END_DEC) ||
                 (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_SIZE_MAX_END_INC))
          {
             if (bi->w < bi->min.w)
               bi->gcc->config.size = bi->w = bi->min.w;
             else
               bi->gcc->config.size = bi->w;

             bi->gcc->config.pos = bi->gcc->state_info.prev_pos;
          }
     }

   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");

        bi->x = pos;
        bi->w = bi->ask.size2;
        bi->gcc->config.size = bi->w;
        pos = bi->x + bi->w;
     }
}

static void
_e_gadcon_layout_smart_gadcons_asked_position_set(E_Smart_Data *sd)
{
   E_Gadcon_Layout_Item *bi;
   Eina_List *l;
   Evas_Object *item;

#if 0
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        if (!bi) continue;

        bi->x = bi->ask.pos;
        bi->w = bi->ask.size2;
     }
#else
   int pos;
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        if (!bi) continue;

        pos = bi->ask.pos + (bi->ask.size / 2);
        if (pos < (bi->ask.res / 3))
          {
             /* hooked to start */
             bi->x = bi->ask.pos;
             bi->w = bi->ask.size2;
             bi->hookp = 0;
          }
        else if (pos > ((2 * bi->ask.res) / 3))
          {
             /* hooked to end */
             bi->x = (bi->ask.pos - bi->ask.res) + sd->w;
             bi->w = bi->ask.size2;
             bi->hookp = bi->ask.res;
          }
        else
          {
             /* hooked to middle */
             if ((bi->ask.pos <= (bi->ask.res / 2)) &&
                 ((bi->ask.pos + bi->ask.size2) > (bi->ask.res / 2)))
               {
                  /* straddles middle */
                  if (bi->ask.res > 2)
                    bi->x = (sd->w / 2) +
                      (((bi->ask.pos + (bi->ask.size2 / 2) -
                         (bi->ask.res / 2)) *
                        (bi->ask.res / 2)) /
                       (bi->ask.res / 2)) - (bi->ask.size2 / 2);
                  else
                    bi->x = sd->w / 2;
                  bi->w = bi->ask.size2;
               }
             else
               {
                  /* either side of middle */
                  bi->x = (bi->ask.pos - (bi->ask.res / 2)) + (sd->w / 2);
                  bi->w = bi->ask.size2;
               }
             bi->hookp = bi->ask.res / 2;
          }
     }
#endif
}

/*
 * The function returns a list of E_Gadcon_Layout_Item_Container
 */
static Eina_List *
_e_gadcon_layout_smart_gadcons_wrap(E_Smart_Data *sd)
{
   Eina_List *l, *list = NULL;
   Evas_Object *item;
   E_Layout_Item_Container *lc;
   E_Gadcon_Layout_Item *bi;

   EINA_LIST_FOREACH(sd->items, l, item)
     {
        bi = evas_object_data_get(item, "e_gadcon_layout_data");
        lc = E_NEW(E_Layout_Item_Container, 1);
        lc->state_info.min_seq = lc->state_info.max_seq = bi->gcc->state_info.seq;
        lc->sd = sd;

        lc->pos = bi->x;
        lc->size = bi->w;

        lc->prev_pos = bi->ask.prev_pos;
        lc->prev_size = bi->ask.prev_size;

        E_LAYOUT_ITEM_CONTAINER_STATE_SET(lc->state, bi->gcc->state_info.state);

        if ((bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION) &&
            (lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE))
          lc->state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;

        lc->items = eina_list_append(lc->items, bi);
        list = eina_list_append(list, lc);
     }
   return list;
}

static void
_e_gadcon_layout_smart_gadcons_position(E_Smart_Data *sd, Eina_List **list)
{
   int ok, lc_moving_prev_pos;
   Eina_List *l, *l2, *l3;
   E_Layout_Item_Container *lc_moving = NULL, *lc_back = NULL, *lc, *lc3;
   E_Gadcon_Layout_Item *bi, *bi_moving = NULL;

   if ((!list) || (!*list)) return;

   EINA_LIST_FOREACH(*list, l, lc_moving)
     {
        if ((lc_moving->state != E_LAYOUT_ITEM_CONTAINER_STATE_NONE) &&
            (lc_moving->state != E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED))
          {
             lc_back = E_NEW(E_Layout_Item_Container, 1);
             lc_back->pos = lc_moving->pos;
             lc_back->prev_pos = lc_moving->prev_pos;
             lc_back->size = lc_moving->size;
             lc_back->prev_size = lc_moving->prev_size;
             lc_back->state_info.min_seq = lc_moving->state_info.min_seq;
             lc_back->state_info.max_seq = lc_moving->state_info.max_seq;
             lc_back->sd = lc_moving->sd;
             EINA_LIST_FOREACH(lc_moving->items, l2, lc)
               lc_back->items = eina_list_append(lc_back->items, lc);
             lc_back->state = lc_moving->state;
             bi_moving = eina_list_data_get(lc_back->items);

             break;
          }
        lc_moving = NULL;
     }

   if (!lc_moving)
     {
        if (lc_back) LC_FREE(lc_back);
        _e_gadcon_layout_smart_gadcons_position_static(sd, list);
        return;
     }

   lc_moving_prev_pos = lc_moving->prev_pos;
   if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC)
     {
        _e_gadcon_layout_smart_restore_gadcons_position_before_move(sd, &lc_moving, lc_back, list);
        EINA_LIST_FOREACH(*list, l, lc)
          if (lc == lc_moving) break;

        ok = 0;
        if ((l) && eina_list_prev(l))
          {
             lc = eina_list_data_get(eina_list_prev(l));

             if (lc_moving->pos < (lc->pos + lc->size))
               {
                  bi = eina_list_data_get(lc_moving->items);
                  if (bi->gcc->state_info.resist <= E_LAYOUT_ITEM_DRAG_RESIST_LEVEL)
                    {
                       if (lc_moving->prev_pos == (lc->pos + lc->size))
                         ok = 1;
                       bi->gcc->state_info.resist++;
                       lc_moving->pos = lc->pos + lc->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);
                    }
                  else
                    {
                       bi->gcc->state_info.resist = 0;
                       if (lc_moving->pos < lc->pos)
                         {
                            lc_moving->pos = (lc->pos + lc->size) - 1;
                            _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);
                         }
                       lc3 = _e_gadcon_layout_smart_containers_position_adjust(sd, lc, lc_moving);
                       if (lc3)
                         {
                            if (lc_moving->prev_pos == (lc->pos + lc->size))
                              ok = 1;

                            l->data = lc3;
                            *list = eina_list_remove_list(*list, eina_list_prev(l));
                            LC_FREE(lc_moving);
                            LC_FREE(lc);
                            lc_moving = lc3;
                         }
                    }
               }
          }
        if (!ok)
          {
             int pos, prev_pos, stop;

             EINA_LIST_FOREACH(*list, l, lc)
               if (lc == lc_moving) break;

             pos = lc_moving->pos + lc_moving->size;
             prev_pos = lc_moving_prev_pos;
             if ((l) && (eina_list_next(l)))
               {
                  stop = 0;
                  EINA_LIST_FOREACH(eina_list_next(l), l2, lc)
                    {
                       if (stop) break;
                       if (lc->pos != prev_pos) break;
                       prev_pos = lc->pos + lc->size;

                       EINA_LIST_FOREACH(lc->items, l3, bi)
                         {
                            if (bi->ask.pos <= pos)
                              {
                                 bi->x = pos;
                                 pos = (bi->x) + (bi->w);
                              }
                            else if (bi->ask.pos < bi->x)
                              {
                                 bi->x = bi->ask.pos;
                                 pos = (bi->x) + (bi->w);
                              }
                            else if (bi->ask.pos == bi->x)
                              {
                                 stop = 1;
                                 break;
                              }
                         }
                    }
               }
          }
     }
   else if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC)
     {
        _e_gadcon_layout_smart_restore_gadcons_position_before_move(sd, &lc_moving, lc_back, list);
        EINA_LIST_FOREACH(*list, l, lc)
          if (lc == lc_moving) break;

        ok = 0;
        if ((l) && eina_list_next(l))
          {
             lc = eina_list_data_get(eina_list_next(l));

             if ((lc_moving->pos + lc_moving->size) > lc->pos)
               {
                  bi = eina_list_data_get(lc_moving->items);
                  if (bi->gcc->state_info.resist <= E_LAYOUT_ITEM_DRAG_RESIST_LEVEL)
                    {
                       if ((lc_moving->prev_pos + lc_moving->size) == lc->pos)
                         ok = 1;
                       bi->gcc->state_info.resist++;
                       lc_moving->pos = lc->pos - lc_moving->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);
                    }
                  else
                    {
                       bi->gcc->state_info.resist = 0;
                       if ((lc_moving->pos + lc_moving->size) > lc->pos)
                         {
                            lc_moving->pos = (lc->pos - lc_moving->size) + 1;
                            _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);
                         }
                       lc3 = _e_gadcon_layout_smart_containers_position_adjust(sd, lc_moving, lc);
                       if (lc3)
                         {
                            if ((lc_moving->prev_pos + lc_moving->size) == lc->pos)
                              ok = 1;

                            l->data = lc3;
                            *list = eina_list_remove_list(*list, eina_list_next(l));
                            LC_FREE(lc_moving);
                            LC_FREE(lc);
                            lc_moving = lc3;
                         }
                    }
               }
          }

        if (!ok)
          {
             int pos, prev_pos, stop;

             EINA_LIST_FOREACH(*list, l, lc)
               if (lc == lc_moving) break;

             pos = lc_moving->pos;
             prev_pos = lc_moving_prev_pos;

             if ((l) && eina_list_prev(l))
               {
                  stop = 0;
                  /* EINA_FUCK_REVERSE_FOREACH(eina_list_prev(l), l2, lc) */
                  for (l2 = l->prev; l2; l2 = l2->prev)
                    {
                       lc = l2->data;

                       if (stop) break;
                       if ((lc->pos + lc->size) == prev_pos) break;
                       prev_pos = lc->pos;

                       EINA_LIST_REVERSE_FOREACH(lc->items, l3, bi)
                         {
                            if ((bi->ask.pos + bi->w) >= pos)
                              {
                                 bi->x = pos - bi->w;
                                 pos = bi->x;
                              }
                            else if (bi->ask.pos > bi->x)
                              {
                                 bi->x = bi->ask.pos;
                                 pos = bi->x;
                              }
                            else if (bi->ask.pos == bi->x)
                              {
                                 stop = 1;
                                 break;
                              }
                         }
                    }
               }
          }
     }
   else if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_DEC)
     {
        _e_gadcon_layout_smart_restore_gadcons_position_before_move(sd, &lc_moving, lc_back, list);
        EINA_LIST_FOREACH(*list, l, lc)
          if (lc == lc_moving) break;

        if ((l) && eina_list_prev(l))
          {
             int new_pos = 0;

             ok = 0;
             new_pos = lc_moving->pos;
             /* EINA_FUCK_REVERSE_FOREACH(eina_list_prev(l), l2, lc) */
             for (l2 = l->prev; l2; l2 = l2->prev)
               {
                  lc = l2->data;
                  if (new_pos >= (lc->pos + lc->size)) break;

                  ok = 1;
                  new_pos -= lc->size;
               }

             if (new_pos < 0)
               {
                  lc_moving->size += new_pos;
                  lc_moving->pos -= new_pos;

                  bi = eina_list_data_get(lc_moving->items);
                  bi->w = lc_moving->size;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);

                  new_pos = 0;
               }

             if (ok)
               {
                  if (!l2) l2 = *list;
                  else l2 = eina_list_next(l2);

                  EINA_LIST_FOREACH(l2, l2, lc)
                    {
                       if (l2 == l) break;
                       lc->pos = new_pos;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
                       EINA_LIST_FOREACH(lc->items, l3, bi)
                         {
                            bi->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
                         }
                       new_pos += lc->size;
                    }
               }
          }
        else if ((l) && (!eina_list_prev(l)))
          {
             if (lc_moving->pos <= 0)
               {
                  lc_moving->size = lc_moving->prev_size;
                  lc_moving->pos = 0;

                  bi = eina_list_data_get(lc_moving->items);
                  bi->w = lc_moving->size;

                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc_moving);
               }
          }
     }
   else if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_INC)
     {
        lc_moving->state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
        _e_gadcon_layout_smart_gadcons_position_static(sd, list);
        if (lc_back) LC_FREE(lc_back);
     }
   else if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_INC)
     {
        _e_gadcon_layout_smart_restore_gadcons_position_before_move(sd, &lc_moving, lc_back, list);
        EINA_LIST_FOREACH(*list, l, lc)
          if (lc == lc_moving) break;

        if ((l) && eina_list_next(l))
          {
             Eina_List *stop = NULL;
             int new_pos = 0;

             ok = 0;
             new_pos = lc_moving->pos + lc_moving->size;
             EINA_LIST_FOREACH(eina_list_next(l), l2, lc)
               {
                  if (new_pos <= lc->pos)
                    {
                       stop = l2;
                       break;
                    }

                  ok = 1;
                  /* new_pos += lc->size; */
               }

             if (new_pos > sd->w)
               {
                  lc_moving->size -= (new_pos - sd->w);
                  bi = eina_list_data_get(lc_moving->items);
                  bi->w = lc_moving->size;

                  new_pos = lc_moving->pos + lc_moving->size;
               }

             if (ok)
               {
                  EINA_LIST_FOREACH(eina_list_next(l), l2, lc)
                    {
                       if (l2 == stop) break;
                       lc->pos = new_pos;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
                       EINA_LIST_FOREACH(lc->items, l3, bi)
                         {
                            bi->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;
                         }
                       new_pos += lc->size;
                    }
               }
          }
        else if ((l) && (!eina_list_next(l)))
          {
             if ((lc_moving->pos + lc_moving->size) >= sd->w)
               {
                  lc_moving->size = lc_moving->prev_size;
                  bi = eina_list_data_get(lc_moving->items);
                  bi->w = lc_moving->size;
               }
          }
     }
   else if (lc_moving->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_DEC)
     {
        lc_moving->state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
        _e_gadcon_layout_smart_gadcons_position_static(sd, list);
        if (lc_back) LC_FREE(lc_back);
     }

   if (bi_moving)
     {
        bi_moving->gcc->config.pos = bi_moving->ask.pos = bi_moving->x;
        bi_moving->gcc->config.size = bi_moving->w;
     }
}

static void
_e_gadcon_layout_smart_gadcons_position_static(E_Smart_Data *sd, Eina_List **list)
{
   int ok;
   Eina_List *l;
   E_Layout_Item_Container *lc, *lc2, *lc3;

   *list = eina_list_sort(*list, eina_list_count(*list), _e_gadcon_layout_smart_containers_sort_cb);

__reposition_again:
   EINA_LIST_FOREACH(*list, l, lc)
     {
        if (!eina_list_next(l)) continue;

        lc2 = eina_list_data_get(eina_list_next(l));

        if (LC_OVERLAP(lc, lc2))
          {
             lc3 = _e_gadcon_layout_smart_containers_position_adjust(sd, lc, lc2);
             if (lc3)
               {
                  l->data = lc3;
                  *list = eina_list_remove_list(*list, eina_list_next(l));
                  LC_FREE(lc);
                  LC_FREE(lc2);
                  goto __reposition_again;
               }
          }
     }

   ok = 1;
   EINA_LIST_FOREACH(*list, l, lc)
     {
        if (lc->pos < 0)
          {
             ok = 0;
             lc->pos = 0;
             _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
             continue;
          }

        if (((lc->pos + lc->size) > sd->w) && (lc->size <= sd->w))
          {
             ok = 0;
             lc->pos = sd->w - lc->size;
             _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
          }
     }
   if (!ok)
     _e_gadcon_layout_smart_gadcons_position_static(sd, list);
}

static E_Layout_Item_Container *
_e_gadcon_layout_smart_containers_position_adjust(E_Smart_Data *sd, E_Layout_Item_Container *lc, E_Layout_Item_Container *lc2)
{
   int create_new = 0;
   Eina_List *l;
   E_Layout_Item_Container *lc3 = NULL;
   E_Layout_Item_Container_State new_state;
   E_Gadcon_Layout_Item *bi, *bi2;

   if ((lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE) &&
       (lc2->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE))
     {
        if (lc->state_info.max_seq <= lc2->state_info.min_seq)
          {
             lc2->pos = lc->pos + lc->size;
             _e_gadcon_layout_smart_position_items_inside_container(sd, lc2);
          }
        else if (lc->state_info.min_seq > lc2->state_info.max_seq)
          {
             lc->pos = lc2->pos + lc2->size;
             _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
          }
        else if (((lc->state_info.min_seq > lc2->state_info.min_seq) &&
                  (lc->state_info.min_seq < lc2->state_info.max_seq)) ||
                 ((lc2->state_info.min_seq > lc->state_info.min_seq) &&
                  (lc2->state_info.min_seq < lc->state_info.max_seq)))
          {
             _e_gadcon_layout_smart_containers_merge(sd, lc, lc2);
          }
        create_new = 1;
        new_state = E_LAYOUT_ITEM_CONTAINER_STATE_NONE;
     }
   else if ((lc->state != E_LAYOUT_ITEM_CONTAINER_STATE_NONE) &&
            (lc2->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE))
     {
        if (lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC)
          {
             int t;

             bi = eina_list_data_get(lc->items);
             bi2 = eina_list_data_get(lc2->items);

             bi->x = ((bi2->x) + (bi2->w)) - (bi->w);
             bi->gcc->config.pos = bi->ask.pos = bi->x;
             bi->gcc->config.size = bi->w;
             bi2->x = (bi->x) - (bi2->w);

             bi2->gcc->state_info.flags &= ~E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE;

             t = bi->gcc->state_info.seq;
             bi->gcc->state_info.seq = bi2->gcc->state_info.seq;
             bi2->gcc->state_info.seq = t;

             _e_gadcon_layout_smart_containers_merge(sd, lc, lc2);
          }
        else if (lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED)
          {
             if (lc->state_info.max_seq < lc2->state_info.min_seq)
               {
                  lc2->pos = lc->pos + lc->size;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc2);
               }
             else if (lc->state_info.min_seq > lc2->state_info.max_seq)
               {
                  lc2->pos = lc->pos - lc2->size;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc2);
               }
             else if (((lc->state_info.min_seq > lc2->state_info.min_seq) &&
                       (lc->state_info.min_seq < lc2->state_info.max_seq)) ||
                      ((lc2->state_info.min_seq > lc->state_info.min_seq) &&
                       (lc2->state_info.min_seq < lc->state_info.max_seq)))
               {
                  int shift = 0;

                  _e_gadcon_layout_smart_containers_merge(sd, lc, lc2);

                  EINA_LIST_FOREACH(lc->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION)
                         {
                            shift = bi->ask.pos - bi->x;
                         }

                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         break;
                    }

                  if (shift)
                    {
                       EINA_LIST_FOREACH(lc->items, l, bi)
                         {
                            bi->x += shift;

                            if (l == lc->items)
                              lc->pos = bi->x;
                         }
                    }
               }
          }
        create_new = 1;
        new_state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
     }
   else if ((lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE) &&
            (lc2->state != E_LAYOUT_ITEM_CONTAINER_STATE_NONE))
     {
        if (lc2->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED)
          {
             if (lc->state_info.max_seq < lc2->state_info.min_seq)
               {
                  lc->pos = lc2->pos - lc->size;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
               }
             else if (lc->state_info.min_seq > lc2->state_info.max_seq)
               {
                  lc->pos = lc2->pos + lc2->size;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
               }
             else if (((lc->state_info.min_seq > lc2->state_info.min_seq) &&
                       (lc->state_info.min_seq < lc2->state_info.max_seq)) ||
                      ((lc2->state_info.min_seq > lc->state_info.min_seq) &&
                       (lc2->state_info.min_seq < lc->state_info.max_seq)))
               {
                  int shift = 0;

                  EINA_LIST_FOREACH(lc->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION)
                         {
                            shift = bi->ask.pos - bi->x;
                         }

                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         break;
                    }

                  if (shift)
                    {
                       EINA_LIST_FOREACH(lc->items, l, bi)
                         {
                            bi->x += shift;

                            if (l == lc->items)
                              lc->pos = bi->x;
                         }
                    }
               }
          }
        else if (lc2->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC)
          {
             int t;

             bi = eina_list_last_data_get(lc->items);
             bi2 = eina_list_data_get(lc2->items);

             bi2->gcc->config.pos = bi2->ask.pos = (bi2->x) = (bi->x);
             bi2->gcc->config.size = bi2->w;
             bi->x = bi2->x + bi2->w;

             t = bi->gcc->state_info.seq;
             bi->gcc->state_info.seq = bi2->gcc->state_info.seq;
             bi2->gcc->state_info.seq = t;

             lc->items = eina_list_remove_list(lc->items, eina_list_last(lc->items));
             lc->items = eina_list_append(lc->items, bi2);
             lc->items = eina_list_append(lc->items, bi);
             lc2->items = eina_list_free(lc2->items);
             E_LAYOUT_ITEM_CONTAINER_SIZE_CHANGE_BY(lc, bi2, 1);
             lc2->pos = lc->pos + lc->size;
             lc2->size = 0;
          }
        create_new = 1;
        new_state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
     }
   else if ((lc->state != E_LAYOUT_ITEM_CONTAINER_STATE_NONE) &&
            (lc2->state != E_LAYOUT_ITEM_CONTAINER_STATE_NONE))
     {
        if ((lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED) &&
            (lc2->state == E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED))
          {
             if (lc->state_info.max_seq < lc2->state_info.min_seq)
               {
                  int move_lc1 = 1;
                  int move_lc2 = 1;

                  EINA_LIST_FOREACH(lc->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         {
                            move_lc1 = 0;
                            break;
                         }
                    }
                  EINA_LIST_FOREACH(lc2->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         {
                            move_lc2 = 0;
                            break;
                         }
                    }

                  if ((move_lc1) && (!move_lc2))
                    {
                       lc->pos = lc2->pos - lc->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
                    }
                  else
                    {
                       lc2->pos = lc->pos + lc->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc2);
                    }
               }
             else if (lc->state_info.min_seq > lc2->state_info.max_seq)
               {
                  int move_lc1 = 1;
                  int move_lc2 = 1;

                  EINA_LIST_FOREACH(lc->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         {
                            move_lc1 = 0;
                            break;
                         }
                    }
                  EINA_LIST_FOREACH(lc2->items, l, bi)
                    {
                       if (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE)
                         {
                            move_lc2 = 0;
                            break;
                         }
                    }

                  if ((!move_lc1) && (move_lc2))
                    {
                       lc2->pos = lc->pos - lc2->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc2);
                    }
                  else
                    {
                       lc->pos = lc2->pos + lc2->size;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, lc);
                    }
               }
             else if (((lc->state_info.min_seq > lc2->state_info.min_seq) &&
                       (lc->state_info.min_seq < lc2->state_info.max_seq)) ||
                      ((lc2->state_info.min_seq > lc->state_info.min_seq) &&
                       (lc2->state_info.min_seq < lc->state_info.max_seq)))
               {
                  int shift = 0;

                  _e_gadcon_layout_smart_containers_merge(sd, lc, lc2);

                  EINA_LIST_FOREACH(lc->items, l, bi)
                    {
                       if ((bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_POSITION) &&
                           (bi->gcc->state_info.flags & E_GADCON_LAYOUT_ITEM_LOCK_ABSOLUTE))
                         {
                            shift = bi->ask.pos - bi->x;
                            break;
                         }
                    }

                  if (shift)
                    {
                       EINA_LIST_FOREACH(lc->items, l, bi)
                         {
                            bi->x += shift;

                            if (l == lc->items)
                              lc->pos = bi->x;
                         }
                    }
               }
             create_new = 1;
             new_state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
          }
     }

   if (create_new)
     {
        lc3 = E_NEW(E_Layout_Item_Container, 1);
        lc3->sd = sd;
        if (lc->pos < lc2->pos)
          {
             lc3->pos = lc->pos;
             EINA_LIST_FOREACH(lc->items, l, bi)
               lc3->items = eina_list_append(lc3->items, bi);
             EINA_LIST_FOREACH(lc2->items, l, bi)
               lc3->items = eina_list_append(lc3->items, bi);

             lc3->state_info.min_seq = lc->state_info.min_seq;
             if (lc2->items)
               lc3->state_info.max_seq = lc2->state_info.max_seq;
             else
               lc3->state_info.max_seq = lc->state_info.max_seq;
          }
        else
          {
             lc3->pos = lc2->pos;
             EINA_LIST_FOREACH(lc2->items, l, bi)
               lc3->items = eina_list_append(lc3->items, bi);
             EINA_LIST_FOREACH(lc->items, l, bi)
               lc3->items = eina_list_append(lc3->items, bi);

             lc3->state_info.min_seq = lc2->state_info.min_seq;
             if (lc->items)
               lc3->state_info.max_seq = lc->state_info.max_seq;
             else
               lc3->state_info.max_seq = lc2->state_info.max_seq;
          }
        lc3->size = lc->size + lc2->size;
        lc3->state = new_state;
     }

   return lc3;
}

static void
_e_gadcon_layout_smart_position_items_inside_container(E_Smart_Data *sd EINA_UNUSED, E_Layout_Item_Container *lc)
{
   int shift;
   Eina_List *l;
   E_Gadcon_Layout_Item *bi;

   if (!lc->items) return;

   bi = eina_list_data_get(lc->items);
   shift = lc->pos - bi->x;

   if (!shift) return;

   EINA_LIST_FOREACH(lc->items, l, bi)
     {
        bi->x += shift;

        if ((bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_POS_DEC) ||
            (bi->gcc->state_info.state == E_LAYOUT_ITEM_STATE_POS_INC))
          {
             bi->gcc->config.pos = bi->ask.pos = bi->x;
          }
     }
}

static void
_e_gadcon_layout_smart_containers_merge(E_Smart_Data *sd EINA_UNUSED, E_Layout_Item_Container *lc, E_Layout_Item_Container *lc2)
{
   int start = 0, size = 0, next = 0, min_seq = 0, max_seq = 0;
   Eina_List *l, *nl = NULL;
   E_Gadcon_Layout_Item *bi;

   EINA_LIST_FOREACH(lc->items, l, bi)
     nl = eina_list_append(nl, bi);
   EINA_LIST_FOREACH(lc2->items, l, bi)
     nl = eina_list_append(nl, bi);

   nl = eina_list_sort(nl, eina_list_count(nl), _e_gadcon_layout_smart_seq_sort_cb);

   EINA_LIST_FOREACH(nl, l, bi)
     {
        if (l == nl)
          {
             min_seq = max_seq = bi->gcc->state_info.seq;
             start = bi->x;
             size = bi->w;
             next = bi->x + bi->w;

             continue;
          }

        max_seq = bi->gcc->state_info.seq;

        bi->x = next;
        size += bi->w;
        next = bi->x + bi->w;
     }

   lc->items = eina_list_free(lc->items);
   lc2->items = eina_list_free(lc->items);
   lc->items = nl;
   lc->pos = start;
   lc->size = size;
   lc->state_info.min_seq = min_seq;
   lc->state_info.max_seq = max_seq;
   lc2->pos = lc->pos + lc->size;
   lc2->size = 0;
}

static void
_e_gadcon_layout_smart_restore_gadcons_position_before_move(E_Smart_Data *sd, E_Layout_Item_Container **lc_moving, E_Layout_Item_Container *lc_back, Eina_List **con_list)
{
   int ok;
   Eina_List *l, *l2, *l3;
   E_Gadcon_Layout_Item *bi, *bi2;
   E_Layout_Item_Container *lc, *lc2, *lc3;

   (*lc_moving)->pos = (*lc_moving)->prev_pos;
   if (((*lc_moving)->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_INC) ||
       ((*lc_moving)->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MIN_END_DEC) ||
       ((*lc_moving)->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_INC) ||
       ((*lc_moving)->state == E_LAYOUT_ITEM_CONTAINER_STATE_SIZE_MAX_END_DEC))
     {
        (*lc_moving)->size = (*lc_moving)->prev_size;
        bi = eina_list_data_get((*lc_moving)->items);

        bi->w = (*lc_moving)->prev_size;
     }
   _e_gadcon_layout_smart_position_items_inside_container(sd, (*lc_moving));
   (*lc_moving)->state = E_LAYOUT_ITEM_CONTAINER_STATE_POS_LOCKED;
   _e_gadcon_layout_smart_gadcons_position_static(sd, con_list);

   lc2 = NULL;
   lc3 = NULL;
   ok = 0;
   EINA_LIST_FOREACH(*con_list, l, lc)
     {
        if (lc->state == E_LAYOUT_ITEM_CONTAINER_STATE_NONE) continue;

        if (eina_list_count(lc->items) == 1)
          {
             bi = eina_list_data_get(lc->items);
             if (bi->gcc->state_info.state != E_LAYOUT_ITEM_STATE_NONE)
               {
                  LC_FREE(lc);
                  l->data = *lc_moving = lc_back;
                  _e_gadcon_layout_smart_position_items_inside_container(sd, (*lc_moving));

                  if (((*lc_moving)->state != E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC) &&
                      ((*lc_moving)->state != E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC))
                    {
                       bi = eina_list_data_get((*lc_moving)->items);
                       bi->w = (*lc_moving)->size;
                    }
               }
          }
        else
          {
             EINA_LIST_FOREACH(lc->items, l2, bi)
               {
                  if (bi->gcc->state_info.state != E_LAYOUT_ITEM_STATE_NONE)
                    {
                       ok = 1;
                       if (l2 != lc->items)
                         {
                            lc2 = E_NEW(E_Layout_Item_Container, 1);
                            lc2->sd = sd;
                            lc2->state = E_LAYOUT_ITEM_CONTAINER_STATE_NONE;
                            EINA_LIST_FOREACH(lc->items, l3, bi2)
                              {
                                 if (l2 == l3) break;
                                 lc2->items = eina_list_append(lc2->items, bi2);
                                 if (l3 == lc->items)
                                   {
                                      lc2->state_info.min_seq = bi2->gcc->state_info.seq;
                                      lc2->pos = lc2->prev_pos = bi2->x;
                                   }
                                 lc2->state_info.max_seq = bi2->gcc->state_info.seq;
                                 E_LAYOUT_ITEM_CONTAINER_SIZE_CHANGE_BY(lc2, bi2, 1);
                              }
                         }

                       if (eina_list_next(l2))
                         {
                            lc3 = E_NEW(E_Layout_Item_Container, 1);
                            lc3->sd = sd;
                            lc3->state = E_LAYOUT_ITEM_CONTAINER_STATE_NONE;
                            EINA_LIST_FOREACH(eina_list_next(l2), l3, bi2)
                              {
                                 lc3->items = eina_list_append(lc3->items, bi2);
                                 if (l3 == eina_list_next(l2))
                                   {
                                      lc3->state_info.min_seq = bi2->gcc->state_info.seq;
                                      lc3->pos = lc3->prev_pos = bi2->x;
                                   }
                                 lc3->state_info.max_seq = bi2->gcc->state_info.seq;
                                 E_LAYOUT_ITEM_CONTAINER_SIZE_CHANGE_BY(lc3, bi2, 1);
                              }
                         }
                       *lc_moving = lc_back;
                       _e_gadcon_layout_smart_position_items_inside_container(sd, *lc_moving);

                       if (((*lc_moving)->state != E_LAYOUT_ITEM_CONTAINER_STATE_POS_INC) &&
                           ((*lc_moving)->state != E_LAYOUT_ITEM_CONTAINER_STATE_POS_DEC))
                         {
                            bi = eina_list_data_get((*lc_moving)->items);
                            bi->w = (*lc_moving)->size;
                         }
                       break;
                    }
               }
             if (ok)
               {
                  LC_FREE(lc);
                  if (lc2)
                    {
                       l->data = lc2;
                       *con_list = eina_list_append(*con_list, *lc_moving);
                       if (lc3)
                         *con_list = eina_list_append(*con_list, lc3);
                       *con_list = eina_list_sort(*con_list, eina_list_count(*con_list),
                                                  _e_gadcon_layout_smart_containers_sort_cb);
                    }
                  else
                    {
                       l->data = *lc_moving;
                       if (lc3)
                         {
                            *con_list = eina_list_append(*con_list, lc3);
                            *con_list = eina_list_sort(*con_list, eina_list_count(*con_list),
                                                       _e_gadcon_layout_smart_containers_sort_cb);
                         }
                    }
                  break;
               }
          }
     }

   EINA_LIST_FOREACH(*con_list, l, lc)
     {
        if (lc == *lc_moving) continue;
        lc->state = E_LAYOUT_ITEM_CONTAINER_STATE_NONE;
     }
}

static void
_e_gadcon_event_populate_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Gadcon_Populate *ev = event;

   e_object_unref(E_OBJECT(ev->gc));
   free(ev);
}

static void
_e_gadcon_custom_populate_job(void *data EINA_UNUSED)
{
   const E_Gadcon_Client_Class *cc;
   E_Config_Gadcon_Client *cf_gcc;
   Eina_List *l, *ll;
   E_Gadcon *gc;

#ifndef E_RELEASE_BUILD
   static Eina_Bool first = EINA_TRUE;
   if (first)
     e_main_ts("gadcon custom populate idler start");
#endif
   EINA_LIST_FREE(custom_populate_requests, gc)
     {
        if (!gc->cf) continue;
        e_gadcon_layout_freeze(gc->o_container);
        EINA_LIST_FOREACH_SAFE(gc->cf->clients, l, ll, cf_gcc)
          {
             if ((!cf_gcc->name) || (!cf_gcc->name[0]) || (!cf_gcc->id) || (!cf_gcc->id[0]))
               {
                  e_gadcon_client_config_del(gc->cf, cf_gcc);
                  continue;
               }
             cc = eina_hash_find(providers, cf_gcc->name);
             if (!cc) continue;
             if (gc->populate_class.func)
               gc->populate_class.func(gc->populate_class.data, gc, cc);
             else
               e_gadcon_populate_class(gc, cc);
          }
        e_gadcon_layout_thaw(gc->o_container);
        _e_gadcon_event_populate(gc);
     }

#ifndef E_RELEASE_BUILD
   if (first)
     e_main_ts("gadcon custom populate idler end");
#endif
   if (!custom_populate_requests)
     {
        custom_populate_job = NULL;
#ifndef E_RELEASE_BUILD
        first = EINA_FALSE;
#endif
     }
}

static void
_e_gadcon_provider_populate_job(void *data EINA_UNUSED)
{
   E_Gadcon_Client_Class *cc;
   Eina_List *l;
   E_Gadcon *gc;

#ifndef E_RELEASE_BUILD
   static Eina_Bool first = EINA_TRUE;
   if (first)
     e_main_ts("gadcon populate idler start");
#endif
   EINA_LIST_FOREACH(gadcons, l, gc)
     {
        int x = 0;
        Eina_Bool freeze = EINA_FALSE;
        if (gc->populate_requests)
          {
             freeze = EINA_TRUE;
             e_gadcon_layout_freeze(gc->o_container);
          }
        EINA_LIST_FREE(gc->populate_requests, cc)
          {
#ifndef E_RELEASE_BUILD
             if (first) e_main_ts(cc->name);
#endif
             if (gc->populate_class.func)
               gc->populate_class.func(gc->populate_class.data, gc, cc);
             else
               e_gadcon_populate_class(gc, cc);
             if (!eina_list_data_find(gc->populated_classes, cc))
               {
                  gc->populated_classes = eina_list_append(gc->populated_classes, cc);
                  if (gc->cf)
                    {
                       Eina_List *ll;
                       E_Config_Gadcon_Client *cf_gcc;

                       if (!gc->awaiting_classes) continue;
                       ll = eina_hash_set(gc->awaiting_classes, cc->name, NULL);
                       EINA_LIST_FREE(ll, cf_gcc)
                         _e_gadcon_client_populate(gc, cc, cf_gcc);
                    }
               }
             x++;
          }
        if (freeze) e_gadcon_layout_thaw(gc->o_container);
        if (x && _modules_loaded) _e_gadcon_event_populate(gc);
     }
//out:
#ifndef E_RELEASE_BUILD
   if (first)
     e_main_ts("gadcon populate idler end");
#endif

   populate_job = NULL;
#ifndef E_RELEASE_BUILD
   first = EINA_FALSE;
#endif
}

static void
_e_gadcon_provider_populate_request(E_Gadcon *gc, const E_Gadcon_Client_Class *cc)
{
   if (eina_list_data_find(gc->populate_requests, cc)) return;
   gc->populate_requests = eina_list_append(gc->populate_requests, cc);
   if (populate_job) ecore_job_del(populate_job);
   populate_job = ecore_job_add(_e_gadcon_provider_populate_job, NULL);
}

static void
_e_gadcon_provider_populate_unrequest(const E_Gadcon_Client_Class *cc)
{
   E_Gadcon *gc;
   Eina_List *l;
   unsigned int more = 0;
   EINA_LIST_FOREACH(gadcons, l, gc)
     {
        gc->populate_requests = eina_list_remove(gc->populate_requests, cc);
        more += eina_list_count(gc->populate_requests);
     }
   if ((!more) && (populate_job))
     {
        ecore_job_del(populate_job);
        populate_job = NULL;
     }
}

/* gadgets movement between different gadcons */

E_API E_Gadcon_Location *
e_gadcon_location_new(const char *name,
                      E_Gadcon_Site site,
                      int (*add_func)(void *data, E_Gadcon_Client *gcc, const E_Gadcon_Client_Class *cc),
                      void *add_data,
                      void (*remove_func)(void *data, E_Gadcon_Client *cc),
                      void *remove_data)
{
   E_Gadcon_Location *loc;

   loc = E_NEW(E_Gadcon_Location, 1);
   loc->name = eina_stringshare_add(name);
   loc->site = site;
   loc->gadget_add.func = add_func;
   loc->gadget_add.data = add_data;
   loc->gadget_remove.func = remove_func;
   loc->gadget_remove.data = remove_data;
   loc->icon_name = NULL;
   return loc;
}

E_API void
e_gadcon_location_set_icon_name(E_Gadcon_Location *loc, const char *name)
{
   if (loc->icon_name) eina_stringshare_del(loc->icon_name);
   if (name)
     loc->icon_name = eina_stringshare_add(name);
   else
     loc->icon_name = NULL;
}

E_API void
e_gadcon_location_free(E_Gadcon_Location *loc)
{
   eina_stringshare_del(loc->name);
   if (loc->icon_name) eina_stringshare_del(loc->icon_name);
   free(loc);
}

E_API void
e_gadcon_location_register(E_Gadcon_Location *loc)
{
   gadcon_locations = eina_list_append(gadcon_locations, loc);
}

E_API void
e_gadcon_location_unregister(E_Gadcon_Location *loc)
{
   gadcon_locations = eina_list_remove(gadcon_locations, loc);
}

static int
_e_gadcon_location_change(E_Gadcon_Client *gcc, E_Gadcon_Location *src, E_Gadcon_Location *dst)
{
   E_Gadcon_Client_Class *cc;

   cc = eina_hash_find(providers, gcc->cf->name);
   if (!cc) return 0;
   if (!dst->gadget_add.func(dst->gadget_add.data, gcc, cc)) return 0;
   gcc->cf = NULL;
   src->gadget_remove.func(src->gadget_remove.data, gcc);
   return 1;
}

E_API Eina_Bool
e_gadcon_client_visible_get(const E_Gadcon_Client *gcc, const E_Desk *desk)
{
   const Eina_List *l;
   E_Zone *zone;

   if (!gcc->gadcon) return EINA_FALSE;
   switch (gcc->gadcon->location->site)
     {
      case E_GADCON_SITE_DESKTOP:
        return EINA_TRUE; // FIXME for when gadman allows per-desk gadgets
      case E_GADCON_SITE_SHELF:
        if (desk) return e_shelf_desk_visible(gcc->gadcon->shelf, desk);
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          if (e_shelf_desk_visible(gcc->gadcon->shelf, e_desk_current_get(zone)))
            return EINA_TRUE;
        break;
      case E_GADCON_SITE_TOOLBAR:
      case E_GADCON_SITE_EFM_TOOLBAR:
        if (desk) return (e_win_client_get(gcc->gadcon->toolbar->fwin)->desk == desk);
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          if (e_win_client_get(gcc->gadcon->toolbar->fwin)->desk == e_desk_current_get(zone)) return EINA_TRUE;
      default:
        break;
     }
   return EINA_FALSE;
}
#include "e.h"

/* local subsystem functions */

static void _e_gadcon_popup_changed_size_hints_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED);
static void _e_gadcon_popup_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED);

static Eina_Bool
_e_popup_autoclose_deskafter_show_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Desk_After_Show *ev = event;
   E_Gadcon_Popup *pop = data;

   if (!pop) return ECORE_CALLBACK_RENEW;
   if (!pop->visible) return ECORE_CALLBACK_RENEW;
   if (!e_gadcon_client_visible_get(pop->gcc, ev->desk))
     e_object_del(E_OBJECT(pop));

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_popup_autoclose_client_fullscreen_cb(void *data, int type EINA_UNUSED, void *event)
{
   E_Event_Client *ev = event;
   E_Gadcon_Popup *pop = data;

   if (!pop) return ECORE_CALLBACK_RENEW;
   if (!pop->visible) return ECORE_CALLBACK_RENEW;
   if (!ev->ec->fullscreen) return ECORE_CALLBACK_RENEW;
   if (e_gadcon_client_visible_get(pop->gcc, ev->ec->desk))
     e_object_del(E_OBJECT(pop));
   return ECORE_CALLBACK_RENEW;
}

static void
_e_gadcon_popup_locked_set(E_Gadcon_Popup *pop, Eina_Bool locked)
{
   if (!pop->gcc)
     return;

   e_gadcon_locked_set(pop->gcc->gadcon, locked);
   pop->gadcon_was_locked = locked;
}

static void
_e_gadcon_popup_delay_del(void *obj)
{
   E_Gadcon_Popup *pop = obj;

   if (pop->comp_object) evas_object_hide(pop->comp_object);
}

static void
_e_gadcon_popup_free(E_Gadcon_Popup *pop)
{
   if (pop->content)
     {
        evas_object_event_callback_del_full(pop->content, EVAS_CALLBACK_DEL,
                                            _e_gadcon_popup_del_cb, pop);
        evas_object_event_callback_del_full(pop->content, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                            _e_gadcon_popup_changed_size_hints_cb, pop);
        pop->content = NULL;
     }
   E_FREE_FUNC(pop->autoclose_handlers[0], ecore_event_handler_del);
   E_FREE_FUNC(pop->autoclose_handlers[1], ecore_event_handler_del);

   if (pop->gadcon_was_locked)
     _e_gadcon_popup_locked_set(pop, 0);
   pop->gcc = NULL;
   evas_object_hide(pop->comp_object);
   E_FREE_FUNC(pop->comp_object, evas_object_del);
   E_FREE_FUNC(pop->show_idler, ecore_idler_del);
   E_FREE_FUNC(pop->show_animator, ecore_animator_del);
   free(pop);
}

static void
_e_gadcon_popup_position(E_Gadcon_Popup *pop)
{
   Evas_Coord gx = 0, gy = 0, gw, gh, zw, zh, zx, zy, px, py;
   E_Zone *zone;

   /* Popup positioning */
   e_gadcon_client_geometry_get(pop->gcc, &gx, &gy, &gw, &gh);
   zone = e_gadcon_zone_get(pop->gcc->gadcon);
   zx = zone->x;
   zy = zone->y;
   zw = zone->w;
   zh = zone->h;
   switch (pop->gcc->gadcon->orient)
     {
      case E_GADCON_ORIENT_CORNER_RT:
      case E_GADCON_ORIENT_CORNER_RB:
      case E_GADCON_ORIENT_RIGHT:
        px = gx - pop->w;
        py = gy;
        if (py + pop->h >= (zy + zh))
          py = gy + gh - pop->h;
        px = MIN(zx + zw - gw - pop->w - 3, px);
        break;

      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_CORNER_LB:
        px = gx + gw;
        py = gy;
        if (py + pop->h >= (zy + zh))
          py = gy + gh - pop->h;
        px = MIN(zx + zw - gw - pop->w + 3, px);
        break;

      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
        py = gy + gh;
        px = (gx + (gw / 2)) - (pop->w / 2);
        if ((px + pop->w) >= (zx + zw))
          px = gx + gw - pop->w;
        else if (px < zx)
          px = zx;
        py = MIN(zy + zh - gh - pop->h + 3, py);
        break;

      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        py = gy - pop->h;
        px = (gx + (gw / 2)) - (pop->w / 2);
        if ((px + pop->w) >= (zx + zw))
          px = gx + gw - pop->w;
        else if (px < zx)
          px = zx;
        py = MIN(zy + zh - gh - pop->h - 3, py);
        break;

      case E_GADCON_ORIENT_FLOAT:
        px = (gx + (gw / 2)) - (pop->w / 2);
        if (gy >= (zy + (zh / 2)))
          py = gy - pop->h;
        else
          py = gy + gh;
        if ((px + pop->w) >= (zx + zw))
          px = gx + gw - pop->w;
        else if (px < zx)
          px = zx;
        break;

      default:
        evas_object_move(pop->comp_object, 50, 50);
        return;
     }
   if (px - zx < 0)
     px = zx;
   if (py - zy < 0)
     py = zy;
   evas_object_move(pop->comp_object, px, py);

   if (pop->gadcon_lock && (!pop->gadcon_was_locked))
     _e_gadcon_popup_locked_set(pop, 1);
}

static void
_e_gadcon_popup_size_recalc(E_Gadcon_Popup *pop, Evas_Object *obj)
{
   Evas_Coord pw, ph, w = 0, h = 0;

   if (!isedje(obj))
     evas_object_size_hint_min_get(obj, &w, &h);
   else
     {
        edje_object_size_min_get(obj, &w, &h);
        edje_object_size_min_restricted_calc(obj, &w, &h, w, h);
        evas_object_size_hint_min_set(obj, w, h);
     }
   edje_object_size_min_calc(pop->o_bg, &pw, &ph);
   pop->w = MAX(pw, pop->w);
   pop->h = MAX(ph, pop->h);
   evas_object_resize(pop->comp_object, pop->w, pop->h);

   _e_gadcon_popup_position(pop);
}

static void
_e_gadcon_popup_changed_size_hints_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Gadcon_Popup *pop = data;

   /* edje calc bug: hint updates on child are not updated for min_calc unless this happens first */
   edje_object_part_unswallow(pop->o_bg, obj);
   edje_object_part_swallow(pop->o_bg, "e.swallow.content", obj);
   _e_gadcon_popup_size_recalc(data, obj);
}

static void
_e_gadcon_popup_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Gadcon_Popup *pop = data;

   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_DEL,
                                       _e_gadcon_popup_del_cb, pop);
   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                       _e_gadcon_popup_changed_size_hints_cb, pop);
   pop->content = NULL;
}

/* externally accessible functions */

E_API E_Gadcon_Popup *
e_gadcon_popup_new(E_Gadcon_Client *gcc, Eina_Bool noshadow)
{
   E_Gadcon_Popup *pop;
   Evas_Object *o;

   pop = E_OBJECT_ALLOC(E_Gadcon_Popup, E_GADCON_POPUP_TYPE, _e_gadcon_popup_free);
   if (!pop) return NULL;
   e_object_delay_del_set(E_OBJECT(pop), _e_gadcon_popup_delay_del);

   o = edje_object_add(e_comp->evas);
   e_theme_edje_object_set(o, "base/theme/gadman", "e/gadman/popup");
   pop->o_bg = o;

   pop->comp_object = e_comp_object_util_add(o, noshadow ? E_COMP_OBJECT_TYPE_NONE : E_COMP_OBJECT_TYPE_POPUP);
   if (e_comp->nocomp)
     evas_object_layer_set(pop->comp_object, E_LAYER_CLIENT_NORMAL);
   else
     evas_object_layer_set(pop->comp_object, E_LAYER_POPUP);

   pop->gcc = gcc;
   pop->gadcon_lock = 1;
   pop->gadcon_was_locked = 0;

   return pop;
}

E_API void
e_gadcon_popup_content_set(E_Gadcon_Popup *pop, Evas_Object *o)
{
   Evas_Object *old_o;

   if (!pop) return;
   E_OBJECT_CHECK(pop);
   E_OBJECT_TYPE_CHECK(pop, E_GADCON_POPUP_TYPE);

   old_o = edje_object_part_swallow_get(pop->o_bg, "e.swallow.content");
   if (old_o != o)
     {
        if (old_o)
          {
             edje_object_part_unswallow(pop->o_bg, old_o);
             evas_object_del(old_o);
          }
        edje_object_part_swallow(pop->o_bg, "e.swallow.content", o);
        evas_object_event_callback_add(o, EVAS_CALLBACK_DEL,
                                       _e_gadcon_popup_del_cb, pop);
        evas_object_event_callback_add(o, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                       _e_gadcon_popup_changed_size_hints_cb, pop);
     }
   pop->content = o;
   _e_gadcon_popup_size_recalc(pop, o);
}

static Eina_Bool
_cb_show_animator(void *data)
{
   E_Gadcon_Popup *pop = data;
   pop->show_animator = NULL;
   E_FREE_FUNC(pop->show_idler, ecore_idler_del);
   evas_object_show(pop->comp_object);
   return EINA_FALSE;
}

static Eina_Bool
_cb_show_idler(void *data)
{
   E_Gadcon_Popup *pop = data;
   pop->show_idler = NULL;
   E_FREE_FUNC(pop->show_animator, ecore_animator_del);
   pop->show_animator = ecore_animator_add(_cb_show_animator, pop);
   return EINA_FALSE;
}

E_API void
e_gadcon_popup_show(E_Gadcon_Popup *pop)
{
   E_OBJECT_CHECK(pop);
   E_OBJECT_TYPE_CHECK(pop, E_GADCON_POPUP_TYPE);

   if (pop->visible) return;

   _e_gadcon_popup_position(pop);
   pop->autoclose_handlers[0] = ecore_event_handler_add(E_EVENT_DESK_AFTER_SHOW, _e_popup_autoclose_deskafter_show_cb, NULL);
   pop->autoclose_handlers[1] = ecore_event_handler_add(E_EVENT_CLIENT_FULLSCREEN, _e_popup_autoclose_client_fullscreen_cb, NULL);
   if (pop->content)
     e_comp_object_util_del_list_append(pop->comp_object, pop->content);
   pop->show_idler = ecore_idler_add(_cb_show_idler, pop);
   pop->visible = EINA_TRUE;
}

E_API void
e_gadcon_popup_hide(E_Gadcon_Popup *pop)
{
   E_OBJECT_CHECK(pop);
   E_OBJECT_TYPE_CHECK(pop, E_GADCON_POPUP_TYPE);
   if (pop->pinned) return;
   if (!pop->visible) return;
   E_FREE_FUNC(pop->show_idler, ecore_idler_del);
   E_FREE_FUNC(pop->show_animator, ecore_animator_del);
   evas_object_hide(pop->comp_object);
   if (pop->gadcon_was_locked)
     _e_gadcon_popup_locked_set(pop, 0);
   pop->visible = EINA_FALSE;
}

E_API void
e_gadcon_popup_toggle_pinned(E_Gadcon_Popup *pop)
{
   if (!pop) return;
   E_OBJECT_CHECK(pop);
   E_OBJECT_TYPE_CHECK(pop, E_GADCON_POPUP_TYPE);

   if (pop->pinned)
     {
        pop->pinned = 0;
        edje_object_signal_emit(pop->o_bg, "e,state,unpinned", "e");
     }
   else
     {
        pop->pinned = 1;
        edje_object_signal_emit(pop->o_bg, "e,state,pinned", "e");
     }
}

E_API void
e_gadcon_popup_lock_set(E_Gadcon_Popup *pop, Eina_Bool setting)
{
   E_OBJECT_CHECK(pop);
   E_OBJECT_TYPE_CHECK(pop, E_GADCON_POPUP_TYPE);

   setting = !!setting;
   if (pop->gadcon_lock == setting) return;
   pop->gadcon_lock = setting;

   if (setting != pop->gadcon_was_locked)
     _e_gadcon_popup_locked_set(pop, setting);
}
#include <e.h>

#ifdef HAVE_ELPUT

#include <Eina.h>
#include <grp.h>
#include <sys/types.h>
#include <pwd.h>
#include <Elput.h>

static Eina_Hash *active_gestures;
static Elput_Manager *manager;

typedef struct {
   Eina_Vector2 pos;
   unsigned int fingers;
   struct {
     Evas_Object *visuals, *win;
   } visuals;
} Swipe_Stats;

static int gesture_capable_devices = 0;
static E_Bindings_Swipe_Live_Update live_update;
static void* live_update_data;
static Eina_List *handlers = NULL;

static Swipe_Stats*
_find_swipe_gesture_recognizition(Elput_Device *dev)
{
   Swipe_Stats *stats = eina_hash_find(active_gestures, dev);

   return stats;
}

static Swipe_Stats*
_start_swipe_gesture_recognizition(Elput_Device *dev)
{
   Swipe_Stats *stats = _find_swipe_gesture_recognizition(dev);

   if (stats)
     eina_hash_del_by_key(active_gestures, dev);

   stats = calloc(1, sizeof(Swipe_Stats));
   EINA_SAFETY_ON_NULL_RETURN_VAL(stats, NULL);

   if (e_bindings_swipe_available())
     {
        E_Zone *zone = e_zone_current_get();

        stats->visuals.win =  elm_notify_add(zone->base);
        elm_notify_align_set(stats->visuals.win, 0.5, 0.5);
        elm_object_tree_focus_allow_set(stats->visuals.win, EINA_FALSE);
        evas_object_layer_set(stats->visuals.win, E_LAYER_CLIENT_PRIO);
        evas_object_show(stats->visuals.win);

        stats->visuals.visuals = elm_progressbar_add(stats->visuals.win);
        elm_object_text_set(stats->visuals.visuals, "Progress of visuals");
        evas_object_size_hint_min_set(stats->visuals.visuals, 300, 50);
        evas_object_show(stats->visuals.visuals);
        elm_object_content_set(stats->visuals.win, stats->visuals.visuals);
     }


   eina_hash_add(active_gestures, dev, stats);

   return stats;
}

static void
_end_swipe_gesture_recognizition(Elput_Device *dev)
{
   eina_hash_del_by_key(active_gestures, dev);
}

static double
_config_angle(Eina_Vector2 pos)
{
   double res = atan(pos.y/pos.x);

   if (res < 0) res += M_PI;
   if (pos.y < 0) res += M_PI;
   return res;
}

static void
_stats_free(void *ptr)
{
   Swipe_Stats *stats = ptr;

   evas_object_del(stats->visuals.win);
   free(stats);
}

static void
_apply_visual_changes(Swipe_Stats *stats)
{
   if (live_update)
     {
        live_update(live_update_data, EINA_FALSE, _config_angle(stats->pos), eina_vector2_length_get(&stats->pos), 0.8, stats->fingers);
     }
   else if (stats->visuals.win)
     {
        Eina_Inarray *res = e_bindings_swipe_find_candidates(E_BINDING_CONTEXT_NONE, _config_angle (stats->pos), eina_vector2_length_get(&stats->pos), stats->fingers);
        E_Binding_Swipe_Candidate *itr;
        double total = 0.0f;
        unsigned int len = 0;

        EINA_INARRAY_FOREACH(res, itr)
          {
             total += itr->acceptance;
             len ++;
          }

        if (len > 0)
          {
             char text_buffer[1000];

             snprintf(text_buffer, sizeof(text_buffer), "%d gestures possible", len);
             elm_progressbar_value_set(stats->visuals.visuals, total/len);
             elm_object_text_set(stats->visuals.visuals, text_buffer);
          }
        else
          {
             elm_progressbar_value_set(stats->visuals.visuals, 0.0f);
             elm_object_text_set(stats->visuals.visuals, "No gesture found");
          }

        eina_inarray_free(res);
     }
}

static Eina_Bool
_swipe_cb(void *data EINA_UNUSED, int type, void *event)
{
   Elput_Swipe_Gesture *gesture = event;
   Elput_Device *dev = elput_swipe_device_get(gesture);

   if (type == ELPUT_EVENT_SWIPE_BEGIN)
     {
        Swipe_Stats *stats = _start_swipe_gesture_recognizition(dev);
        stats->fingers = elput_swipe_finger_count_get(gesture);
        stats->pos.x = stats->pos.y = 0;
     }
   else if (type == ELPUT_EVENT_SWIPE_UPDATE)
     {
        Swipe_Stats *stats = _find_swipe_gesture_recognizition(dev);

        stats->pos.x += elput_swipe_dx_get(gesture);
        stats->pos.y += elput_swipe_dy_get(gesture);

        _apply_visual_changes(stats);
     }
   else if (type == ELPUT_EVENT_SWIPE_END)
     {
        Swipe_Stats *stats = _find_swipe_gesture_recognizition(dev);

        if (live_update)
          live_update(live_update_data, EINA_TRUE, _config_angle(stats->pos), eina_vector2_length_get(&stats->pos), 0.8, stats->fingers);
        else
          e_bindings_swipe_handle(E_BINDING_CONTEXT_NONE, NULL, _config_angle(stats->pos), eina_vector2_length_get(&stats->pos), stats->fingers);

        _end_swipe_gesture_recognizition(dev);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_debug(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Elput_Event_Seat_Caps *caps = event;
   const Eina_List *node, *devices = elput_seat_devices_get(caps->seat);
   Elput_Device *device;
   int number_of_gesture_devices = 0;

   EINA_LIST_FOREACH(devices, node, device)
     {
        if (elput_device_caps_get(device) & ELPUT_DEVICE_CAPS_GESTURE)
          {
             number_of_gesture_devices++;
          }
     }
   gesture_capable_devices= number_of_gesture_devices;
   return ECORE_CALLBACK_PASS_ON;
}

static void
_init_for_x11(void)
{
   const char *device = NULL;

   if (!elput_init())
     {
        ERR("Failed to init elput");
        return;
     }
   device = getenv("XDG_SEAT");
   if (!device) device = "seat0";
   manager = elput_manager_connect_gestures(device, 0);

   EINA_SAFETY_ON_NULL_RETURN(manager);
   elput_input_init(manager);
}

static void
_shutdown_for_x11(void)
{
   elput_manager_disconnect(manager);
   manager = NULL;
   elput_shutdown();
}

static int
_detect_vm(void)
{
   static int on_vm = -1;
   int ret;

   if (on_vm >= 0) return on_vm;
   ret = system("systemd-detect-virt");
   if      (ret == 0) on_vm = 1;
   else if (ret == 1) on_vm = 0;
   else
     {
        ret = system("hostnamectl status | grep 'Chassis: vm'");
        if      (ret == 0) on_vm = 1;
        else if (ret == 1) on_vm = 0;
        // fallback to assuming not on vm
        else  on_vm = 0;
     }
   return on_vm;
}

E_API int
e_gesture_init(void)
{
   // we have some bizarre bug on vbox -> this causes xorg to stop displaying
   // the screen output even though it's rendered and you can grab it through
   // screenshots, xrandr reports a perfectly well configured display.
   // somehow using elput to get input devices from logind causes xorg
   // internally to not handle e's first restart after wizard - as if
   // the xserver goes into some bixarre internal state - it doesn't report
   // any errors though. this works around that by just avoiding the gesture
   // support on vm's - they wont have touchpads anyway as they will emulate
   // a normal mouse mostly... :)
   if (_detect_vm()) return 1;

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _init_for_x11();
     }

   active_gestures = eina_hash_pointer_new(_stats_free);

   E_LIST_HANDLER_APPEND(handlers, ELPUT_EVENT_SWIPE_BEGIN, _swipe_cb, NULL);
   E_LIST_HANDLER_APPEND(handlers, ELPUT_EVENT_SWIPE_UPDATE, _swipe_cb, NULL);
   E_LIST_HANDLER_APPEND(handlers, ELPUT_EVENT_SWIPE_END, _swipe_cb, NULL);
   E_LIST_HANDLER_APPEND(handlers, ELPUT_EVENT_SEAT_CAPS, _debug, NULL);

   return 1;
}

E_API int
e_gesture_shutdown(void)
{
   Ecore_Event_Handler *hand;

   if (_detect_vm()) return 1;

   if (active_gestures)
     {
        eina_hash_free(active_gestures);
        active_gestures = NULL;
     }

   EINA_LIST_FREE(handlers, hand)
     {
        ecore_event_handler_del(hand);
     }

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _shutdown_for_x11();
     }

   return 1;
}

E_API void
e_bindings_swipe_live_update_hook_set(E_Bindings_Swipe_Live_Update update, void *data)
{
   live_update = update;
   live_update_data = data;
}

E_API int
e_bindings_gesture_capable_devices_get(void)
{
   return gesture_capable_devices;
}

#endif
#include "e.h"

#define TEXT_PRESS_KEY_SEQUENCE            _("Please press a key combination.<ps/><ps/>" \
                                             "Press <hilight>Escape</hilight> to abort.")
#define TEXT_PRESS_MOUSE_BINDING_SEQUENCE _("Please hold any modifier you want<ps/>"            \
                                             "and press any button on your mouse<ps/>" \
                                             "or roll a wheel to assign mouse binding.<ps/><ps/>"      \
                                             "Press <hilight>Escape</hilight> to abort.")
static Eina_Bool
_e_grab_dialog_key_handler(void *data, int type EINA_UNUSED, Ecore_Event_Key *ev)
{
   E_Grab_Dialog *eg = data;

   if (ev->window != eg->grab_win) return ECORE_CALLBACK_RENEW;
   if (!strcmp(ev->key, "Escape") &&
       !(ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT) &&
       !(ev->modifiers & ECORE_EVENT_MODIFIER_CTRL) &&
       !(ev->modifiers & ECORE_EVENT_MODIFIER_ALT) &&
       !(ev->modifiers & ECORE_EVENT_MODIFIER_WIN))
     {
        e_object_del(data);
        return ECORE_CALLBACK_RENEW;
     }

   if (eg->key)
     {
        e_object_ref(data);
        eg->key(eg->data ? : eg, type, ev);
        e_object_unref(data);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_grab_dialog_wheel_handler(void *data, int type EINA_UNUSED, Ecore_Event_Mouse_Wheel *ev)
{
   E_Grab_Dialog *eg = data;

   if (ev->window != eg->grab_win) return ECORE_CALLBACK_RENEW;

   if (eg->wheel)
     {
        e_object_ref(data);
        eg->wheel(eg->data ? : eg, type, ev);
        e_object_unref(data);
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_grab_dialog_mouse_handler(void *data, int type EINA_UNUSED, Ecore_Event_Mouse_Button *ev)
{
   E_Grab_Dialog *eg = data;

   if (ev->window != eg->grab_win) return ECORE_CALLBACK_RENEW;

   if (eg->mouse)
     {
        e_object_ref(data);
        eg->mouse(eg->data ? : eg, type, ev);
        e_object_unref(data);
     }
   else
     e_object_del(data);
   return ECORE_CALLBACK_RENEW;
}

static void
_e_grab_dialog_free(E_Grab_Dialog *eg)
{
   if (eg->grab_win)
     {
        e_grabinput_release(eg->grab_win, eg->grab_win);
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          ecore_x_window_free(eg->grab_win);
        else
#endif
          e_comp_ungrab_input(1, 1);
     }
   E_FREE_LIST(eg->handlers, ecore_event_handler_del);
   e_bindings_disabled_set(0);

   e_object_del(E_OBJECT(eg->dia));
   free(eg);
}

static void
_e_grab_dialog_delete(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_del(data);
}

static void
_e_grab_dialog_dia_del(void *data)
{
   E_Dialog *dia = data;

   e_object_del(dia->data);
}

E_API E_Grab_Dialog *
e_grab_dialog_show(Evas_Object *parent, Eina_Bool is_mouse, Ecore_Event_Handler_Cb key, Ecore_Event_Handler_Cb mouse, Ecore_Event_Handler_Cb wheel, const void *data)
{
   E_Grab_Dialog *eg;
   Ecore_Event_Handler *eh;

   if (parent)
     evas_object_focus_set(e_win_client_get(parent)->frame, 0);

   eg = E_OBJECT_ALLOC(E_Grab_Dialog, E_GRAB_DIALOG_TYPE, _e_grab_dialog_free);
   if (!eg) return NULL;

   if (is_mouse)
     {
        eg->dia = e_dialog_new(parent, "E", "_mousebind_getmouse_dialog");
        e_dialog_title_set(eg->dia, _("Mouse Binding Combination"));
        e_dialog_icon_set(eg->dia, "preferences-desktop-mouse", 48);
        e_dialog_text_set(eg->dia, TEXT_PRESS_MOUSE_BINDING_SEQUENCE);
     }
   else
     {
        eg->dia = e_dialog_new(parent, "E", "_keybind_getkey_dialog");
        e_dialog_title_set(eg->dia, _("Key Binding Combination"));
        e_dialog_icon_set(eg->dia, "preferences-desktop-keyboard-shortcuts", 48);
        e_dialog_text_set(eg->dia, TEXT_PRESS_KEY_SEQUENCE);
     }
   eg->dia->data = eg;
   elm_win_center(eg->dia->win, 1, 1);
   elm_win_borderless_set(eg->dia->win, 1);
   e_dialog_resizable_set(eg->dia, 0);
   e_object_del_attach_func_set(E_OBJECT(eg->dia), _e_grab_dialog_dia_del);
   evas_object_event_callback_add(eg->dia->win, EVAS_CALLBACK_DEL, _e_grab_dialog_delete, eg);

#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        eg->grab_win = ecore_x_window_input_new(e_comp->root, 0, 0, 1, 1);
        ecore_x_window_show(eg->grab_win);
        e_grabinput_get(eg->grab_win, 0, eg->grab_win);
     }
   else
#endif
     {
        e_comp_grab_input(1, 1);
        eg->grab_win = e_comp->ee_win;
     }

   eg->key = key;
   eg->mouse = mouse;
   eg->wheel = wheel;
   eg->data = (void *)data;

   eh = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, (Ecore_Event_Handler_Cb)_e_grab_dialog_key_handler, eg);
   eg->handlers = eina_list_append(eg->handlers, eh);
   eh = ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, (Ecore_Event_Handler_Cb)_e_grab_dialog_mouse_handler, eg);
   eg->handlers = eina_list_append(eg->handlers, eh);
   if (wheel)
     {
        eh = ecore_event_handler_add(ECORE_EVENT_MOUSE_WHEEL, (Ecore_Event_Handler_Cb)_e_grab_dialog_wheel_handler, eg);
        eg->handlers = eina_list_append(eg->handlers, eh);
     }
   e_dialog_show(eg->dia);
   evas_object_layer_set(e_win_client_get(eg->dia->win)->frame, E_LAYER_CLIENT_PRIO);
   e_bindings_disabled_set(1);
   return eg;
}

#include "e.h"

/* local subsystem functions */
static void      _e_grabinput_focus_do(Ecore_Window win, E_Focus_Method method);
static void      _e_grabinput_focus(Ecore_Window win, E_Focus_Method method);

/* local subsystem globals */
static Ecore_Window grab_mouse_win = 0;
static Ecore_Window grab_key_win = 0;
static Ecore_Window focus_win = 0;
static E_Focus_Method focus_method = E_FOCUS_METHOD_NO_INPUT;
static double last_focus_time = 0.0;

static Ecore_Window focus_fix_win = 0;
static E_Focus_Method focus_fix_method = E_FOCUS_METHOD_NO_INPUT;

static void (*lost_cb) (void *data) = NULL;
static void *lost_data = NULL;

/* externally accessible functions */
EINTERN int
e_grabinput_init(void)
{
   return 1;
}

EINTERN int
e_grabinput_shutdown(void)
{
   return 1;
}

E_API void
e_grabinput_lost_cb_set(void (*cb) (void *data), void *data)
{
   lost_cb = cb;
   lost_data = data;
}

E_API int
e_grabinput_get(Ecore_Window mouse_win, int confine_mouse, Ecore_Window key_win)
{
   Eina_Bool ungrabbed = EINA_FALSE;

   if (grab_mouse_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          ecore_x_pointer_ungrab();
#endif
        grab_mouse_win = 0;
        ungrabbed = EINA_TRUE;
     }
   if (grab_key_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          ecore_x_keyboard_ungrab();
#endif

        grab_key_win = 0;
        focus_win = 0;
        ungrabbed = EINA_TRUE;
     }
   if (ungrabbed)
     {
        if (lost_cb) lost_cb(lost_data);
        lost_cb = NULL;
        lost_data = NULL;
     }
   if (mouse_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          {
             int ret = 0;
             if (confine_mouse)
               ret = ecore_x_pointer_confine_grab(mouse_win);
             else
               ret = ecore_x_pointer_grab(mouse_win);
             if (!ret) return 0;
          }
#endif
        grab_mouse_win = mouse_win;
     }
   if (key_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          {
             int ret = 0;

             ret = ecore_x_keyboard_grab(key_win);
             if (!ret)
               {
                  if (grab_mouse_win)
                    {
                       ecore_x_pointer_ungrab();
                       grab_mouse_win = 0;
                    }
                  return 0;
               }
          }
#endif
        grab_key_win = key_win;
     }
#ifdef HAVE_WAYLAND_ONLY
   (void)confine_mouse;
#endif
   return 1;
}

E_API void
e_grabinput_release(Ecore_Window mouse_win, Ecore_Window key_win)
{
   if (mouse_win == grab_mouse_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          ecore_x_pointer_ungrab();
#endif

        grab_mouse_win = 0;
     }
   if (key_win == grab_key_win)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type == E_PIXMAP_TYPE_X)
          ecore_x_keyboard_ungrab();
#endif

        grab_key_win = 0;
        if (focus_win != 0)
          {
             /* fprintf(stderr, "release focus to %x\n", focus_win); */
             _e_grabinput_focus(focus_win, focus_method);
             focus_win = 0;
             focus_method = E_FOCUS_METHOD_NO_INPUT;
          }
     }
}

E_API void
e_grabinput_focus(Ecore_Window win, E_Focus_Method method)
{
   if (grab_key_win != 0)
     {
        /* fprintf(stderr, "while grabbed focus changed to %x\n", win); */
        focus_win = win;
        focus_method = method;
     }
   else
     {
        /* fprintf(stderr, "focus to %x\n", win); */
        _e_grabinput_focus(win, method);
     }
}

E_API double
e_grabinput_last_focus_time_get(void)
{
   return last_focus_time;
}

E_API Ecore_Window
e_grabinput_last_focus_win_get(void)
{
   return focus_fix_win;
}

E_API Ecore_Window
e_grabinput_key_win_get(void)
{
   return grab_key_win;
}

E_API Ecore_Window
e_grabinput_mouse_win_get(void)
{
   return grab_mouse_win;
}

static void
_e_grabinput_focus_do(Ecore_Window win, E_Focus_Method method)
{
   /* fprintf(stderr, "focus to %x method %i\n", win, method); */
   switch (method)
     {
      case E_FOCUS_METHOD_NO_INPUT:
        break;

      case E_FOCUS_METHOD_LOCALLY_ACTIVE:
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp_util_has_x())
          {
             ecore_x_window_focus(win);
             ecore_x_icccm_take_focus_send(win, ecore_x_current_time_get());
          }
#endif
        break;

      case E_FOCUS_METHOD_GLOBALLY_ACTIVE:
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp_util_has_x())
          ecore_x_icccm_take_focus_send(win, ecore_x_current_time_get());
#endif
        break;

      case E_FOCUS_METHOD_PASSIVE:
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp_util_has_x())
          ecore_x_window_focus(win);
#endif
        break;

      default:
        break;
     }
}

static void
_e_grabinput_focus(Ecore_Window win, E_Focus_Method method)
{
   focus_fix_win = win;
   focus_fix_method = method;
   /* fprintf(stderr, "foc do 1\n"); */
   _e_grabinput_focus_do(win, method);
   last_focus_time = ecore_loop_time_get();
}
#include "e.h"

#ifndef HAVE_WAYLAND_ONLY
static void e_hints_e16_comms_pretend(Ecore_X_Window root, Ecore_X_Window propwin);
static void e_hints_openoffice_gnome_fake(Ecore_Window root);
//static void e_hints_openoffice_kde_fake(Ecore_Window root);

E_API Ecore_X_Atom ATM__QTOPIA_SOFT_MENU = 0;
E_API Ecore_X_Atom ATM__QTOPIA_SOFT_MENUS = 0;
E_API Ecore_X_Atom ATM_GNOME_SM_PROXY = 0;
E_API Ecore_X_Atom ATM_ENLIGHTENMENT_COMMS = 0;
E_API Ecore_X_Atom ATM_ENLIGHTENMENT_VERSION = 0;
E_API Ecore_X_Atom ATM_ENLIGHTENMENT_SCALE = 0;

E_API Ecore_X_Atom ATM_NETWM_SHOW_WINDOW_MENU = 0;
E_API Ecore_X_Atom ATM_NETWM_PERFORM_BUTTON_ACTION = 0;
E_API Ecore_X_Atom ATM_GTK_FRAME_EXTENTS = 0;

E_API Ecore_X_Atom ATM_STEAM_GAME = 0;
#endif

EINTERN void
e_hints_init(Ecore_Window root, Ecore_Window propwin)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)root;
   (void)propwin;
#else
   const char *atom_names[] = {
      "_QTOPIA_SOFT_MENU",
      "_QTOPIA_SOFT_MENUS",
      "GNOME_SM_PROXY",
      "ENLIGHTENMENT_COMMS",
      "ENLIGHTENMENT_VERSION",
      "ENLIGHTENMENT_SCALE",
      "_NET_WM_SHOW_WINDOW_MENU",
      "_NET_WM_PERFORM_BUTTON_ACTION",
      "_GTK_FRAME_EXTENTS",
      "STEAM_GAME",
   };
   Ecore_X_Atom atoms[EINA_C_ARRAY_LENGTH(atom_names)];
   Ecore_X_Atom supported[46];
   int supported_num;
   Ecore_X_Window win, twin;
   int nwins;
   char *name;
   double ts;

   ecore_x_atoms_get(atom_names, EINA_C_ARRAY_LENGTH(atom_names), atoms);
   ATM__QTOPIA_SOFT_MENU = atoms[0];
   ATM__QTOPIA_SOFT_MENUS = atoms[1];
   ATM_GNOME_SM_PROXY = atoms[2];
   ATM_ENLIGHTENMENT_COMMS = atoms[3];
   ATM_ENLIGHTENMENT_VERSION = atoms[4];
   ATM_ENLIGHTENMENT_SCALE = atoms[5];
   ATM_NETWM_SHOW_WINDOW_MENU = atoms[6];
   ATM_NETWM_PERFORM_BUTTON_ACTION = atoms[7];
   ATM_GTK_FRAME_EXTENTS = atoms[8];
   ATM_STEAM_GAME = atoms[9];

   supported_num = 0;
   /* Set what hints we support */
   /* Root Window Properties (and Related Messages) */
   supported[supported_num++] = ECORE_X_ATOM_NET_CLIENT_LIST;
   supported[supported_num++] = ECORE_X_ATOM_NET_CLIENT_LIST_STACKING;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_NUMBER_OF_DESKTOPS, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_DESKTOP_GEOMETRY, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_DESKTOP_VIEWPORT, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_CURRENT_DESKTOP, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_DESKTOP_NAMES, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_ACTIVE_WINDOW;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WORKAREA, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_SUPPORTING_WM_CHECK;
   supported[supported_num++] = ECORE_X_ATOM_NET_VIRTUAL_ROOTS;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_DESKTOP_LAYOUT, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_SHOWING_DESKTOP, 1);*/

   /* Other Root Window Messages */
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_CLOSE_WINDOW, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_MOVERESIZE_WINDOW, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_MOVERESIZE;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_RESTACK_WINDOW, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_REQUEST_FRAME_EXTENTS;

   /* Application Window Properties */
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_NAME;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_VISIBLE_NAME;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_ICON_NAME;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_VISIBLE_ICON_NAME;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_DESKTOP, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_DESKTOP;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_DOCK;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_TOOLBAR;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_MENU;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_UTILITY;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_SPLASH;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_DIALOG;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_WINDOW_TYPE_NORMAL;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_MODAL;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_STICKY;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_MAXIMIZED_VERT;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_MAXIMIZED_HORZ;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_SHADED;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_SKIP_TASKBAR;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_SKIP_PAGER;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_HIDDEN;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_FULLSCREEN;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_ABOVE;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STATE_BELOW;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_STATE_DEMANDS_ATTENTION, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ALLOWED_ACTIONS, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_MOVE, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_RESIZE, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_MINIMIZE, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_SHADE, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_STICK, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_MAXIMIZE_HORZ, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_MAXIMIZE_VERT, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_FULLSCREEN, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_CHANGE_DESKTOP, 1);*/
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ACTION_CLOSE, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STRUT;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_STRUT_PARTIAL;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_ICON_GEOMETRY, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_ICON;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_PID;
   /*ecore_x_netwm_supported(roots[supported_num], ECORE_X_ATOM_NET_WM_HANDLED_ICONS, 1);*/
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_USER_TIME;
   supported[supported_num++] = ECORE_X_ATOM_NET_FRAME_EXTENTS;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_PING;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_SYNC_REQUEST;
   supported[supported_num++] = ECORE_X_ATOM_NET_WM_SYNC_REQUEST_COUNTER;
   supported[supported_num++] = ECORE_X_ATOM_E_VIDEO_PARENT;
   supported[supported_num++] = ECORE_X_ATOM_E_VIDEO_POSITION;

   supported[supported_num++] = ATM_NETWM_SHOW_WINDOW_MENU;
   supported[supported_num++] = ATM_NETWM_PERFORM_BUTTON_ACTION;
   supported[supported_num++] = ATM_GTK_FRAME_EXTENTS;




   /* check for previous netwm wm and wait for it to die */
   ts = ecore_time_get();
   nwins = ecore_x_window_prop_window_get(root,
                                          ECORE_X_ATOM_NET_SUPPORTING_WM_CHECK,
                                          &win, 1);
   if ((nwins > 0) && (win != propwin))
     {
        twin = win;
        for (;; )
          {
             Ecore_X_Window selfwin = 0;

             /* check that supporting wm win points to itself to be valid */
             nwins = ecore_x_window_prop_window_get(twin,
                                                    ECORE_X_ATOM_NET_SUPPORTING_WM_CHECK,
                                                    &selfwin, 1);
             if (nwins < 1) break;
             if (selfwin != twin) break;
             /* check the wm is e */
             if (ecore_x_netwm_name_get(twin, &name))
               {
                  if (name)
                    {
                       /* if it is NOT e - don't care here as all this code is dealing with e restarts */
                       if (strcmp(name, "Enlightenment"))
                         {
                            free(name);
                            break;
                         }
                       free(name);
                    }
                  /* no name - not e - don't care */
                  else
                    break;
               }
             else
               /* can't get name - obviously not e */
               break;
             /* have we been spinning too long? 2 sec */
             if ((ecore_time_get() - ts) > 2.0)
               {
                  e_error_message_show(_("A previous instance of Enlightenment is still active\n"
                                         "on this screen. Aborting startup.\n"));
                  exit(101);
               }
             /* get/check agan */
             nwins = ecore_x_window_prop_window_get(root,
                                                    ECORE_X_ATOM_NET_SUPPORTING_WM_CHECK,
                                                    &twin, 1);
             if (nwins < 1) break;
             if (twin != win) break;
          }
     }

/*
* I don't FUCKING believe it. if we PRETEND we are Kwin - java is happy.
* why? it expects a double reparenting wm then. java insists on finding this
* out when it should be irrelevant! stupid code! I can't believe the time we
* just wasted hunting a bug that wasn't and that is due to sheer stupid
* coding (in java's awt layer that swing also uses).
*/
/* Now for more stupidity... Openoffice.org will change its look and feel
* depending on what wm it thinks there is... so if we pretend to be Kwin...
* it tries to use kde preferences, if found.
*/
/* I have disabled this now by pretending to be E16 with e16 comms. this
* means java plays nice and uses our FRAME property.. but we had to do other
* evil stuff as java EXPECTS all this at REPARENT time... i've deferred
* reparenting... i hate java!
*/
/*	     ecore_x_netwm_wm_identify(root, win, "KWin");*/
   ecore_x_netwm_wm_identify(root, propwin, "Enlightenment");
/* this makes openoffice.org read gtk settings so it doesn't look like shit */
   e_hints_openoffice_gnome_fake(root);

   ecore_x_netwm_supported_set(root, supported, supported_num);

   /* fake mwm, this might crash some ol' motif apps, if
      they still exist, but at least it makes borderless
      feature of Eterm and urxvt work... */
   ecore_x_atom_get("_MOTIF_WM_INFO");
   e_hints_e16_comms_pretend(root, propwin);
   ecore_x_sync();
#endif
}

#ifndef HAVE_WAYLAND_ONLY
/*
 * This is here so we don't have to pretend to be Kwin anymore - we pretend
 * to do old e16 style ipc. in fact we just ignore it... but set up the
 * window port anyway
 */
static void
e_hints_e16_comms_pretend(Ecore_X_Window root, Ecore_X_Window propwin)
{
   char buf[256];

   /* to help detect this is NOT e16 */
   snprintf(buf, sizeof(buf), "Enlightenment %s", VERSION);
   ecore_x_window_prop_property_set(propwin, ATM_ENLIGHTENMENT_VERSION, ECORE_X_ATOM_STRING, 8, buf, strlen(buf));
   ecore_x_window_prop_property_set(root, ATM_ENLIGHTENMENT_VERSION, ECORE_X_ATOM_STRING, 8, buf, strlen(buf));

   snprintf(buf, sizeof(buf), "WINID %8x", (int)propwin);
   ecore_x_window_prop_property_set(propwin, ATM_ENLIGHTENMENT_COMMS, ECORE_X_ATOM_STRING, 8, buf, 14);

   ecore_x_window_prop_property_set(root, ATM_ENLIGHTENMENT_COMMS, ECORE_X_ATOM_STRING, 8, buf, 14);
}
#endif

#if 0
THIS FUNCTION DOES NOTHING!!!!
EINTERN void
e_hints_manager_init(E_Manager *man)
{
   /* Set desktop count, desktop names and workarea */
   int i = 0, num = 0;
   unsigned int *areas = NULL;
   Eina_List *cl;
   E_Container *c;
   Ecore_X_Window *vroots = NULL;
   /* FIXME: Desktop names not yet implemented */
/*   char			**names; */

   e_hints_e16_comms_pretend(man);

   num = eina_list_count(man->containers);

   vroots = calloc(num, sizeof(Ecore_X_Window));
   if (!vroots) return;

/*   names = calloc(num, sizeof(char *));*/

   areas = calloc(4 * num, sizeof(unsigned int));
   if (!areas)
     {
        free(vroots);
        return;
     }

   EINA_LIST_FOREACH(man->containers, cl, c)
     {
        areas[4 * i] = c->x;
        areas[4 * i + 1] = c->y;
        areas[4 * i + 2] = c->w;
        areas[4 * i + 3] = c->h;
        vroots[i++] = c->win;
     }

#if 0
   ecore_x_netwm_desk_count_set(man->root, num);
   /* No need for workarea without desktops */
   ecore_x_netwm_desk_workareas_set(man->root, num, areas);
#endif

   free(vroots);
   free(areas);
}
#endif

E_API void
e_hints_client_list_set(void)
{
#ifdef HAVE_WAYLAND_ONLY
#else
   /* Get client count by adding client lists on all containers */
   unsigned int i = 0;
   Ecore_X_Window *clients = NULL;

   if (!e_comp_util_has_x()) return;
   if (e_comp->clients)
     {
        E_Client *ec;
        const Eina_List *ll;
        const char *name;

        clients = calloc(e_clients_count(), sizeof(Ecore_X_Window));
        EINA_LIST_FOREACH(e_comp->clients, ll, ec)
          {
             if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_X) continue;
             name = evas_object_name_get(ec->frame);
             if (!((name) && (!strcmp(name, "layer_obj"))))
               {
                  clients[i++] = e_client_util_win_get(ec);
               }
          }
     }
   ecore_x_netwm_client_list_set(e_comp->root, clients, i);
   free(clients);
#endif
}

/* Client list is already in stacking order, so this function is nearly
 * identical to the previous one */
E_API void
e_hints_client_stacking_set(void)
{
#ifdef HAVE_WAYLAND_ONLY
#else
   unsigned int c, i = 0, non_x = 0, count = 0;
   Ecore_X_Window *clients = NULL;
   const char *name;

//#define CLIENT_STACK_DEBUG
   /* Get client count */
   c = 20 + (e_clients_count() * 2);
   if (c)
     {
        E_Client *ec;
#ifdef CLIENT_STACK_DEBUG
        Eina_List *ll = NULL;
#endif
        clients = calloc(c, sizeof(Ecore_X_Window));
        E_CLIENT_FOREACH(ec)
          {
             if (e_pixmap_type_get(ec->pixmap) != E_PIXMAP_TYPE_X)
               {
                  non_x++;
                  continue;
               }
             name = evas_object_name_get(ec->frame);
             if (!((name) && (!strcmp(name, "layer_obj"))))
               {
                  clients[i++] = e_client_util_win_get(ec);
                  count++;
               }
#ifdef CLIENT_STACK_DEBUG
             ll = eina_list_append(ll, ec);
#endif
             if (i > c)
               {
                  ERR("Window list size greater than window count.");
                  break;
               }
          }
     }
   /* XXX: it should be "more correct" to be setting the stacking atom as "windows per root"
    * since any apps using it are probably not going to want windows from other screens
    * to be returned in the list
    */
   if (i <= c)
     ecore_x_netwm_client_list_stacking_set(e_comp->root, clients, count);
   free(clients);
#endif
}

E_API void
e_hints_active_window_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_comp_util_has_x()) return;
   if (ec && (e_pixmap_type_get(ec->pixmap) == E_PIXMAP_TYPE_X))
     ecore_x_netwm_client_active_set(e_comp->root, e_client_util_win_get(ec));
   else
     ecore_x_netwm_client_active_set(e_comp->root, 0);
#endif
}

EINTERN void
e_hints_window_init(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   E_Remember *rem = NULL;

   if (!e_client_has_xwindow(ec)) return;
   if (ec->remember)
     rem = ec->remember;

   if ((rem) && (rem->apply & E_REMEMBER_APPLY_LAYER))
     {
        ec->layer = rem->prop.layer;
        evas_object_layer_set(ec->frame, ec->layer);
     }
   else
     {
        if (!ec->lock_client_stacking)
          {
             if (ec->netwm.type == E_WINDOW_TYPE_DESKTOP)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_DESKTOP);
             else if (ec->netwm.state.stacking == E_STACKING_BELOW)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_BELOW);
             else if (ec->netwm.state.stacking == E_STACKING_ABOVE)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_ABOVE);
             else if (ec->netwm.type == E_WINDOW_TYPE_DOCK)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_ABOVE);
             else if (!evas_object_layer_get(ec->frame)) //impossible?
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
          }
        else
          evas_object_raise(ec->frame);
     }

   if ((ec->parent) && (e_config->transient.layer))
     evas_object_layer_set(ec->frame, ec->parent->layer);

#if 0
   /* Ignore this, E has incompatible desktop setup */
   if (ecore_x_netwm_desktop_get(e_client_util_win_get(ec), &ec->netwm.desktop))
     {
        if (ec->netwm.desktop == 0xffffffff)
          {
             e_client_stick(ec);
          }
        else if (ec->netwm.desktop < (ec->zone->desk_x_count * ec->zone->desk_y_count))
          {
             E_Desk *desk;

             desk = e_desk_at_pos_get(ec->zone, ec->netwm.desktop);
             if (desk)
               e_client_desk_set(ec, desk);
          }
        else
          {
             /* Update netwm desktop with current desktop */
             e_hints_window_desktop_set(ec);
          }
     }
   else
     {
        /* Update netwm desktop with current desktop */
        e_hints_window_desktop_set(ec);
     }
#endif

   if (ec->netwm.state.sticky)
     {
        if (!ec->lock_client_sticky)
          e_client_stick(ec);
        else
          e_hints_window_sticky_set(ec, 0);
     }
   if (ec->netwm.state.shaded)
     {
        if (!ec->lock_client_shade)
          e_client_shade(ec, e_hints_window_shade_direction_get(ec));
        else
          e_hints_window_shaded_set(ec, 0);
     }
   if ((ec->netwm.state.maximized_v) && (ec->netwm.state.maximized_h))
     {
        if (!ec->lock_client_maximize)
          {
             e_hints_window_size_get(ec);
             e_client_maximize(ec, e_config->maximize_policy);
          }
        else
          e_hints_window_maximized_set(ec, 0, 0);
     }
   else if (ec->netwm.state.maximized_h)
     {
        if (!ec->lock_client_maximize)
          {
             e_hints_window_size_get(ec);
             e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | E_MAXIMIZE_HORIZONTAL);
          }
        else
          e_hints_window_maximized_set(ec, 0, 0);
     }
   else if (ec->netwm.state.maximized_v)
     {
        if (!ec->lock_client_maximize)
          {
             e_hints_window_size_get(ec);
             do
               {
                  if (ec->client.w == (ec->zone->w / 2))
                    {
                       if (!ec->client.x)
                         {
                            e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | E_MAXIMIZE_LEFT);
                            break;
                         }
                       else if (ec->client.x == ec->zone->w / 2)
                         {
                            e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | E_MAXIMIZE_RIGHT);
                            break;
                         }
                    }
                  e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) | E_MAXIMIZE_VERTICAL);
               } while (0);
          }
        else
          e_hints_window_maximized_set(ec, 0, 0);
     }
   if (ec->netwm.state.fullscreen)
     {
        if (!ec->lock_client_fullscreen)
          {
             e_hints_window_size_get(ec);
             e_client_fullscreen(ec, e_config->fullscreen_policy);
          }
        else
          e_hints_window_fullscreen_set(ec, 0);
     }
   if ((ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_ICONIC) &&
       (ec->netwm.state.hidden))
     {
        if (!ec->lock_client_iconify)
          e_client_iconify(ec);
        else
          e_hints_window_visible_set(ec);
     }
   else if ((ec->parent) && (e_config->transient.iconify) && (ec->parent->iconic))
     e_client_iconify(ec);
   /* If a window isn't iconic, and is one the current desk,
    * show it! */
   else if (ec->desk == e_desk_current_get(ec->zone))
     {
        /* ...but only if it's supposed to be shown */
        if (ec->re_manage && (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN))
          {
             ec->changes.visible = 1;
             ec->visible = 1;
          }
     }
   /* e hints */
/*
   if (ec->e.state.centered)
     {
        e_client_center(ec);
     }
 */
#endif
}

E_API void
e_hints_window_state_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   Ecore_X_Window_State state[10];
   int num = 0;

   if (!e_client_has_xwindow(ec)) return;
   if (ec->netwm.state.modal)
     state[num++] = ECORE_X_WINDOW_STATE_MODAL;
   if (ec->netwm.state.sticky)
     state[num++] = ECORE_X_WINDOW_STATE_STICKY;
   if (ec->netwm.state.maximized_v)
     state[num++] = ECORE_X_WINDOW_STATE_MAXIMIZED_VERT;
   if (ec->netwm.state.maximized_h)
     state[num++] = ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ;
   if (ec->netwm.state.shaded)
     state[num++] = ECORE_X_WINDOW_STATE_SHADED;
   if (ec->internal)
     {
        if (ec->netwm.state.skip_taskbar)
          state[num++] = ECORE_X_WINDOW_STATE_SKIP_TASKBAR;
        if (ec->netwm.state.skip_pager)
          state[num++] = ECORE_X_WINDOW_STATE_SKIP_PAGER;
     }
   if (ec->netwm.state.hidden)
     state[num++] = ECORE_X_WINDOW_STATE_HIDDEN;
   if (ec->netwm.state.fullscreen)
     state[num++] = ECORE_X_WINDOW_STATE_FULLSCREEN;

   switch (ec->netwm.state.stacking)
     {
      case E_STACKING_ABOVE:
        state[num++] = ECORE_X_WINDOW_STATE_ABOVE;
        break;

      case E_STACKING_BELOW:
        state[num++] = ECORE_X_WINDOW_STATE_BELOW;
        break;

      case E_STACKING_NONE:
      default:
        break;
     }
   ecore_x_netwm_window_state_set(e_client_util_win_get(ec), state, num);
   if (!ec->internal)
     {
        num = 0;
        if (ec->netwm.state.skip_taskbar)
          state[num++] = ECORE_X_WINDOW_STATE_SKIP_TASKBAR;
        if (ec->netwm.state.skip_pager)
          state[num++] = ECORE_X_WINDOW_STATE_SKIP_PAGER;
        if (num)
          ecore_x_netwm_window_state_set(e_client_util_win_get(ec), state, num);
     }
#endif
}

E_API void
e_hints_allowed_action_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   Ecore_X_Action action[10];
   int num = 0;

   if (!e_client_has_xwindow(ec)) return;
   if (ec->netwm.action.move)
     action[num++] = ECORE_X_ACTION_MOVE;
   if (ec->netwm.action.resize)
     action[num++] = ECORE_X_ACTION_RESIZE;
   if (ec->netwm.action.minimize)
     action[num++] = ECORE_X_ACTION_MINIMIZE;
   if (ec->netwm.action.shade)
     action[num++] = ECORE_X_ACTION_SHADE;
   if (ec->netwm.action.stick)
     action[num++] = ECORE_X_ACTION_STICK;
   if (ec->netwm.action.maximized_h)
     action[num++] = ECORE_X_ACTION_MAXIMIZE_HORZ;
   if (ec->netwm.action.maximized_v)
     action[num++] = ECORE_X_ACTION_MAXIMIZE_VERT;
   if (ec->netwm.action.fullscreen)
     action[num++] = ECORE_X_ACTION_FULLSCREEN;
   if (ec->netwm.action.change_desktop)
     action[num++] = ECORE_X_ACTION_CHANGE_DESKTOP;
   if (ec->netwm.action.close)
     action[num++] = ECORE_X_ACTION_CLOSE;

   ecore_x_netwm_allowed_action_set(e_client_util_win_get(ec), action, num);
#endif
}

E_API void
e_hints_window_type_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   ecore_x_netwm_window_type_set(e_client_util_win_get(ec),
                                 (Ecore_X_Window_Type)ec->netwm.type);
#endif
}

E_API void
e_hints_window_type_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   Ecore_X_Window_Type *types = NULL;
   int num, i, j, k;

   if (!e_client_has_xwindow(ec)) return;
   num = ecore_x_netwm_window_types_get(e_client_util_win_get(ec), &types);
   if (ec->netwm.extra_types)
     {
        free(ec->netwm.extra_types);
        ec->netwm.extra_types = NULL;
        ec->netwm.extra_types_num = 0;
     }
   if (num == 0)
     ec->netwm.type = E_WINDOW_TYPE_UNKNOWN;
   else
     {
        j = 0;
        ec->netwm.type = (E_Window_Type)types[j];
        j++;
        while ((j < num) &&
               (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN))
          {
             ec->netwm.type = (E_Window_Type)types[j];
             j++;
          }
        if (num > j)
          {
             ec->netwm.extra_types =
               malloc((num - j) * sizeof(Ecore_X_Window_Type));
             if (ec->netwm.extra_types)
               {
                  for (i = j, k = 0; i < num; i++, k++)
                    ec->netwm.extra_types[k] = (E_Window_Type)types[i];
                  ec->netwm.extra_types_num = num - j;
               }
          }
        free(types);
        evas_object_pass_events_set(ec->frame, ec->netwm.type == E_WINDOW_TYPE_DND);
     }
   ec->dialog = (ec->netwm.type == E_WINDOW_TYPE_DIALOG);
   if (!ec->dialog)
     ec->tooltip = (ec->netwm.type == E_WINDOW_TYPE_TOOLTIP);
#endif
}

#ifdef E_COMP_X_H
E_API void
e_hints_window_state_update(E_Client *ec, int state, int action)
{
   if (!e_client_has_xwindow(ec)) return;
   switch (state)
     {
      case ECORE_X_WINDOW_STATE_ICONIFIED:
        if (action != ECORE_X_WINDOW_STATE_ACTION_ADD) return;
#ifndef HAVE_WAYLAND_ONLY
        if (ec->icccm.state == ECORE_X_WINDOW_STATE_HINT_ICONIC) return;
#endif
        if (ec->lock_client_iconify) return;
        e_client_iconify(ec);
        break;

      case ECORE_X_WINDOW_STATE_MODAL:
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             if (ec->netwm.state.modal)
               {
                  ec->netwm.state.modal = 0;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             if (!ec->netwm.state.modal)
               {
                  ec->netwm.state.modal = 1;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             ec->netwm.state.modal = !ec->netwm.state.modal;
             ec->netwm.update.state = 1;
             EC_CHANGED(ec);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_STICKY:
        if (ec->lock_client_sticky) return;
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             e_client_unstick(ec);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             e_client_stick(ec);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             if (ec->sticky)
               e_client_unstick(ec);
             else
               e_client_stick(ec);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_MAXIMIZED_VERT:
      case ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ:
      case INT_MAX:
      {
         int max[] =
         {
            [ECORE_X_WINDOW_STATE_MAXIMIZED_VERT] = E_MAXIMIZE_VERTICAL,
            [ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ] = E_MAXIMIZE_HORIZONTAL,
         };
         E_Maximize m;

         if (state > ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ)
           m = E_MAXIMIZE_BOTH;
         else
           m = max[state];
         if (ec->lock_client_maximize) return;
         switch (action)
           {
            case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
              if ((ec->maximized & m) == m)
                e_client_unmaximize(ec, m);
              break;

            case ECORE_X_WINDOW_STATE_ACTION_ADD:
              if ((ec->maximized & m) == m) break;
              ec->changes.need_maximize = 1;
              ec->maximized &= ~E_MAXIMIZE_TYPE;
              ec->maximized |= (e_config->maximize_policy & E_MAXIMIZE_TYPE) | m;
              EC_CHANGED(ec);
              break;

            case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
              if ((ec->maximized & m) == m)
                {
                   e_client_unmaximize(ec, m);
                   break;
                }
              ec->changes.need_maximize = 1;
              ec->maximized &= ~E_MAXIMIZE_TYPE;
              ec->maximized |= (e_config->maximize_policy & E_MAXIMIZE_TYPE) | m;
              EC_CHANGED(ec);
              break;
           }
      }
        break;

      case ECORE_X_WINDOW_STATE_SHADED:
        if (ec->lock_client_shade) return;
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             e_client_unshade(ec, e_hints_window_shade_direction_get(ec));
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             e_client_shade(ec, e_hints_window_shade_direction_get(ec));
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             if (ec->shaded)
               e_client_unshade(ec, e_hints_window_shade_direction_get(ec));
             else
               e_client_shade(ec, e_hints_window_shade_direction_get(ec));
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_SKIP_TASKBAR:
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             if (ec->netwm.state.skip_taskbar)
               {
                  ec->netwm.state.skip_taskbar = 0;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
                  e_client_prop_misc_changed(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             if (!ec->netwm.state.skip_taskbar)
               {
                  ec->netwm.state.skip_taskbar = 1;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
                  e_client_prop_misc_changed(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             ec->netwm.state.skip_taskbar = !ec->netwm.state.skip_taskbar;
             ec->netwm.update.state = 1;
             EC_CHANGED(ec);
             e_client_prop_misc_changed(ec);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_SKIP_PAGER:
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             if (ec->netwm.state.skip_pager)
               {
                  ec->netwm.state.skip_pager = 0;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
                  e_client_prop_misc_changed(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             if (!ec->netwm.state.skip_pager)
               {
                  ec->netwm.state.skip_pager = 1;
                  ec->netwm.update.state = 1;
                  EC_CHANGED(ec);
                  e_client_prop_misc_changed(ec);
               }
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             ec->netwm.state.skip_pager = !ec->netwm.state.skip_pager;
             ec->netwm.update.state = 1;
             EC_CHANGED(ec);
             e_client_prop_misc_changed(ec);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_HIDDEN:
        /* XXX: fixme */
        break;

      case ECORE_X_WINDOW_STATE_FULLSCREEN:
        if (ec->lock_client_fullscreen) return;
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             e_client_unfullscreen(ec);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             e_client_fullscreen(ec, e_config->fullscreen_policy);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             if (ec->fullscreen)
               e_client_unfullscreen(ec);
             else
               e_client_fullscreen(ec, e_config->fullscreen_policy);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_ABOVE:
        if (ec->lock_client_stacking) return;
        /* FIXME: Should this require that BELOW is set to 0 first, or just
         * do it? */
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_ABOVE);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             if (ec->layer == E_LAYER_CLIENT_ABOVE)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
             else
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_ABOVE);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_BELOW:
        if (ec->lock_client_stacking) return;
        /* FIXME: Should this require that ABOVE is set to 0 first, or just
         * do it? */
        switch (action)
          {
           case ECORE_X_WINDOW_STATE_ACTION_REMOVE:
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_ADD:
             evas_object_layer_set(ec->frame, E_LAYER_CLIENT_BELOW);
             break;

           case ECORE_X_WINDOW_STATE_ACTION_TOGGLE:
             if (ec->layer == E_LAYER_CLIENT_BELOW)
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
             else
               evas_object_layer_set(ec->frame, E_LAYER_CLIENT_BELOW);
             break;
          }
        break;

      case ECORE_X_WINDOW_STATE_DEMANDS_ATTENTION:
        /* FIXME */
        break;

      case ECORE_X_WINDOW_STATE_UNKNOWN:
        /* Ignore */
        break;
     }
}
#endif

E_API void
e_hints_window_state_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   unsigned int i, num, was_here = 0;
   Ecore_X_Window_State *state;

   if (!e_client_has_xwindow(ec)) return;
   ec->netwm.state.modal = 0;
   ec->netwm.state.sticky = 0;
   ec->netwm.state.maximized_v = 0;
   ec->netwm.state.maximized_h = 0;
   ec->netwm.state.shaded = 0;
   ec->netwm.state.skip_taskbar = 0;
   ec->netwm.state.skip_pager = 0;
   ec->netwm.state.hidden = 0;
   ec->netwm.state.fullscreen = 0;
   ec->netwm.state.stacking = 0;

   if (ecore_x_window_prop_card32_get(e_client_util_win_get(ec),
                                      E_ATOM_E_WAS_HERE,
                                      &was_here, 1) != 1)
     {
        unsigned int val = 1;

        was_here = 0;
        ecore_x_window_prop_card32_set(e_client_util_win_get(ec),
                                       E_ATOM_E_WAS_HERE,
                                       &val, 1);
     }
   ecore_x_netwm_window_state_get(e_client_util_win_get(ec), &state, &num);
   if (state)
     {
        for (i = 0; i < num; i++)
          {
             switch (state[i])
               {
                case ECORE_X_WINDOW_STATE_ICONIFIED:
                  /* Ignore */
                  break;

                case ECORE_X_WINDOW_STATE_MODAL:
                  ec->netwm.state.modal = 1;
                  break;

                case ECORE_X_WINDOW_STATE_STICKY:
                  ec->netwm.state.sticky = 1;
                  break;

                case ECORE_X_WINDOW_STATE_MAXIMIZED_VERT:
                  ec->netwm.state.maximized_v = 1;
                  break;

                case ECORE_X_WINDOW_STATE_MAXIMIZED_HORZ:
                  ec->netwm.state.maximized_h = 1;
                  break;

                case ECORE_X_WINDOW_STATE_SHADED:
                  ec->netwm.state.shaded = 1;
                  break;

                case ECORE_X_WINDOW_STATE_SKIP_TASKBAR:
                  ec->netwm.state.skip_taskbar = 1;
                  break;

                case ECORE_X_WINDOW_STATE_SKIP_PAGER:
                  ec->netwm.state.skip_pager = 1;
                  break;

                case ECORE_X_WINDOW_STATE_HIDDEN:
                  if (!was_here)
                    ec->netwm.state.hidden = 1;
                  break;

                case ECORE_X_WINDOW_STATE_FULLSCREEN:
                  ec->netwm.state.fullscreen = 1;
                  break;

                case ECORE_X_WINDOW_STATE_ABOVE:
                  ec->netwm.state.stacking = E_STACKING_ABOVE;
                  break;

                case ECORE_X_WINDOW_STATE_BELOW:
                  ec->netwm.state.stacking = E_STACKING_BELOW;
                  break;

                case ECORE_X_WINDOW_STATE_DEMANDS_ATTENTION:
                  /* FIXME */
                  break;

                case ECORE_X_WINDOW_STATE_UNKNOWN:
                  /* Ignore */
                  break;
               }
          }
        free(state);
     }
#endif
}

E_API void
e_hints_allowed_action_update(E_Client *ec, int action)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)action;
#else
   if (!e_client_has_xwindow(ec)) return;
   switch (action)
     {
      case ECORE_X_ACTION_MOVE:
        break;

      case ECORE_X_ACTION_RESIZE:
        break;

      case ECORE_X_ACTION_MINIMIZE:
        break;

      case ECORE_X_ACTION_SHADE:
        break;

      case ECORE_X_ACTION_STICK:
        break;

      case ECORE_X_ACTION_MAXIMIZE_HORZ:
        break;

      case ECORE_X_ACTION_MAXIMIZE_VERT:
        break;

      case ECORE_X_ACTION_FULLSCREEN:
        break;

      case ECORE_X_ACTION_CHANGE_DESKTOP:
        break;

      case ECORE_X_ACTION_CLOSE:
        break;

      case ECORE_X_ACTION_ABOVE:
        break;

      case ECORE_X_ACTION_BELOW:
        break;
     }
#endif
}

E_API void
e_hints_allowed_action_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   Ecore_X_Action *action;
   unsigned int i;
   unsigned int num;

   if (!e_client_has_xwindow(ec)) return;
   ec->netwm.action.move = 0;
   ec->netwm.action.resize = 0;
   ec->netwm.action.minimize = 0;
   ec->netwm.action.shade = 0;
   ec->netwm.action.stick = 0;
   ec->netwm.action.maximized_h = 0;
   ec->netwm.action.maximized_v = 0;
   ec->netwm.action.fullscreen = 0;
   ec->netwm.action.change_desktop = 0;
   ec->netwm.action.close = 0;

   ecore_x_netwm_allowed_action_get(e_client_util_win_get(ec), &action, &num);
   if (action)
     {
        for (i = 0; i < num; i++)
          {
             switch (action[i])
               {
                case ECORE_X_ACTION_MOVE:
                  ec->netwm.action.move = 1;
                  break;

                case ECORE_X_ACTION_RESIZE:
                  ec->netwm.action.resize = 1;
                  break;

                case ECORE_X_ACTION_MINIMIZE:
                  ec->netwm.action.minimize = 1;
                  break;

                case ECORE_X_ACTION_SHADE:
                  ec->netwm.action.shade = 1;
                  break;

                case ECORE_X_ACTION_STICK:
                  ec->netwm.action.stick = 1;
                  break;

                case ECORE_X_ACTION_MAXIMIZE_HORZ:
                  ec->netwm.action.maximized_h = 1;
                  break;

                case ECORE_X_ACTION_MAXIMIZE_VERT:
                  ec->netwm.action.maximized_v = 1;
                  break;

                case ECORE_X_ACTION_FULLSCREEN:
                  ec->netwm.action.fullscreen = 1;
                  break;

                case ECORE_X_ACTION_CHANGE_DESKTOP:
                  ec->netwm.action.change_desktop = 1;
                  break;

                case ECORE_X_ACTION_CLOSE:
                  ec->netwm.action.close = 1;
                  break;

                case ECORE_X_ACTION_ABOVE:
                  break;

                case ECORE_X_ACTION_BELOW:
                  break;
               }
          }
        free(action);
     }
#endif
}

#ifndef HAVE_WAYLAND_ONLY
static void
_e_hints_process_wakeup(E_Client *ec)
{
   // check for e vkbd state property - if its there, it's efl, so sending a
   // a fake sigchild to wake things up os just fine
   if (!ec->vkbd.have_property) return;
   if (ec->netwm.pid <= 0) return;
# ifdef SIGCHLD
   kill(ec->netwm.pid, SIGCHLD);
# endif
}
#endif

E_API void
e_hints_window_visible_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   if ((ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN) &&
       (!ec->frozen))
     {
        ec->icccm.state = ECORE_X_WINDOW_STATE_HINT_NORMAL;
        ecore_x_icccm_state_set(e_client_util_win_get(ec), ECORE_X_WINDOW_STATE_HINT_NORMAL);
     }
   else if (ec->frozen)
     {
        if (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
          ec->icccm.state = ECORE_X_WINDOW_STATE_HINT_NORMAL;
        ecore_x_icccm_state_set(e_client_util_win_get(ec), ECORE_X_WINDOW_STATE_HINT_ICONIC);
     }
   if (ec->netwm.state.hidden)
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.hidden = 0;
        EC_CHANGED(ec);
     }
   _e_hints_process_wakeup(ec);
#endif
}

E_API void
e_hints_window_iconic_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   if (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
     {
        ec->icccm.state = ECORE_X_WINDOW_STATE_HINT_ICONIC;
        ecore_x_icccm_state_set(e_client_util_win_get(ec), ECORE_X_WINDOW_STATE_HINT_ICONIC);
     }
   else if (ec->frozen)
     {
        if (ec->icccm.state != ECORE_X_WINDOW_STATE_HINT_WITHDRAWN)
          ec->icccm.state = ECORE_X_WINDOW_STATE_HINT_ICONIC;
        ecore_x_icccm_state_set(e_client_util_win_get(ec), ECORE_X_WINDOW_STATE_HINT_ICONIC);
     }
   if (!ec->netwm.state.hidden)
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.hidden = 1;
        EC_CHANGED(ec);
     }
   _e_hints_process_wakeup(ec);
#endif
}

E_API void
e_hints_window_hidden_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   if (ec->netwm.state.hidden)
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.hidden = 0;
        EC_CHANGED(ec);
     }
   ecore_x_window_prop_property_del(e_client_util_win_get(ec), ECORE_X_ATOM_WM_STATE);
   _e_hints_process_wakeup(ec);
#endif
}

E_API void
e_hints_window_shaded_set(E_Client *ec, int on)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)on;
#else
   if (!e_client_has_xwindow(ec)) return;
   if ((!ec->netwm.state.shaded) && (on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.shaded = 1;
        if (ec->hacks.iconic_shading)
          e_hints_window_iconic_set(ec);
        EC_CHANGED(ec);
     }
   else if ((ec->netwm.state.shaded) && (!on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.shaded = 0;
        if (ec->hacks.iconic_shading)
          e_hints_window_visible_set(ec);
        EC_CHANGED(ec);
     }
   _e_hints_process_wakeup(ec);
#endif
}

E_API void
e_hints_window_shade_direction_set(E_Client *ec, E_Direction dir)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)dir;
#else
   if (!e_client_has_xwindow(ec)) return;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_SHADE_DIRECTION, &dir, 1);
#endif
}

E_API E_Direction
e_hints_window_shade_direction_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   int ret;
   E_Direction dir;

   if (!e_client_has_xwindow(ec)) return E_DIRECTION_UP;
   ret = ecore_x_window_prop_card32_get(e_client_util_win_get(ec),
                                        E_ATOM_SHADE_DIRECTION,
                                        &dir, 1);
   if (ret == 1)
     return dir;
#endif
   return E_DIRECTION_UP;
}

E_API void
e_hints_window_size_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   unsigned int sizes[4];

   if (!e_client_has_xwindow(ec)) return;
   sizes[0] = ec->x;
   sizes[1] = ec->y;
   sizes[2] = ec->w;
   sizes[3] = ec->h;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_BORDER_SIZE, sizes, 4);
#endif
}

E_API void
e_hints_window_size_unset(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   ecore_x_window_prop_property_del(e_client_util_win_get(ec), E_ATOM_BORDER_SIZE);
#endif
}

E_API int
e_hints_window_size_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   int ret;
   unsigned int sizes[4];

   if (!e_client_has_xwindow(ec)) return 0;
   memset(sizes, 0, sizeof(sizes));
   ret = ecore_x_window_prop_card32_get(e_client_util_win_get(ec), E_ATOM_BORDER_SIZE,
                                        sizes, 4);
   if (ret != 4)
     return 0;

   ec->x = sizes[0];
   ec->y = sizes[1];
   ec->w = sizes[2];
   ec->h = sizes[3];
#endif

   return 1;
}

E_API void
e_hints_window_maximized_set(E_Client *ec, int horizontal, int vertical)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)horizontal;
   (void)vertical;
#else
   if (!e_client_has_xwindow(ec)) return;
   if ((horizontal) && (!ec->netwm.state.maximized_h))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.maximized_h = 1;
        EC_CHANGED(ec);
     }
   else if ((!horizontal) && (ec->netwm.state.maximized_h))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.maximized_h = 0;
        EC_CHANGED(ec);
     }
   if ((vertical) && (!ec->netwm.state.maximized_v))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.maximized_v = 1;
        EC_CHANGED(ec);
     }
   else if ((!vertical) && (ec->netwm.state.maximized_v))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.maximized_v = 0;
        EC_CHANGED(ec);
     }
#endif
}

E_API void
e_hints_window_fullscreen_set(E_Client *ec,
                              int on)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)on;
#else
   if (!e_client_has_xwindow(ec)) return;
   if ((!ec->netwm.state.fullscreen) && (on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.fullscreen = 1;
        EC_CHANGED(ec);
     }
   else if ((ec->netwm.state.fullscreen) && (!on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.fullscreen = 0;
        EC_CHANGED(ec);
     }
#endif
}

E_API void
e_hints_window_sticky_set(E_Client *ec, int on)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)on;
#else
   if (!e_client_has_xwindow(ec)) return;
   if ((!ec->netwm.state.sticky) && (on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.sticky = 1;
        EC_CHANGED(ec);
     }
   else if ((ec->netwm.state.sticky) && (!on))
     {
        ec->netwm.update.state = 1;
        ec->netwm.state.sticky = 0;
        EC_CHANGED(ec);
     }
#endif
}

E_API void
e_hints_window_stacking_set(E_Client *ec, E_Stacking stacking)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
   (void)stacking;
#else
   if (!e_client_has_xwindow(ec)) return;
   if (ec->netwm.state.stacking == stacking) return;
   ec->netwm.update.state = 1;
   ec->netwm.state.stacking = stacking;
   EC_CHANGED(ec);
#endif
}

E_API void
e_hints_window_desktop_set(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   /* This function is only called when really changing desktop,
    * so just set the property and don't care about the roundtrip.
    */
   unsigned int deskpos[2];

   /* if valgrind complains here it is complaining ec->netwm.desktop
    * is an uninitialised variable - but it isn't. it can't be. its part of
    * a calloc()'d struct and thus has to have been set to 0. hell even
    * e_client.c explicitly sets it to 0 on creation of the border object.
    */
   if (!e_client_has_xwindow(ec)) return;
   deskpos[0] = ec->desk->x;
   deskpos[1] = ec->desk->y;
   ecore_x_window_prop_card32_set(e_client_util_win_get(ec), E_ATOM_DESK, deskpos, 2);

#if 0
   ecore_x_netwm_desktop_set(e_client_util_win_get(ec), current);
#endif
   ec->netwm.desktop = (ec->desk->y * ec->zone->desk_x_count) + ec->desk->x;
#endif
}

E_API void
e_hints_window_e_state_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   /* Remember to update the count if we add more states! */
   Ecore_X_Atom state[1];
   int num = 0, i = 0;
   int size = 0;

   if (!e_client_has_xwindow(ec)) return;
   memset(state, 0, sizeof(state));

   /* ugly, but avoids possible future overflow if more states are added */
   size = (sizeof(state) / sizeof(state[0]));

   num =
     ecore_x_window_prop_card32_get(e_client_util_win_get(ec), E_ATOM_WINDOW_STATE,
                                    state, size);
   if (!num) return;

   for (i = 0; (i < num) && (i < size); i++)
     {
        if (state[i] == E_ATOM_WINDOW_STATE_CENTERED)
          ec->e.state.centered = 1;
     }
#endif
}

E_API void
e_hints_window_e_state_set(E_Client *ec EINA_UNUSED)
{
   /* TODO */
}

E_API void
e_hints_window_qtopia_soft_menu_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   unsigned int val;

   if (!e_client_has_xwindow(ec)) return;
   if (ecore_x_window_prop_card32_get(e_client_util_win_get(ec), ATM__QTOPIA_SOFT_MENU, &val, 1))
     ec->qtopia.soft_menu = val;
   else
     ec->qtopia.soft_menu = 0;
#endif
}

E_API void
e_hints_window_qtopia_soft_menus_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   unsigned int val;

   if (!e_client_has_xwindow(ec)) return;
   if (ecore_x_window_prop_card32_get(e_client_util_win_get(ec), ATM__QTOPIA_SOFT_MENUS, &val, 1))
     ec->qtopia.soft_menus = val;
   else
     ec->qtopia.soft_menus = 0;
#endif
}

E_API void
e_hints_window_steam_game_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   unsigned int val;

   if (!e_client_has_xwindow(ec)) return;
   if (ecore_x_window_prop_card32_get(e_client_util_win_get(ec), ATM_STEAM_GAME, &val, 1))
     ec->steam.steam_game_id = val;
   else
     ec->steam.steam_game_id = 0;
#endif
}

E_API void
e_hints_window_virtual_keyboard_state_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   Ecore_X_Atom atom = 0;

   if (!e_client_has_xwindow(ec)) return;
   ec->vkbd.state = ecore_x_e_virtual_keyboard_state_get(e_client_util_win_get(ec));
   if (ecore_x_window_prop_atom_get(e_client_util_win_get(ec),
                                    ECORE_X_ATOM_E_VIRTUAL_KEYBOARD_STATE,
                                    &atom, 1))
     ec->vkbd.have_property = 1;
   else
     ec->vkbd.have_property = 0;
#endif
}

E_API void
e_hints_window_virtual_keyboard_get(E_Client *ec)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)ec;
#else
   if (!e_client_has_xwindow(ec)) return;
   ec->vkbd.vkbd = ecore_x_e_virtual_keyboard_get(e_client_util_win_get(ec));
#endif
}

static void
e_hints_openoffice_gnome_fake(Ecore_Window root)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)root;
#else
   const char *string = "ATM_GNOME_SM_PROXY";

   ecore_x_window_prop_property_set(root, ATM_GNOME_SM_PROXY, ECORE_X_ATOM_STRING,
                                    8, (void *)string, strlen(string));
#endif
}

#if 0
static void
e_hints_openoffice_kde_fake(Ecore_Window root)
{
#ifdef HAVE_WAYLAND_ONLY
   (void)root;
#else
   Ecore_X_Window win2;

   win2 = ecore_x_window_new(root, -20, -20, 1, 1);
   ecore_x_netwm_wm_identify(root, win2, "KWin");
#endif
}
#endif

E_API void
e_hints_scale_update(void)
{
#ifdef HAVE_WAYLAND_ONLY
   Eina_List *l;
   E_Comp_Wl_Output *output;

   EINA_LIST_FOREACH(e_comp_wl->outputs, l, output)
     output->scale = e_scale;

#else
   unsigned int scale = e_scale * 1000;

   if (e_comp->root)
     ecore_x_window_prop_card32_set(e_comp->root, ATM_ENLIGHTENMENT_SCALE, &scale, 1);
#endif
}


#ifdef HAVE_WAYLAND
static Eina_List *aux_hints_supported = NULL;

static E_Comp_Client_Data *
e_pixmap_cdata_get(E_Pixmap *cp)
{
   return e_pixmap_client_get(cp)->comp_data;
}

E_API const Eina_List *
e_hints_aux_hint_supported_add(const char *hint)
{
   Eina_List *l;
   const char *supported;

   EINA_LIST_FOREACH(aux_hints_supported, l, supported)
     {
        if (!strcmp(supported, hint))
          return aux_hints_supported;
     }

   aux_hints_supported = eina_list_append(aux_hints_supported, hint);

   return aux_hints_supported;
}

E_API const Eina_List *
e_hints_aux_hint_supported_del(const char *hint)
{
   Eina_List *l;
   const char *supported;

   EINA_LIST_FOREACH(aux_hints_supported, l, supported)
     {
        if (!strcmp(supported, hint))
          {
             aux_hints_supported = eina_list_remove(aux_hints_supported, hint);
             break;
          }
     }

   return aux_hints_supported;
}

E_API const Eina_List *
e_hints_aux_hint_supported_get(void)
{
   return aux_hints_supported;
}

E_API Eina_Bool
e_hints_aux_hint_add(E_Client *ec, int32_t id, const char *name, const char *val)
{
   if (!ec) return EINA_FALSE;
   return e_hints_aux_hint_add_with_pixmap(ec->pixmap, id, name, val, -1);
}

E_API Eina_Bool
e_hints_aux_hint_add_fd(E_Client *ec, int32_t id, const char *name, int32_t fd)
{
   if (!ec) return EINA_FALSE;
   return e_hints_aux_hint_add_with_pixmap(ec->pixmap, id, name, NULL, fd);
}

E_API Eina_Bool
e_hints_aux_hint_change(E_Client *ec, int32_t id, const char *val)
{
   if (!ec) return EINA_FALSE;
   return e_hints_aux_hint_change_with_pixmap(ec->pixmap, id, val, -1);
}

E_API Eina_Bool
e_hints_aux_hint_change_fd(E_Client *ec, int32_t id, int32_t fd)
{
   if (!ec) return EINA_FALSE;
   return e_hints_aux_hint_change_with_pixmap(ec->pixmap, id, NULL, fd);
}

E_API Eina_Bool
e_hints_aux_hint_del(E_Client *ec, int32_t id)
{
   if (!ec) return EINA_FALSE;
   return e_hints_aux_hint_del_with_pixmap(ec->pixmap, id);
}

E_API const char *
e_hints_aux_hint_value_get(E_Client *ec, const char *name)
{
   if (!ec) return NULL;
   return e_hints_aux_hint_value_get_with_pixmap(ec->pixmap, name);
}

E_API Eina_Bool
e_hints_aux_hint_add_with_pixmap(E_Pixmap *cp, int32_t id, const char *name, const char *val, int32_t fd)
{
   E_Comp_Wl_Client_Data *cdata;
   Eina_Bool found = EINA_FALSE;
   E_Comp_Wl_Aux_Hint *hint;
   Eina_List *l;

   if ((!cp) || (!val) || (!name)) return EINA_FALSE;
   cdata = (E_Comp_Wl_Client_Data*)e_pixmap_cdata_get(cp);
   if (!cdata) return EINA_FALSE;

   EINA_LIST_FOREACH(cdata->aux_hint.hints, l, hint)
     {
        if (hint->id == id)
          {
             if (strcmp(hint->val, val) != 0)
               {
                  eina_stringshare_del(hint->val);
                  hint->val = eina_stringshare_add(val);
                  hint->fd = fd;
                  hint->changed = EINA_TRUE;
                  if (hint->deleted)
                    hint->deleted = EINA_FALSE;
                  cdata->aux_hint.changed = 1;
               }
             found = EINA_TRUE;
             break;
          }
     }

   if (!found)
     {
        hint = E_NEW(E_Comp_Wl_Aux_Hint, 1);
        EINA_SAFETY_ON_NULL_RETURN_VAL(hint, EINA_FALSE);

        memset(hint, 0, sizeof(E_Comp_Wl_Aux_Hint));

        hint->id = id;
        hint->hint = eina_stringshare_add(name);
        hint->val = eina_stringshare_add(val);
        hint->fd = fd;
        hint->changed = EINA_TRUE;
        hint->deleted = EINA_FALSE;
        cdata->aux_hint.hints = eina_list_append(cdata->aux_hint.hints, hint);
        cdata->aux_hint.changed = 1;
     }

   if (!found)
     return EINA_TRUE;

   return EINA_FALSE;
}

E_API Eina_Bool
e_hints_aux_hint_change_with_pixmap(E_Pixmap *cp, int32_t id, const char *val, int32_t fd)
{
   E_Comp_Wl_Client_Data *cdata;
   Eina_List *l;
   E_Comp_Wl_Aux_Hint *hint;
   Eina_Bool found = EINA_FALSE;

   if ((!cp) || (!val)) return EINA_FALSE;
   cdata = (E_Comp_Wl_Client_Data*)e_pixmap_cdata_get(cp);
   if (!cdata) return EINA_FALSE;

   EINA_LIST_FOREACH(cdata->aux_hint.hints, l, hint)
     {
        if (hint->id == id)
          {
             if ((hint->val) && (strcmp(hint->val, val) != 0))
               {
                  eina_stringshare_del(hint->val);
                  hint->val = eina_stringshare_add(val);
                  hint->fd = fd;
                  hint->changed = EINA_TRUE;
                  cdata->aux_hint.changed = 1;
               }

             if (hint->deleted)
               hint->deleted = EINA_FALSE;

             found = EINA_TRUE;
             break;
          }
     }

   if (found)
     return EINA_TRUE;

   return EINA_FALSE;
}

E_API Eina_Bool
e_hints_aux_hint_del_with_pixmap(E_Pixmap *cp, int32_t id)
{
   E_Comp_Wl_Client_Data *cdata;
   Eina_List *l, *ll;
   E_Comp_Wl_Aux_Hint *hint;
   int res = -1;

   if (!cp) return EINA_FALSE;
   cdata = (E_Comp_Wl_Client_Data*)e_pixmap_cdata_get(cp);
   if (!cdata) return EINA_FALSE;

   EINA_LIST_FOREACH_SAFE(cdata->aux_hint.hints, l, ll, hint)
     {
        if (hint->id == id)
          {
             hint->changed = EINA_TRUE;
             hint->deleted = EINA_TRUE;
             cdata->aux_hint.changed = 1;
             res = hint->id;
             break;
          }
     }

   if (res == -1)
     return EINA_FALSE;

   return EINA_TRUE;
}

E_API const char *
e_hints_aux_hint_value_get_with_pixmap(E_Pixmap *cp, const char *name)
{
   E_Comp_Wl_Client_Data *cdata;
   Eina_List *l;
   E_Comp_Wl_Aux_Hint *hint;
   const char *res = NULL;

   if (!cp) return NULL;
   cdata = (E_Comp_Wl_Client_Data*)e_pixmap_cdata_get(cp);
   if (!cdata) return NULL;

   EINA_LIST_REVERSE_FOREACH(cdata->aux_hint.hints, l, hint)
     {
        if ((!hint->deleted) &&
            (!strcmp(hint->hint, name)))
          {
             res =  hint->val;
             break;
          }
     }

   return res;
}
#endif
#include "e.h"

typedef struct _E_Smart_Data E_Smart_Data;
typedef struct _Cache_Item   Cache_Item;
typedef struct _Cache        Cache;

struct _E_Smart_Data
{
   Evas_Coord     x, y, w, h;
   Evas_Object   *obj;
   Evas_Object   *eventarea;
   const char    *fdo;
   const char    *url;
   const char    *tmp_file;
   Ecore_Con_Url       *con_url;
   Ecore_Event_Handler *url_hand;
   Ecore_Timer   *guessing_animation;
   Ecore_Timer   *timer, *fdo_reload_timer;
   double         last_resize;
   int            size;
   int            frame, frame_count;
   int            tmp_fd;
   unsigned char  fill_inside E_BITFIELD;
   unsigned char  scale_up E_BITFIELD;
   unsigned char  preload E_BITFIELD;
   unsigned char  loading E_BITFIELD;
   unsigned char  animated E_BITFIELD;
   unsigned char  invalid E_BITFIELD;
   unsigned char  edje E_BITFIELD;
};

struct _Cache_Item
{
   unsigned int timestamp;

   Evas_Object *icon, *obj;
   const char  *id;
   Eina_List   *objs;
};

struct _Cache
{
   Eina_Hash   *hash;

   char        *file;
   Eet_File    *ef;
   Ecore_Timer *timer;
   Eina_List   *load_queue;
};

/* local subsystem functions */
static void      _e_icon_smart_reconfigure(E_Smart_Data *sd);
static void      _e_icon_smart_init(void);
static void      _e_icon_smart_add(Evas_Object *obj);
static void      _e_icon_smart_del(Evas_Object *obj);
static void      _e_icon_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void      _e_icon_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void      _e_icon_smart_show(Evas_Object *obj);
static void      _e_icon_smart_hide(Evas_Object *obj);
static void      _e_icon_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void      _e_icon_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void      _e_icon_smart_clip_unset(Evas_Object *obj);
static void      _e_icon_obj_prepare(Evas_Object *obj, E_Smart_Data *sd);
static void      _e_icon_preloaded(void *data, Evas *e, Evas_Object *obj, void *event_info);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

EINTERN int
e_icon_init(void)
{
   return 1;
}

EINTERN int
e_icon_shutdown(void)
{
   return 1;
}

/* externally accessible functions */
E_API Evas_Object *
e_icon_add(Evas *evas)
{
   _e_icon_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

static void
_e_icon_obj_prepare(Evas_Object *obj, E_Smart_Data *sd)
{
   if (!sd->obj) return;

   if (!sd->edje)
     {
        Evas_Object *pclip;

        pclip = evas_object_clip_get(sd->obj);
        evas_object_del(sd->obj);
        sd->obj = evas_object_image_add(evas_object_evas_get(obj));
        if (!sd->animated)
          evas_object_image_scale_hint_set(sd->obj,
                                           EVAS_IMAGE_SCALE_HINT_STATIC);
        evas_object_smart_member_add(sd->obj, obj);
        evas_object_stack_below(sd->obj, sd->eventarea);
        evas_object_event_callback_add(sd->obj, EVAS_CALLBACK_IMAGE_PRELOADED,
                                       _e_icon_preloaded, obj);
        evas_object_clip_set(sd->obj, pclip);
     }
}

static Eina_Bool
_frame_anim(void *data)
{
   E_Smart_Data *sd = data;
   double t;
   int fr;

   sd->frame++;
   fr = (sd->frame % (sd->frame_count)) + 1;
   evas_object_image_animated_frame_set(sd->obj, fr);
   t = evas_object_image_animated_frame_duration_get(sd->obj, fr, 0);
   sd->timer = ecore_timer_loop_add(t, _frame_anim, sd);
   return EINA_FALSE;
}

static int
_handle_anim(E_Smart_Data *sd)
{
   double t;

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   if (!evas_object_image_animated_get(sd->obj)) return 0;
   sd->frame_count = evas_object_image_animated_frame_count_get(sd->obj);
   if (sd->frame_count < 2) return 0;
   if (!sd->invalid) evas_object_show(sd->obj);
   t = evas_object_image_animated_frame_duration_get(sd->obj, sd->frame, 0);
   sd->timer = ecore_timer_loop_add(t, _frame_anim, sd);
   return 1;
}

static Eina_Bool
_url_complete_cb(void *data, int type EINA_UNUSED, void *event_info)
{
   Evas_Object *obj = data;
   E_Smart_Data *sd = evas_object_smart_data_get(obj);
   Ecore_Con_Event_Url_Complete *ev = event_info;

   if (!sd) return EINA_TRUE;
   if (ev->url_con != sd->con_url) return EINA_TRUE;
   e_icon_file_set(obj, sd->tmp_file);
   return EINA_FALSE;
}

E_API void
e_icon_file_set(Evas_Object *obj, const char *file)
{
   E_Smart_Data *sd;
   int len;

   if (!file) return;
   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;

   len = strlen(file);
   if ((len > 4) && (!strcasecmp(file + len - 4, ".edj")))
     {
        e_icon_file_edje_set(obj, file, "icon");
        return;
     }

   if ((sd->url) && (!strcmp(sd->url, file))) return;
   /* smart code here */
   sd->edje = EINA_FALSE;
   _e_icon_obj_prepare(obj, sd);
   /* FIXME: 64x64 - unhappy about this. use icon size */
   sd->loading = 0;
   if (sd->fdo)
     {
        eina_stringshare_del(sd->fdo);
        sd->fdo = NULL;
     }
   if (sd->url)
     {
        eina_stringshare_del(sd->url);
        sd->url = NULL;
     }
   if (sd->con_url)
     {
        ecore_con_url_free(sd->con_url);
        sd->con_url = NULL;
     }
   if (sd->tmp_file)
     {
        if (!!strcmp(file, sd->tmp_file))
          {
             // if we set a file that is not the tmp file - delete tmp file
             ecore_file_unlink(sd->tmp_file);
             eina_stringshare_del(sd->tmp_file);
             sd->tmp_file = NULL;
          }
     }
   if (sd->tmp_fd >= 0)
     {
        close(sd->tmp_fd);
        sd->tmp_fd = -1;
     }
   if (sd->url_hand)
     {
        ecore_event_handler_del(sd->url_hand);
        sd->url_hand = NULL;
     }

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_FALSE;

   if (sd->size != 0)
     evas_object_image_load_size_set(sd->obj, sd->size, sd->size);
   if (sd->preload) evas_object_hide(sd->obj);

   if (sd->preload)
     evas_object_image_load_head_skip_set(sd->obj, EINA_TRUE);
   if ((!strncasecmp(file, "http://", 7)) ||
       (!strncasecmp(file, "https://", 8)))
     {
        char buf[4096];

        snprintf(buf, sizeof(buf), "/tmp/.e-icon-tmp-XXXXXX");
        sd->tmp_fd = mkstemp(buf);
        if (sd->tmp_fd >= 0)
          {
             sd->url = eina_stringshare_add(file);
             sd->tmp_file = eina_stringshare_add(buf);
             sd->con_url = ecore_con_url_new(file);
             ecore_con_url_fd_set(sd->con_url, sd->tmp_fd);
             if (!ecore_con_url_get(sd->con_url))
               {
                  ecore_file_unlink(buf);
                  close(sd->tmp_fd);
                  ecore_con_url_free(sd->con_url);
                  sd->tmp_fd = -1;
                  sd->con_url = NULL;
               }
             else
               {
                  sd->url_hand = ecore_event_handler_add
                    (ECORE_CON_EVENT_URL_COMPLETE, _url_complete_cb, obj);
               }
          }
        return;
     }
   else
     evas_object_image_file_set(sd->obj, file, NULL);
//   if (evas_object_image_load_error_get(sd->obj) != EVAS_LOAD_ERROR_NONE)
//     return EINA_FALSE;

   if (!_handle_anim(sd))
     {
        if (sd->preload)
          {
             sd->loading = 1;
             evas_object_image_preload(sd->obj, EINA_FALSE);
          }
        else if (evas_object_visible_get(obj))
          {
             evas_object_show(sd->obj);
          }
     }
   _e_icon_smart_reconfigure(sd);
}

E_API void
e_icon_file_key_set(Evas_Object *obj, const char *file, const char *key)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;

   /* smart code here */
   sd->loading = 0;
   if (sd->fdo)
     {
        eina_stringshare_del(sd->fdo);
        sd->fdo = NULL;
     }
   if (sd->url)
     {
        eina_stringshare_del(sd->url);
        sd->url = NULL;
     }
   if (sd->con_url)
     {
        ecore_con_url_free(sd->con_url);
        sd->con_url = NULL;
     }
   if (sd->tmp_file)
     {
        ecore_file_unlink(sd->tmp_file);
        eina_stringshare_del(sd->tmp_file);
        sd->tmp_file = NULL;
     }
   if (sd->tmp_fd >= 0)
     {
        close(sd->tmp_fd);
        sd->tmp_fd = -1;
     }
   if (sd->url_hand)
     {
        ecore_event_handler_del(sd->url_hand);
        sd->url_hand = NULL;
     }

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_FALSE;

   _e_icon_obj_prepare(obj, sd);
   if (sd->size != 0)
     evas_object_image_load_size_set(sd->obj, sd->size, sd->size);
   if (sd->preload) evas_object_hide(sd->obj);
   if (sd->preload)
     evas_object_image_load_head_skip_set(sd->obj, EINA_TRUE);
   evas_object_image_file_set(sd->obj, file, key);
//   if (evas_object_image_load_error_get(sd->obj) != EVAS_LOAD_ERROR_NONE)
//     return EINA_FALSE;
   if (!_handle_anim(sd))
     {
        if (sd->preload)
          {
             sd->loading = 1;
             evas_object_image_preload(sd->obj, EINA_FALSE);
          }
        else if (evas_object_visible_get(obj))
          evas_object_show(sd->obj);
     }
   _e_icon_smart_reconfigure(sd);
}

E_API void
e_icon_edje_object_set(Evas_Object *obj, Evas_Object *edje)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;

   /* smart code here */
   if (sd->obj) evas_object_del(sd->obj);
   sd->loading = 0;
   if (sd->fdo)
     {
        eina_stringshare_del(sd->fdo);
        sd->fdo = NULL;
     }
   if (sd->url)
     {
        eina_stringshare_del(sd->url);
        sd->url = NULL;
     }
   if (sd->con_url)
     {
        ecore_con_url_free(sd->con_url);
        sd->con_url = NULL;
     }
   if (sd->tmp_file)
     {
        ecore_file_unlink(sd->tmp_file);
        eina_stringshare_del(sd->tmp_file);
        sd->tmp_file = NULL;
     }
   if (sd->tmp_fd >= 0)
     {
        close(sd->tmp_fd);
        sd->tmp_fd = -1;
     }
   if (sd->url_hand)
     {
        ecore_event_handler_del(sd->url_hand);
        sd->url_hand = NULL;
     }

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_TRUE;
   sd->obj = edje;

   if (evas_object_visible_get(obj)) evas_object_show(sd->obj);
   evas_object_smart_member_add(sd->obj, obj);
   _e_icon_smart_reconfigure(sd);
}

E_API Evas_Object *
e_icon_edje_get(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   if (!(sd = evas_object_smart_data_get(obj)))
     return NULL;
   return sd->edje ? sd->obj : NULL;
}

E_API void
e_icon_file_edje_set(Evas_Object *obj, const char *file, const char *part)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;

   /* smart code here */
   if (sd->obj) evas_object_del(sd->obj);
   sd->loading = 0;
   if (sd->fdo)
     {
        eina_stringshare_del(sd->fdo);
        sd->fdo = NULL;
     }
   if (sd->url)
     {
        eina_stringshare_del(sd->url);
        sd->url = NULL;
     }
   if (sd->con_url)
     {
        ecore_con_url_free(sd->con_url);
        sd->con_url = NULL;
     }
   if (sd->tmp_file)
     {
        ecore_file_unlink(sd->tmp_file);
        eina_stringshare_del(sd->tmp_file);
        sd->tmp_file = NULL;
     }
   if (sd->tmp_fd >= 0)
     {
        close(sd->tmp_fd);
        sd->tmp_fd = -1;
     }
   if (sd->url_hand)
     {
        ecore_event_handler_del(sd->url_hand);
        sd->url_hand = NULL;
     }

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_TRUE;

   sd->obj = edje_object_add(evas_object_evas_get(obj));
   edje_object_file_set(sd->obj, file, part);
//   if (edje_object_load_error_get(sd->obj) != EDJE_LOAD_ERROR_NONE)
//     return EINA_FALSE;
   evas_object_smart_member_add(sd->obj, obj);
   evas_object_stack_below(sd->obj, sd->eventarea);
   if (evas_object_visible_get(obj)) evas_object_show(sd->obj);
   _e_icon_smart_reconfigure(sd);
}

E_API void
e_icon_fdo_icon_set(Evas_Object *obj, const char *icon)
{
   E_Smart_Data *sd;
   const char *path;
   int len;

   if (!icon) return;
   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (icon[0] == '/')
     {
        e_icon_file_set(obj, icon);
        return;
     }

   if (!(sd = evas_object_smart_data_get(obj))) return;

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_FALSE;

   eina_stringshare_replace(&sd->fdo, icon);
   if (!sd->fdo) return;

   path = efreet_icon_path_find(e_config->icon_theme, sd->fdo, sd->size);
   if (!path)
     {
        if (e_util_strcmp(e_config->icon_theme, "hicolor"))
          path = efreet_icon_path_find("hicolor", sd->fdo, sd->size);
        if (!path) return;
     }

   len = strlen(icon);
   if ((len > 4) && (!strcasecmp(icon + len - 4, ".edj")))
     {
        e_icon_file_edje_set(obj, path, "icon");
        return;
     }

   /* smart code here */
   _e_icon_obj_prepare(obj, sd);
   sd->loading = 0;
   if (sd->size != 0)
     evas_object_image_load_size_set(sd->obj, sd->size, sd->size);
   if (sd->preload) evas_object_hide(sd->obj);
   if (sd->preload)
     evas_object_image_load_head_skip_set(sd->obj, EINA_TRUE);
   evas_object_image_file_set(sd->obj, path, NULL);
//   if (evas_object_image_load_error_get(sd->obj) != EVAS_LOAD_ERROR_NONE)
//     return EINA_FALSE;
   if (sd->preload)
     {
        sd->loading = 1;
        evas_object_image_preload(sd->obj, 0);
     }
   else if (evas_object_visible_get(obj))
     evas_object_show(sd->obj);
   _e_icon_smart_reconfigure(sd);
   return;
}

E_API void
e_icon_image_object_set(Evas_Object *obj, Evas_Object *o)
{
   E_Smart_Data *sd;
   const char *str;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   str = evas_object_type_get(o);
   if ((!str) || strcmp(str, "image"))
     CRI(EINA_COLOR_RED "******************\ntrying to set an image object of type '%s'! this is not what you want!\n******************\n"EINA_COLOR_RESET, str);

   if (sd->timer) ecore_timer_del(sd->timer);
   sd->timer = NULL;
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   sd->guessing_animation = NULL;
   sd->frame = 0;
   sd->frame_count = 0;
   sd->invalid = 0;
   sd->edje = EINA_FALSE;

   /* smart code here */
   if (sd->obj) evas_object_del(sd->obj);
   sd->loading = 0;
   sd->obj = o;
   evas_object_smart_member_add(sd->obj, obj);
   evas_object_stack_below(sd->obj, sd->eventarea);
   if (evas_object_visible_get(obj)) evas_object_show(sd->obj);
   _handle_anim(sd);
   _e_icon_smart_reconfigure(sd);
}

E_API Eina_Bool
e_icon_file_get(const Evas_Object *obj, const char **file, const char **group)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(EINA_FALSE);
   if ((!file) && (!group)) return EINA_FALSE;
   if (file) *file = NULL;
   if (group) *group = NULL;
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   if (sd->edje)
     {
        edje_object_file_get(sd->obj, file, group);
        return file || group;
     }
   evas_object_image_file_get(sd->obj, file, group);
   return file || group;
}

E_API void
e_icon_smooth_scale_set(Evas_Object *obj, Eina_Bool smooth)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   if (sd->edje) return;
   evas_object_image_smooth_scale_set(sd->obj, smooth);
}

E_API Eina_Bool
e_icon_smooth_scale_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   if (sd->edje)
     return EINA_FALSE;
   return evas_object_image_smooth_scale_get(sd->obj);
}

E_API void
e_icon_alpha_set(Evas_Object *obj, Eina_Bool alpha)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   if (sd->edje) return;
   evas_object_image_alpha_set(sd->obj, alpha);
}

E_API Eina_Bool
e_icon_alpha_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   if (sd->edje) return EINA_FALSE;
   return evas_object_image_alpha_get(sd->obj);
}

E_API void
e_icon_preload_set(Evas_Object *obj, Eina_Bool preload)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   sd->preload = preload;
}

E_API Eina_Bool
e_icon_preload_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   return sd->preload;
}

E_API void
e_icon_size_get(const Evas_Object *obj, int *w, int *h)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj)))
     {
        if (w) *w = 0;
        if (h) *h = 0;
        return;
     }
   if (sd->edje)
     edje_object_size_min_calc(sd->obj, w, h);
   else
     evas_object_image_size_get(sd->obj, w, h);
}

E_API Eina_Bool
e_icon_fill_inside_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   return sd->fill_inside;
}

E_API void
e_icon_fill_inside_set(Evas_Object *obj, Eina_Bool fill_inside)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   fill_inside = !!fill_inside;
   if (sd->fill_inside == fill_inside) return;
   sd->fill_inside = fill_inside;
   _e_icon_smart_reconfigure(sd);
}

E_API Eina_Bool
e_icon_scale_up_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return EINA_FALSE;
   return sd->scale_up;
}

E_API void
e_icon_scale_up_set(Evas_Object *obj, Eina_Bool scale_up)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   scale_up = !!scale_up;
   if (sd->scale_up == scale_up) return;
   sd->scale_up = scale_up;
   _e_icon_smart_reconfigure(sd);
}

E_API void
e_icon_data_set(Evas_Object *obj, void *data, int w, int h)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   if (sd->edje) return;
   evas_object_image_size_set(sd->obj, w, h);
   evas_object_image_data_copy_set(sd->obj, data);
}

E_API void *
e_icon_data_get(const Evas_Object *obj, int *w, int *h)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(NULL);
   if (!(sd = evas_object_smart_data_get(obj))) return NULL;
   if (sd->edje) return NULL;
   evas_object_image_size_get(sd->obj, w, h);
   return evas_object_image_data_get(sd->obj, 0);
}

E_API void
e_icon_scale_size_set(Evas_Object *obj, int size)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   sd->size = size;
   if (sd->edje) return;
   evas_object_image_load_size_set(sd->obj, sd->size, sd->size);
}

E_API int
e_icon_scale_size_get(const Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   if (!(sd = evas_object_smart_data_get(obj))) return 0;
   return sd->size;
}

E_API void
e_icon_selected_set(const Evas_Object *obj, Eina_Bool selected)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   if (!sd->edje) return;
   if (selected)
     edje_object_signal_emit(sd->obj, "e,state,selected", "e");
   else
     edje_object_signal_emit(sd->obj, "e,state,unselected", "e");
}

E_API void
e_icon_edje_emit(const Evas_Object *obj, const char *sig, const char *src)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR();
   if (!(sd = evas_object_smart_data_get(obj))) return;
   edje_object_signal_emit(sd->obj, sig, src);
}

/* local subsystem globals */
static void
_e_icon_smart_reconfigure(E_Smart_Data *sd)
{
   int iw, ih;
   Evas_Coord x, y, w, h;

   if (!sd->obj) return;
   w = sd->w;
   h = sd->h;
   x = sd->x;
   y = sd->y;
   if (!sd->loading)
     {
        if (sd->edje)
          {
             evas_object_move(sd->obj, x, y);
             evas_object_resize(sd->obj, w, h);
          }
        else
          {
             iw = 0;
             ih = 0;
             evas_object_image_size_get(sd->obj, &iw, &ih);
             if (iw < 1) iw = 1;
             if (ih < 1) ih = 1;

             if (sd->fill_inside)
               {
                  w = sd->w;
                  h = ((double)ih * w) / (double)iw;
                  if (h > sd->h)
                    {
                       h = sd->h;
                       w = ((double)iw * h) / (double)ih;
                    }
               }
             else
               {
                  w = sd->w;
                  h = ((double)ih * w) / (double)iw;
                  if (h < sd->h)
                    {
                       h = sd->h;
                       w = ((double)iw * h) / (double)ih;
                    }
               }
             if (!sd->scale_up)
               {
                  if ((w > iw) || (h > ih))
                    {
                       w = iw;
                       h = ih;
                    }
               }
             x = sd->x + ((sd->w - w) / 2);
             y = sd->y + ((sd->h - h) / 2);
             evas_object_move(sd->obj, x, y);
             evas_object_image_fill_set(sd->obj, 0, 0, w, h);
             evas_object_resize(sd->obj, w, h);
          }
     }
   evas_object_move(sd->eventarea, x, y);
   evas_object_resize(sd->eventarea, w, h);
}

static void
_e_icon_smart_init(void)
{
   if (_e_smart) return;
   {
      static Evas_Smart_Class sc = EVAS_SMART_CLASS_INIT_NAME_VERSION("e_icon");
      if (!sc.add)
        {
           sc.add = _e_icon_smart_add;
           sc.del = _e_icon_smart_del;
           sc.move = _e_icon_smart_move;
           sc.resize = _e_icon_smart_resize;
           sc.show = _e_icon_smart_show;
           sc.hide = _e_icon_smart_hide;
           sc.color_set = _e_icon_smart_color_set;
           sc.clip_set = _e_icon_smart_clip_set;
           sc.clip_unset = _e_icon_smart_clip_unset;
        }
      _e_smart = evas_smart_class_new(&sc);
   }
}

static void
_e_icon_preloaded(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;
   int iw, ih;

   if (!(sd = evas_object_smart_data_get(data))) return;

   evas_object_smart_callback_call(data, "preloaded", NULL);
   evas_object_image_size_get(sd->obj, &iw, &ih);
   if ((iw > 0) && (ih > 0))
     {
        sd->invalid = 0;
        evas_object_show(sd->obj);
     }
   else
     sd->invalid = 1;
   sd->loading = 0;
   _e_icon_smart_reconfigure(sd);
}

static void
_e_icon_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!(sd = calloc(1, sizeof(E_Smart_Data)))) return;

   sd->obj = evas_object_image_add(evas_object_evas_get(obj));
   evas_object_event_callback_add(sd->obj, EVAS_CALLBACK_IMAGE_PRELOADED,
                                  _e_icon_preloaded, obj);

   sd->eventarea = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_color_set(sd->eventarea, 0, 0, 0, 0);
   evas_object_smart_member_add(sd->eventarea, obj);

   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->fill_inside = 1;
   sd->scale_up = 1;
   sd->size = 64;
   sd->preload = 0;
   sd->tmp_fd = -1;
   evas_object_smart_member_add(sd->obj, obj);
   evas_object_smart_data_set(obj, sd);
}

static void
_e_icon_smart_del(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   evas_object_del(sd->obj);
   evas_object_del(sd->eventarea);
   if (sd->fdo) eina_stringshare_del(sd->fdo);
   if (sd->fdo_reload_timer) ecore_timer_del(sd->fdo_reload_timer);
   if (sd->timer) ecore_timer_del(sd->timer);
   if (sd->guessing_animation) ecore_timer_del(sd->guessing_animation);
   if (sd->url) eina_stringshare_del(sd->url);
   if (sd->con_url) ecore_con_url_free(sd->con_url);
   if (sd->tmp_file)
     {
        ecore_file_unlink(sd->tmp_file);
        eina_stringshare_del(sd->tmp_file);
     }
   if (sd->tmp_fd >= 0) close(sd->tmp_fd);
   if (sd->url_hand) ecore_event_handler_del(sd->url_hand);
   evas_object_smart_data_set(obj, NULL);
   memset(sd, 0, sizeof(*sd));
   free(sd);
}

static void
_e_icon_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   if ((sd->x == x) && (sd->y == y)) return;
   sd->x = x;
   sd->y = y;
   _e_icon_smart_reconfigure(sd);
}

static Eina_Bool
_e_icon_guess_anim(void *data)
{
   E_Smart_Data *sd = data;
   double t = ecore_loop_time_get();

   if (t - sd->last_resize < 0.2)
     {
        evas_object_image_scale_hint_set(sd->obj,
                                         EVAS_IMAGE_SCALE_HINT_DYNAMIC);
        sd->animated = EINA_TRUE;
     }
   else
     {
        evas_object_image_scale_hint_set(sd->obj,
                                         EVAS_IMAGE_SCALE_HINT_STATIC);
     }

   sd->guessing_animation = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_e_icon_fdo_reload(void *data)
{
   E_Smart_Data *sd = data;
   const char *path;

   sd->fdo_reload_timer = NULL;
   sd->size = MAX(sd->w, sd->h);
   path = efreet_icon_path_find(e_config->icon_theme, sd->fdo, sd->size);
   if (!path)
     {
        if (e_util_strcmp(e_config->icon_theme, "hicolor"))
          path = efreet_icon_path_find("hicolor", sd->fdo, sd->size);
        if (!path) return EINA_FALSE;
     }


   /* smart code here */
   evas_object_image_load_size_set(sd->obj, sd->size, sd->size);
   evas_object_image_file_set(sd->obj, path, NULL);
   if (sd->preload)
     {
        sd->loading = 1;
        evas_object_image_preload(sd->obj, 0);
     }
   return EINA_FALSE;
}

static void
_e_icon_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   if ((sd->w == w) && (sd->h == h)) return;
   sd->w = w;
   sd->h = h;
   if (sd->fdo)
     {
        if (sd->fdo_reload_timer) ecore_timer_del(sd->fdo_reload_timer);
        sd->fdo_reload_timer = ecore_timer_loop_add(0.1, _e_icon_fdo_reload, sd);
     }

   if ((!sd->edje) && ((sd->loading && sd->preload) ||
                       (!sd->loading && !sd->preload))
       && !sd->animated)
     {
        evas_object_image_scale_hint_set(sd->obj,
                                         EVAS_IMAGE_SCALE_HINT_DYNAMIC);
        if (!sd->guessing_animation)
          sd->guessing_animation = ecore_timer_loop_add(0.3,
                                                   _e_icon_guess_anim,
                                                   sd);
     }

   sd->last_resize = ecore_loop_time_get();
   _e_icon_smart_reconfigure(sd);
}

static void
_e_icon_smart_show(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   if (!((sd->preload) && (sd->loading)))
     {
        if (!sd->invalid) evas_object_show(sd->obj);
     }

   evas_object_show(sd->eventarea);
}

static void
_e_icon_smart_hide(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   evas_object_hide(sd->obj);
   evas_object_hide(sd->eventarea);
}

static void
_e_icon_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   evas_object_color_set(sd->obj, r, g, b, a);
}

static void
_e_icon_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   evas_object_clip_set(sd->obj, clip);
   evas_object_clip_set(sd->eventarea, clip);
}

static void
_e_icon_smart_clip_unset(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (!(sd = evas_object_smart_data_get(obj))) return;
   evas_object_clip_unset(sd->obj);
   evas_object_clip_unset(sd->eventarea);
}
#include "e.h"

#define SMART_NAME     "e_ilist"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;

typedef struct _E_Smart_Data E_Smart_Data;
struct _E_Smart_Data
{
   Evas_Coord    x, y, w, h, iw, ih;
   Evas_Object  *o_smart, *o_edje, *o_box;
   Eina_List    *items;
   Eina_List    *selected_items;
   int           selected;
   const char   *theme;
   unsigned char selector E_BITFIELD;
   unsigned char multi_select E_BITFIELD;
   unsigned char on_hold E_BITFIELD;

   struct
   {
      char        *buf;
      unsigned int size;
      Ecore_Timer *timer;
   } typebuf;
   Eina_Bool disabled E_BITFIELD;
};

static void          _e_smart_init(void);
static void          _e_smart_add(Evas_Object *obj);
static void          _e_smart_del(Evas_Object *obj);
static void          _e_smart_show(Evas_Object *obj);
static void          _e_smart_hide(Evas_Object *obj);
static void          _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void          _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void          _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void          _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void          _e_smart_clip_unset(Evas_Object *obj);
static void          _e_smart_reconfigure(E_Smart_Data *sd);
static void          _e_smart_event_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void          _e_smart_event_mouse_up(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void          _e_smart_event_key_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);

static void          _e_typebuf_add(Evas_Object *obj, const char *s);
static void          _e_typebuf_match(Evas_Object *obj);
static Eina_Bool     _e_typebuf_timer_cb(void *data);
static void          _e_typebuf_timer_update(Evas_Object *obj);
static void          _e_typebuf_timer_delete(Evas_Object *obj);
static void          _e_typebuf_clean(Evas_Object *obj);

static E_Ilist_Item *_e_ilist_item_new(E_Smart_Data *sd, Evas_Object *icon, Evas_Object *end, const char *label, int header, Ecore_End_Cb func, Ecore_End_Cb func_hilight, void *data, void *data2);
static void          _e_ilist_item_theme_set(E_Ilist_Item *si, Eina_Bool custom, Eina_Bool header, Eina_Bool even);
static void          _e_ilist_widget_hack_cb(E_Smart_Data *sd, Evas_Object *obj EINA_UNUSED, Evas_Object *scr);

static void          _item_select(E_Ilist_Item *si);
static void          _item_unselect(E_Ilist_Item *si);

static Evas_Smart *_e_smart = NULL;

E_API Evas_Object *
e_ilist_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_ilist_append(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, int header, void (*func)(void *data, void *data2), void (*func_hilight)(void *data, void *data2), void *data, void *data2)
{
   E_Ilist_Item *si;
   Evas_Coord mw = 0, mh = 0;
   const char *stacking;

   API_ENTRY return;
   si = _e_ilist_item_new(sd, icon, end, label, header, func, func_hilight, data, data2);
   sd->items = eina_list_append(sd->items, si);

   edje_object_size_min_calc(si->o_base, &mw, &mh);
   evas_object_size_hint_min_set(si->o_base, mw, mh);
   elm_box_pack_end(sd->o_box, si->o_base);
   stacking = edje_object_data_get(si->o_base, "stacking");
   if (stacking)
     {
        if (!strcmp(stacking, "below")) evas_object_lower(si->o_base);
        else if (!strcmp(stacking, "above"))
          evas_object_raise(si->o_base);
     }

   evas_object_lower(sd->o_box);

   evas_object_show(si->o_base);
}

E_API void
e_ilist_append_relative(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, int header, void (*func)(void *data, void *data2), void (*func_hilight)(void *data, void *data2), void *data, void *data2, int relative)
{
   E_Ilist_Item *si, *ri;
   Evas_Coord mw = 0, mh = 0;
   const char *stacking;

   API_ENTRY return;
   si = _e_ilist_item_new(sd, icon, end, label, header, func, func_hilight, data, data2);

   ri = eina_list_nth(sd->items, relative);
   if (ri)
     sd->items = eina_list_append_relative(sd->items, si, ri);
   else
     sd->items = eina_list_append(sd->items, si);

   edje_object_size_min_calc(si->o_base, &mw, &mh);
   evas_object_size_hint_min_set(si->o_base, mw, mh);
   if (ri)
     elm_box_pack_after(sd->o_box, si->o_base, ri->o_base);
   else
     elm_box_pack_end(sd->o_box, si->o_base);
   stacking = edje_object_data_get(si->o_base, "stacking");
   if (stacking)
     {
        if (!strcmp(stacking, "below")) evas_object_lower(si->o_base);
        else if (!strcmp(stacking, "above"))
          evas_object_raise(si->o_base);
     }

   evas_object_lower(sd->o_box);
   evas_object_show(si->o_base);
}

E_API void
e_ilist_prepend(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, int header, void (*func)(void *data, void *data2), void (*func_hilight)(void *data, void *data2), void *data, void *data2)
{
   E_Ilist_Item *si;
   Evas_Coord mw = 0, mh = 0;

   API_ENTRY return;
   si = _e_ilist_item_new(sd, icon, end, label, header, func, func_hilight, data, data2);
   sd->items = eina_list_prepend(sd->items, si);

   edje_object_size_min_calc(si->o_base, &mw, &mh);
   evas_object_size_hint_min_set(si->o_base, mw, mh);
   elm_box_pack_start(sd->o_box, si->o_base);

   evas_object_lower(sd->o_box);
   evas_object_show(si->o_base);
}

E_API void
e_ilist_prepend_relative(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, int header, void (*func)(void *data, void *data2), void (*func_hilight)(void *data, void *data2), void *data, void *data2, int relative)
{
   E_Ilist_Item *si, *ri;
   Evas_Coord mw = 0, mh = 0;

   API_ENTRY return;
   si = _e_ilist_item_new(sd, icon, end, label, header, func, func_hilight, data, data2);

   ri = eina_list_nth(sd->items, relative);
   if (ri)
     sd->items = eina_list_prepend_relative(sd->items, si, ri);
   else
     sd->items = eina_list_prepend(sd->items, si);

   edje_object_size_min_calc(si->o_base, &mw, &mh);
   evas_object_size_hint_min_set(si->o_base, mw, mh);
   if (ri)
     elm_box_pack_before(sd->o_box, si->o_base, ri->o_base);
   else
     elm_box_pack_end(sd->o_box, si->o_base);

   evas_object_lower(sd->o_box);
   evas_object_show(si->o_base);
}

E_API void
e_ilist_clear(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return;

   e_ilist_freeze(obj);
   EINA_LIST_FREE(sd->items, si)
     {
        if (!si) continue;
        if (si->o_icon) evas_object_del(si->o_icon);
        if (si->o_end) evas_object_del(si->o_end);
        if (si->label) eina_stringshare_del(si->label);
        evas_object_del(si->o_base);
        E_FREE(si);
     }
   if (sd->selected_items) sd->selected_items = eina_list_free(sd->selected_items);
   e_ilist_thaw(obj);
   sd->selected = -1;
}

E_API void
e_ilist_freeze(Evas_Object *obj)
{
   API_ENTRY return;
}

E_API void
e_ilist_thaw(Evas_Object *obj)
{
   API_ENTRY return;
}

E_API int
e_ilist_count(Evas_Object *obj)
{
   API_ENTRY return 0;
   return eina_list_count(sd->items);
}

E_API int
e_ilist_selector_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->selector;
}

E_API void
e_ilist_selector_set(Evas_Object *obj, int selector)
{
   API_ENTRY return;
   sd->selector = selector;
}

E_API Eina_Bool
e_ilist_multi_select_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->multi_select;
}

E_API void
e_ilist_multi_select_set(Evas_Object *obj, Eina_Bool multi)
{
   API_ENTRY return;
   sd->multi_select = multi;
}

E_API void
e_ilist_size_min_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   API_ENTRY return;
   elm_box_recalculate(sd->o_box);
   evas_object_size_hint_min_get(sd->o_box, w, h);
}

E_API void
e_ilist_unselect(Evas_Object *obj)
{
   API_ENTRY return;

   if (!sd->items) return;
   if (sd->selected < 0) return;
   while (sd->selected_items)
     _item_unselect(sd->selected_items->data);
   sd->selected = -1;
}

E_API void
e_ilist_selected_set(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;
   int i;

   API_ENTRY return;
   if (!sd->items) return;

   i = eina_list_count(sd->items);
   if (n >= i) n = i - 1;
   else if (n < 0)
     n = 0;

   e_ilist_unselect(obj);
   if (!(si = eina_list_nth(sd->items, n))) return;

   /* NB: Remove this if headers ever become selectable */
   while (si->header && ((++n) < i))
     if (!(si = eina_list_nth(sd->items, n))) return;
   while (si->header && ((--n) >= 0))
     if (!(si = eina_list_nth(sd->items, n))) return;
   if (si->header) return;

   _item_select(si);
   sd->selected = n;
   if (si->func_hilight) si->func_hilight(si->data, si->data2);
   if (sd->selector) return;
   if (!sd->on_hold)
     {
        if (si->func) si->func(si->data, si->data2);
     }
}

E_API const Eina_List *
e_ilist_selected_items_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->selected_items;
}

E_API int
e_ilist_selected_get(Evas_Object *obj)
{
   Eina_List *l = NULL;
   E_Ilist_Item *li = NULL;
   int i, j;

   API_ENTRY return -1;
   if (!sd->items) return -1;
   if (!sd->multi_select)
     return sd->selected;
   j = -1;
   i = 0;
   /* Return the index the of last selected item */
   EINA_LIST_FOREACH(sd->items, l, li)
     {
        if (li && li->selected) j = i;
        i++;
     }
   return j;
}

E_API const char *
e_ilist_selected_label_get(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   if (sd->multi_select) return NULL;
   if (sd->selected < 0) return NULL;
   si = eina_list_nth(sd->items, sd->selected);
   if (si)
     {
        if (!si->label)
          {
             si->label =
               eina_stringshare_add(edje_object_part_text_get(si->o_base,
                                                              "e.text.label"));
          }
        if (si->label) return si->label;
     }
   return NULL;
}

E_API void *
e_ilist_selected_data_get(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   if (sd->multi_select) return NULL;
   if (sd->selected < 0) return NULL;
   si = eina_list_nth(sd->items, sd->selected);
   if (si) return si->data;
   return NULL;
}

E_API void *
e_ilist_selected_data2_get(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   if (sd->multi_select) return NULL;
   if (sd->selected < 0) return NULL;
   si = eina_list_nth(sd->items, sd->selected);
   if (si) return si->data2;
   return NULL;
}

E_API Evas_Object *
e_ilist_selected_icon_get(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   if (sd->multi_select) return NULL;
   if (sd->selected < 0) return NULL;
   si = eina_list_nth(sd->items, sd->selected);
   if (si) return si->o_icon;
   return NULL;
}

E_API Evas_Object *
e_ilist_selected_end_get(Evas_Object *obj)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   if (sd->multi_select) return NULL;
   if (sd->selected < 0) return NULL;
   si = eina_list_nth(sd->items, sd->selected);
   if (si) return si->o_end;
   return NULL;
}

E_API void
e_ilist_selected_geometry_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y, Evas_Coord *w, Evas_Coord *h)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return;
   if (!sd->items) return;
   if (sd->selected < 0) return;
   if (!(si = eina_list_nth(sd->items, sd->selected))) return;
   evas_object_geometry_get(si->o_base, x, y, w, h);
   *x -= sd->x;
   *y -= sd->y;
}

E_API int
e_ilist_selected_count_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   if (!sd->items) return 0;
   return eina_list_count(sd->selected_items);
}

E_API void
e_ilist_remove_num(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;
   Eina_List *item;
   int w, h;
   Eina_Bool resize = EINA_FALSE;

   API_ENTRY return;
   if (!sd->items) return;
   item = eina_list_nth_list(sd->items, n);
   if (!item) return;
   si = eina_list_data_get(item);
   if (!si) return;
   sd->items = eina_list_remove_list(sd->items, item);
   if (si->selected) sd->selected_items = eina_list_remove(sd->selected_items, si);

   evas_object_geometry_get(sd->o_box, NULL, NULL, &w, &h);
   if ((sd->w == w) && (sd->h == h))
     {
        resize = EINA_TRUE;
        evas_object_geometry_get(si->o_base, NULL, NULL, &w, &h);
     }

   if (sd->selected == n) sd->selected = -1;
   if (si->o_icon) evas_object_del(si->o_icon);
   if (si->o_end) evas_object_del(si->o_end);
   if (si->label) eina_stringshare_del(si->label);
   evas_object_del(si->o_base);
   E_FREE(si);

   /* if ilist size is size of box (e_widget_ilist),
    * autoresize here to prevent skewed perspective as in ticket #1678
    */
   if (!resize) return;
   if (!sd->items) return;
   evas_object_resize(sd->o_smart, w, sd->h - h);
}

E_API const char *
e_ilist_nth_label_get(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   si = eina_list_nth(sd->items, n);
   if (si)
     {
        if (!si->label)
          {
             si->label =
               eina_stringshare_add(edje_object_part_text_get(si->o_base,
                                                              "e.text.label"));
          }
        if (si->label) return si->label;
     }
   return NULL;
}

E_API void
e_ilist_item_label_set(E_Ilist_Item *si, const char *label)
{
   EINA_SAFETY_ON_NULL_RETURN(si);
   eina_stringshare_replace(&si->label, label);
   edje_object_part_text_set(si->o_base, "e.text.label", label);
}

E_API void
e_ilist_nth_label_set(Evas_Object *obj, int n, const char *label)
{
   E_Ilist_Item *si = NULL;

   /* check for a NULL label first...simpler, faster check then doing
    * API_ENTRY check first */
   if (!label) return;
   API_ENTRY return;
   if (!sd->items) return;
   si = eina_list_nth(sd->items, n);
   if (si) e_ilist_item_label_set(si, label);
}

E_API Evas_Object *
e_ilist_nth_icon_get(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   si = eina_list_nth(sd->items, n);
   if (si) return si->o_icon;
   return NULL;
}

E_API void
e_ilist_nth_icon_set(Evas_Object *obj, int n, Evas_Object *icon)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return;
   if (!sd->items) return;
   if (!(si = eina_list_nth(sd->items, n))) return;
   if (si->o_icon)
     {
        edje_object_part_unswallow(si->o_base, si->o_icon);
        evas_object_del(si->o_icon);
     }
   si->o_icon = icon;
   E_WEIGHT(si->o_icon, 1, 0);
   E_FILL(si->o_icon);
   if (si->o_icon)
     {
        evas_object_size_hint_min_set(si->o_icon, sd->iw, sd->ih);
        edje_object_part_swallow(si->o_base, "e.swallow.icon", si->o_icon);
        evas_object_show(si->o_icon);
     }
}

E_API Evas_Object *
e_ilist_nth_end_get(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return NULL;
   if (!sd->items) return NULL;
   si = eina_list_nth(sd->items, n);
   if (si) return si->o_end;
   return NULL;
}

E_API void
e_ilist_nth_end_set(Evas_Object *obj, int n, Evas_Object *end)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return;
   if (!sd->items) return;
   if (!(si = eina_list_nth(sd->items, n))) return;
   if (si->o_end)
     {
        edje_object_part_unswallow(si->o_base, si->o_end);
        evas_object_del(si->o_end);
     }
   si->o_end = end;
   if (si->o_end)
     {
        evas_object_size_hint_min_set(si->o_end, sd->iw, sd->ih);
        edje_object_part_swallow(si->o_base, "e.swallow.end", si->o_end);
        evas_object_show(si->o_end);
     }
}

E_API Eina_Bool
e_ilist_nth_is_header(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return 0;
   if (!sd->items) return 0;
   si = eina_list_nth(sd->items, n);
   if (si) return si->header;
   return 0;
}

E_API void
e_ilist_nth_geometry_get(Evas_Object *obj, int n, Evas_Coord *x, Evas_Coord *y, Evas_Coord *w, Evas_Coord *h)
{
   E_Ilist_Item *si = NULL;

   API_ENTRY return;
   if (!sd->items) return;
   if (!(si = eina_list_nth(sd->items, n))) return;
   evas_object_geometry_get(si->o_base, x, y, w, h);
   *x -= sd->x;
   *y -= sd->y;
}

E_API void
e_ilist_icon_size_set(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   Eina_List *l = NULL;
   E_Ilist_Item *si = NULL;

   API_ENTRY return;
   if ((sd->iw == w) && (sd->ih == h)) return;
   sd->iw = w;
   sd->ih = h;
   EINA_LIST_FOREACH(sd->items, l, si)
     {
        Evas_Coord mw = 0, mh = 0;

        if (!si) continue;
        if (!si->o_icon) continue;
        evas_object_size_hint_min_set(si->o_icon, w, h);
        edje_object_part_swallow(si->o_base, "e.swallow.icon", si->o_icon);

        if (si->o_end)
          {
             Evas_Coord ew = 0, eh = 0;

             evas_object_size_hint_min_get(si->o_end, &ew, &eh);
             if ((ew <= 0) || (eh <= 0))
               {
                  ew = w;
                  eh = h;
               }
             evas_object_size_hint_min_set(si->o_end, ew, eh);
          }

        edje_object_size_min_calc(si->o_base, &mw, &mh);
        evas_object_size_hint_min_set(si->o_icon, mw, mh);
     }
}

E_API const Eina_List *
e_ilist_items_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->items;
}

E_API void
e_ilist_multi_select(Evas_Object *obj, int n)
{
   E_Ilist_Item *si = NULL;
   int i;

   API_ENTRY return;
   if ((!sd->items) || (!sd->multi_select)) return;

   i = eina_list_count(sd->items);
   if (n >= i) n = i - 1;
   else if (n < 0)
     n = 0;

   if (!(si = eina_list_nth(sd->items, n))) return;
   if (si->header) return;
   sd->selected = n;
   if (si->selected)
     {
        _item_unselect(si);
        if (si->func_hilight) si->func_hilight(si->data, si->data2);
        if (sd->selector) return;
        if (!sd->on_hold)
          {
             if (si->func) si->func(si->data, si->data2);
          }
        return;
     }
   _item_select(si);
   if (si->func_hilight) si->func_hilight(si->data, si->data2);
   if (sd->selector) return;
   if (!sd->on_hold)
     {
        if (si->func) si->func(si->data, si->data2);
     }
}

E_API void
e_ilist_range_select(Evas_Object *obj, int n)
{
   int i, j, dir;

   API_ENTRY return;
   if ((!sd->items) || (!sd->multi_select)) return;

   i = eina_list_count(sd->items);
   if (n >= i) n = i - 1;
   else if (n < 0)
     n = 0;

   if (n < sd->selected) dir = 0;
   else dir = 1;

   if (!eina_list_nth(sd->items, n)) return;
   if (dir == 1)
     {
        for (j = (sd->selected + 1); ((j < i) && (j <= n)); j++)
          e_ilist_multi_select(sd->o_smart, j);
     }
   else
     {
        for (j = (sd->selected - 1); ((j >= 0) && (j >= n)); j--)
          e_ilist_multi_select(sd->o_smart, j);
     }
}

E_API Eina_Bool
e_ilist_custom_edje_file_set(Evas_Object *obj, const char *file, const char *group)
{
   Eina_List *l;
   E_Ilist_Item *si;
   Eina_Bool even = EINA_FALSE;

   API_ENTRY return EINA_FALSE;

   edje_object_file_set(sd->o_edje, file, group);
   eina_stringshare_replace(&sd->theme, group);

   EINA_LIST_FOREACH(sd->items, l, si)
     {
        _e_ilist_item_theme_set(si, !!sd->theme, si->header, even);
        if (si->o_icon)
          edje_object_part_swallow(si->o_base, "e.swallow.icon", si->o_icon);
        if (si->o_end)
          edje_object_part_swallow(si->o_base, "e.swallow.end", si->o_end);
        even = !even;
     }
   return EINA_TRUE;
}

/* SMART FUNCTIONS */
static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static Evas_Smart_Class sc = EVAS_SMART_CLASS_INIT_NAME_VERSION(SMART_NAME);
      if (!sc.add)
        {
           sc.add = _e_smart_add;
           sc.del = _e_smart_del;
           sc.move = _e_smart_move;
           sc.resize = _e_smart_resize;
           sc.show = _e_smart_show;
           sc.hide = _e_smart_hide;
           sc.color_set = _e_smart_color_set;
           sc.clip_set = _e_smart_clip_set;
           sc.clip_unset = _e_smart_clip_unset;
        }
      _e_smart = evas_smart_class_new(&sc);
   }
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Evas *e;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   e = evas_object_evas_get(obj);

   sd->o_smart = obj;
   sd->x = sd->y = sd->w = sd->h = 0;
   sd->iw = sd->ih = 24;
   sd->selected = -1;
   sd->multi_select = 0;

   sd->typebuf.buf = NULL;
   sd->typebuf.size = 0;
   sd->typebuf.timer = NULL;

   sd->o_box = elm_box_add(e_win_evas_win_get(e));
   elm_box_align_set(sd->o_box, 0.0, 0.0);
   elm_box_homogeneous_set(sd->o_box, 0);
   evas_object_smart_member_add(sd->o_box, obj);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN,
                                  _e_smart_event_key_down, sd);
   evas_object_propagate_events_set(obj, 0);

   sd->o_edje = edje_object_add(e);
   e_theme_edje_object_set(sd->o_edje, "base/theme/widgets", "e/ilist");
   evas_object_smart_member_add(sd->o_edje, obj);

   evas_object_smart_callback_add(obj, "changed", (Evas_Smart_Cb)_e_ilist_widget_hack_cb, sd);
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;

   _e_typebuf_clean(obj);

   e_ilist_clear(obj);
   evas_object_del(sd->o_box);
   evas_object_del(sd->o_edje);
   eina_stringshare_del(sd->theme);
   free(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->o_edje);
   evas_object_show(sd->o_box);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->o_edje);
   evas_object_hide(sd->o_box);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   if ((sd->x == x) && (sd->y == y)) return;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   if ((sd->w == w) && (sd->h == h)) return;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->o_edje, r, g, b, a);
   evas_object_color_set(sd->o_box, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->o_edje, clip);
   evas_object_clip_set(sd->o_box, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->o_edje);
   evas_object_clip_unset(sd->o_box);
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   evas_object_move(sd->o_edje, sd->x, sd->y);
   evas_object_resize(sd->o_edje, sd->w, sd->h);
   evas_object_move(sd->o_box, sd->x, sd->y);
   evas_object_resize(sd->o_box, sd->w, sd->h);
}

static void
_e_smart_event_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Smart_Data *sd;
   Evas_Event_Mouse_Down *ev;
   E_Ilist_Item *si;

   ev = event_info;
   si = data;
   sd = si->sd;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) sd->on_hold = 1;
   else sd->on_hold = 0;

   /* NB: Remove if headers ever become selectable */
   if (si->header) return;

   if (ev->flags & EVAS_BUTTON_DOUBLE_CLICK)
     evas_object_smart_callback_call(sd->o_smart, "selected", NULL);
}

static void
_e_smart_event_mouse_up(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Smart_Data *sd;
   Evas_Event_Mouse_Up *ev;
   E_Ilist_Item *si, *item;
   Eina_List *l = NULL;
   int i;

   ev = event_info;
   si = data;
   sd = si->sd;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) sd->on_hold = 1;
   else sd->on_hold = 0;

   /* NB: Remove if headers ever become selectable */
   if (si->header) return;

   if (sd->on_hold)
     {
        sd->on_hold = 0;
        return;
     }

   if (!sd->items) return;

   i = 0;
   EINA_LIST_FOREACH(sd->items, l, item)
     {
        if (item == si)
          {
             if (!sd->multi_select)
               e_ilist_selected_set(sd->o_smart, i);
             else
               {
                  if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
                    e_ilist_range_select(sd->o_smart, i);
                  else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
                    e_ilist_multi_select(sd->o_smart, i);
                  else
                    e_ilist_selected_set(sd->o_smart, i);
               }
             break;
          }
        i++;
     }

   if (!sd->selector) return;
   if (!(si = eina_list_nth(sd->items, sd->selected))) return;
   if (si->func) si->func(si->data, si->data2);
}

static void
_e_smart_event_key_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Key_Down *ev;
   E_Smart_Data *sd;
   E_Ilist_Item *si;
   int n, ns;

   sd = data;
   ev = event_info;
   ns = sd->selected;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) sd->on_hold = 1;
   else sd->on_hold = 0;

   if ((!strcmp(ev->key, "Up")) || (!strcmp(ev->key, "KP_Up")))
     {
        n = ns;
        do
          {
             if (n == 0)
               {
                  n = ns;
                  break;
               }
             --n;
             si = eina_list_nth(sd->items, n);
          }
        while ((si) && (si->header));
        if (n != ns)
          {
             if (!sd->multi_select)
               e_ilist_selected_set(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               e_ilist_multi_select(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               e_ilist_range_select(sd->o_smart, n);
             else
               e_ilist_selected_set(sd->o_smart, n);
          }
     }
   else if ((!strcmp(ev->key, "Down")) || (!strcmp(ev->key, "KP_Down")))
     {
        n = ns;
        do
          {
             if (n == ((int)eina_list_count(sd->items) - 1))
               {
                  n = ns;
                  break;
               }
             ++n;
             si = eina_list_nth(sd->items, n);
          }
        while ((si) && (si->header));
        if (n != ns)
          {
             if (!sd->multi_select)
               e_ilist_selected_set(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               e_ilist_multi_select(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               e_ilist_range_select(sd->o_smart, n);
             else
               e_ilist_selected_set(sd->o_smart, n);
          }
     }
   else if ((!strcmp(ev->key, "Home")) || (!strcmp(ev->key, "KP_Home")))
     {
        n = -1;
        do
          {
             if (n == ((int)eina_list_count(sd->items) - 1))
               {
                  n = ns;
                  break;
               }
             ++n;
             si = eina_list_nth(sd->items, n);
          }
        while ((si) && (si->header));
        if (n != ns)
          {
             if (!sd->multi_select)
               e_ilist_selected_set(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               e_ilist_multi_select(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               e_ilist_range_select(sd->o_smart, n);
             else
               e_ilist_selected_set(sd->o_smart, n);
          }
     }
   else if ((!strcmp(ev->key, "End")) || (!strcmp(ev->key, "KP_End")))
     {
        n = eina_list_count(sd->items);
        do
          {
             if (n == 0)
               {
                  n = ns;
                  break;
               }
             --n;
             si = eina_list_nth(sd->items, n);
          }
        while ((si) && (si->header));
        if (n != ns)
          {
             if (!sd->multi_select)
               e_ilist_selected_set(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Control"))
               e_ilist_multi_select(sd->o_smart, n);
             else if (evas_key_modifier_is_set(ev->modifiers, "Shift"))
               e_ilist_range_select(sd->o_smart, n);
             else
               e_ilist_selected_set(sd->o_smart, n);
          }
     }
   else if ((!strcmp(ev->key, "Return")) ||
            (!strcmp(ev->key, "KP_Enter")) ||
            (!strcmp(ev->key, "space") && !sd->typebuf.buf))
     {
        if (!sd->on_hold)
          {
             si = eina_list_nth(sd->items, sd->selected);
             if (si)
               {
                  if (si->func) si->func(si->data, si->data2);
               }
          }
     }
   else if (!strcmp(ev->key, "Escape"))
     _e_typebuf_clean(obj);
   else if (strcmp(ev->key, "BackSpace") && strcmp(ev->key, "Tab") && ev->string)
     _e_typebuf_add(obj, ev->string);

   sd->on_hold = 0;
}

static void
_e_ilist_widget_hack_cb(E_Smart_Data *sd, Evas_Object *obj EINA_UNUSED, Evas_Object *scr)
{
   int w, h;
   e_scrollframe_child_viewport_size_get(scr, &w, &h);
   evas_object_resize(sd->o_edje, w, h);
}

static void
_e_typebuf_add(Evas_Object *obj, const char *s)
{
   int len;

   INTERNAL_ENTRY;

   if (!sd->typebuf.buf)
     {
        sd->typebuf.buf = malloc(16);
        if (sd->typebuf.buf)
          {
             sd->typebuf.size = 16;
             sd->typebuf.buf[0] = '\0';
          }
        else
          {
             _e_typebuf_clean(obj);
             return;
          }
     }

   len = strlen(sd->typebuf.buf);
   if (len + strlen(s) + 2 + 1 >= sd->typebuf.size)
     {
        char *p = realloc(sd->typebuf.buf, sd->typebuf.size + strlen(s) + 16);
        if (p)
          {
             sd->typebuf.buf = p;
             sd->typebuf.size = sd->typebuf.size + strlen(s) + 16;
          }
        else
          {
             _e_typebuf_clean(obj);
             return;
          }
     }

   strcat(sd->typebuf.buf, s);
   edje_object_part_text_set(sd->o_edje, "e.text.typebuf_label", sd->typebuf.buf);
   edje_object_signal_emit(sd->o_edje, "e,state,typebuf,start", "e");
   _e_typebuf_match(obj);
   _e_typebuf_timer_update(obj);
}

static void
_e_typebuf_match(Evas_Object *obj)
{
   char *match;
   Eina_List *l;
   int n;
   E_Ilist_Item *si = NULL;

   INTERNAL_ENTRY;

   match = malloc(strlen(sd->typebuf.buf) + 2 + 1);
   if (!match) return;

   strcpy(match, "*");
   strcat(match, sd->typebuf.buf);
   strcat(match, "*");

   n = 0;
   EINA_LIST_FOREACH(sd->items, l, si)
     {
        const char *label = NULL;

        if (si)
          {
             if (si->label)
               label = si->label;
             else
               label = edje_object_part_text_get(si->o_base, "e.text.label");

             if (e_util_glob_case_match(label, match))
               {
                  e_ilist_selected_set(obj, n);
                  break;
               }
          }
        n++;
     }

   free(match);
}

static Eina_Bool
_e_typebuf_timer_cb(void *data)
{
   Evas_Object *obj = data;

   _e_typebuf_clean(obj);
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_typebuf_timer_update(Evas_Object *obj)
{
   INTERNAL_ENTRY;

   if (sd->typebuf.timer)
     ecore_timer_del(sd->typebuf.timer);

   sd->typebuf.timer = ecore_timer_loop_add(3.0, _e_typebuf_timer_cb, obj);
}

static void
_e_typebuf_timer_delete(Evas_Object *obj)
{
   INTERNAL_ENTRY;

   if (sd->typebuf.timer)
     {
        ecore_timer_del(sd->typebuf.timer);
        sd->typebuf.timer = NULL;
     }
}

static void
_e_typebuf_clean(Evas_Object *obj)
{
   INTERNAL_ENTRY;

   E_FREE(sd->typebuf.buf);
   sd->typebuf.size = 0;
   _e_typebuf_timer_delete(obj);
   edje_object_signal_emit(sd->o_edje, "e,state,typebuf,stop", "e");
}

static void
_item_select(E_Ilist_Item *si)
{
   const char *selectraise;
   E_Smart_Data *sd = si->sd;
   si->selected = EINA_TRUE;
   selectraise = edje_object_data_get(si->o_base, "selectraise");
   if ((selectraise) && (!strcmp(selectraise, "on")))
     evas_object_raise(si->o_base);
   edje_object_signal_emit(si->o_base, "e,state,selected", "e");
   if (si->o_icon)
     {
        const char *t = evas_object_type_get(si->o_icon);

        if (!strcmp(t, "edje"))
          edje_object_signal_emit(si->o_icon, "e,state,selected", "e");
        else if (!strcmp(t, "e_icon"))
          e_icon_selected_set(si->o_icon, EINA_TRUE);
     }
   sd->selected_items = eina_list_append(sd->selected_items, si);
}

static void
_item_unselect(E_Ilist_Item *si)
{
   const char *stacking, *selectraise;
   E_Smart_Data *sd = si->sd;
   si->selected = EINA_FALSE;
   edje_object_signal_emit(si->o_base, "e,state,unselected", "e");
   if (si->o_icon)
     {
        if (strcmp(evas_object_type_get(si->o_icon), "e_icon") && e_icon_edje_get(si->o_icon))
          edje_object_signal_emit(si->o_icon, "e,state,unselected", "e");
        else
          e_icon_selected_set(si->o_icon, EINA_FALSE);
     }
   stacking = edje_object_data_get(si->o_base, "stacking");
   selectraise = edje_object_data_get(si->o_base, "selectraise");
   if ((selectraise) && (!strcmp(selectraise, "on")))
     {
        if ((stacking) && (!strcmp(stacking, "below")))
          evas_object_lower(si->o_base);
     }
   sd->selected_items = eina_list_remove(sd->selected_items, si);
}

static void
_e_ilist_item_theme_set(E_Ilist_Item *si, Eina_Bool custom, Eina_Bool header, Eina_Bool even)
{
   E_Smart_Data *sd = si->sd;
   const char *file;
   char buf[4096];

   if ((!custom) || (!sd->theme))
     {
        if (header)
          {
             if (!even)
               {
                  if (!e_theme_edje_object_set(si->o_base, "base/theme/widgets",
                                               "e/widgets/ilist_header_odd"))
                    e_theme_edje_object_set(si->o_base, "base/theme/widgets",
                                            "e/widgets/ilist_header");
               }
             else
               e_theme_edje_object_set(si->o_base, "base/theme/widgets",
                                       "e/widgets/ilist_header");
          }
        else
          {
             if (!even)
               e_theme_edje_object_set(si->o_base, "base/theme/widgets",
                                       "e/widgets/ilist_odd");
             else
               e_theme_edje_object_set(si->o_base, "base/theme/widgets",
                                       "e/widgets/ilist");
          }
        return;
     }
   edje_object_file_get(sd->o_edje, &file, NULL);
   if (header)
     {
        if (even)
          {
             snprintf(buf, sizeof(buf), "%s/ilist_header", sd->theme);
             if (edje_object_file_set(si->o_base, file, buf)) return;
             _e_ilist_item_theme_set(si, EINA_FALSE, header, even);
             return;
          }
        snprintf(buf, sizeof(buf), "%s/ilist_header_odd", sd->theme);
        if (edje_object_file_set(si->o_base, file, buf)) return;
        _e_ilist_item_theme_set(si, EINA_FALSE, header, even);
        return;
     }
   if (even)
     {
        snprintf(buf, sizeof(buf), "%s/ilist", sd->theme);
        if (edje_object_file_set(si->o_base, file, buf)) return;
        _e_ilist_item_theme_set(si, EINA_FALSE, header, even);
        return;
     }
   snprintf(buf, sizeof(buf), "%s/ilist_odd", sd->theme);
   if (edje_object_file_set(si->o_base, file, buf)) return;
   _e_ilist_item_theme_set(si, EINA_FALSE, header, even);
   return;
}

static E_Ilist_Item *
_e_ilist_item_new(E_Smart_Data *sd, Evas_Object *icon, Evas_Object *end, const char *label, int header, Ecore_End_Cb func, Ecore_End_Cb func_hilight, void *data, void *data2)
{
   int isodd;
   E_Ilist_Item *si;

   si = E_NEW(E_Ilist_Item, 1);
   si->sd = sd;
   si->o_base = edje_object_add(evas_object_evas_get(sd->o_smart));
   E_EXPAND(si->o_base);
   E_FILL(si->o_base);

   isodd = eina_list_count(sd->items) & 0x1;
   _e_ilist_item_theme_set(si, !!sd->theme, header, !isodd);
   if (label)
     {
        si->label = eina_stringshare_add(label);
        edje_object_part_text_set(si->o_base, "e.text.label", label);
     }

   si->o_icon = icon;
   if (si->o_icon)
     {
        evas_object_size_hint_min_set(si->o_icon, sd->iw, sd->ih);
        edje_object_part_swallow(si->o_base, "e.swallow.icon", si->o_icon);
        evas_object_show(si->o_icon);
     }
   si->o_end = end;
   if (si->o_end)
     {
        Evas_Coord ew = 0, eh = 0;

        evas_object_size_hint_min_get(si->o_end, &ew, &eh);
        if ((ew <= 0) || (eh <= 0))
          {
             ew = sd->iw;
             eh = sd->ih;
          }
        evas_object_size_hint_min_set(si->o_end, ew, eh);
        edje_object_part_swallow(si->o_base, "e.swallow.end", si->o_end);
        evas_object_show(si->o_end);
     }
   si->func = func;
   si->func_hilight = func_hilight;
   si->data = data;
   si->data2 = data2;
   si->header = header;

   evas_object_event_callback_add(si->o_base, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_smart_event_mouse_down, si);
   evas_object_event_callback_add(si->o_base, EVAS_CALLBACK_MOUSE_UP,
                                  _e_smart_event_mouse_up, si);
   if (sd->disabled)
     edje_object_signal_emit(si->o_base, "e,state,disabled", "e");
   else
     edje_object_signal_emit(si->o_base, "e,state,enabled", "e");
   return si;
}

E_API void
e_ilist_disabled_set(Evas_Object *obj, Eina_Bool set)
{
   E_Ilist_Item *ili;
   const Eina_List *l;

   API_ENTRY return;
   sd->disabled = !!set;
   EINA_LIST_FOREACH(sd->items, l, ili)
     {
        if (sd->disabled)
          edje_object_signal_emit(ili->o_base, "e,state,disabled", "e");
        else
          edje_object_signal_emit(ili->o_base, "e,state,enabled", "e");
     }
}
#include <Eina.h>
#include <Eet.h>
#include <Ecore_File.h>
#include "e_macros.h"
#define E_TYPEDEFS
#include "e_config_data.h"
#include "e_intl_data.h"
#undef E_TYPEDEFS
#include "e_config_data.h"
#include "e_intl_data.h"


static void _e_help(void);

/* externally accessible functions */

int
main(int argc, char **argv)
{
   int i = 0;
   int valid_args = 0;
   int write_ops = 0;
   Eet_File *ef = NULL;
   E_Input_Method_Config *write_imc = NULL;
   E_Input_Method_Config *read_imc = NULL;

   char *file = NULL;
   char *set_name = NULL;
   char *set_exe = NULL;
   char *set_setup = NULL;
   char *set_gtk_im_module = NULL;
   char *set_qt_im_module = NULL;
   char *set_xmodifiers = NULL;
   char *set_ecore_imf_module = NULL;
   int list = 0;

   /* handle some command-line parameters */
   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-set-name")) && (i < (argc - 1)))
          {
             i++;
             set_name = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-exe")) && (i < (argc - 1)))
          {
             i++;
             set_exe = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-setup")) && (i < (argc - 1)))
          {
             i++;
             set_setup = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-gtk-im-module")) && (i < (argc - 1)))
          {
             i++;
             set_gtk_im_module = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-qt-im-module")) && (i < (argc - 1)))
          {
             i++;
             set_qt_im_module = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-xmodifiers")) && (i < (argc - 1)))
          {
             i++;
             set_xmodifiers = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-set-ecore-imf-module")) && (i < (argc - 1)))
          {
             i++;
             set_ecore_imf_module = argv[i];
             valid_args++;
             write_ops++;
          }
        else if ((!strcmp(argv[i], "-h")) ||
                 (!strcmp(argv[i], "-help")) ||
                 (!strcmp(argv[i], "--h")) ||
                 (!strcmp(argv[i], "--help")))
          {
             _e_help();
             exit(0);
          }
        else if ((!strcmp(argv[i], "-list")))
          {
             list = 1;
             valid_args++;
          }
        else
          file = argv[i];
     }
   if (!file)
     {
        printf("ERROR: no file specified!\n");
        _e_help();
        exit(0);
     }

   if (valid_args == 0)
     {
        printf("ERROR: no valid arguments!\n");
        _e_help();
        exit(0);
     }

   eet_init();
   e_intl_data_init();

   if (write_ops != 0 && ecore_file_exists(file))
     {
        ef = eet_open(file, EET_FILE_MODE_READ_WRITE);
     }
   else if (write_ops != 0)
     {
        ef = eet_open(file, EET_FILE_MODE_WRITE);
     }
   else
     {
        ef = eet_open(file, EET_FILE_MODE_READ);
     }

   if (!ef)
     {
        printf("ERROR: cannot open file %s for READ/WRITE (%i:%s)\n",
               file, errno, strerror(errno));
        return -1;
     }

   /* If File Exists, Try to read imc */
   read_imc = e_intl_input_method_config_read(ef);

   /* else create new imc */
   if (write_ops != 0)
     {
        write_imc = calloc(sizeof(E_Input_Method_Config), 1);
        write_imc->version = E_INTL_INPUT_METHOD_CONFIG_VERSION;
        if (read_imc)
          {
             write_imc->e_im_name = read_imc->e_im_name;
             write_imc->gtk_im_module = read_imc->gtk_im_module;
             write_imc->qt_im_module = read_imc->qt_im_module;
             write_imc->xmodifiers = read_imc->xmodifiers;
             write_imc->ecore_imf_module = read_imc->ecore_imf_module;
             write_imc->e_im_exec = read_imc->e_im_exec;
             write_imc->e_im_setup_exec = read_imc->e_im_setup_exec;
          }

        if (set_name)
          write_imc->e_im_name = set_name;
        if (set_gtk_im_module)
          write_imc->gtk_im_module = set_gtk_im_module;
        if (set_qt_im_module)
          write_imc->qt_im_module = set_qt_im_module;
        if (set_xmodifiers)
          write_imc->xmodifiers = set_xmodifiers;
        if (set_ecore_imf_module)
          write_imc->ecore_imf_module = set_ecore_imf_module;
        if (set_exe)
          write_imc->e_im_exec = set_exe;
        if (set_setup)
          write_imc->e_im_setup_exec = set_setup;

        /* write imc to file */
        e_intl_input_method_config_write(ef, write_imc);
     }

   if (list)
     {
        printf("Config File List:\n");
        printf("Config Version:    %i\n", read_imc->version);
        printf("Config Name:       %s\n", read_imc->e_im_name);
        printf("Command Line:      %s\n", read_imc->e_im_exec);
        printf("Setup Line:        %s\n", read_imc->e_im_setup_exec);
        printf("gtk_im_module:     %s\n", read_imc->gtk_im_module);
        printf("qt_im_module:      %s\n", read_imc->qt_im_module);
        printf("xmodifiers:        %s\n", read_imc->xmodifiers);
        printf("ecore_imf_module:  %s\n", read_imc->ecore_imf_module);
     }

   e_intl_input_method_config_free(read_imc);
   free(write_imc);
   eet_close(ef);
   e_intl_data_shutdown();
   eet_shutdown();
   /* just return 0 to keep the compiler quiet */
   return 0;
}

static void
_e_help(void)
{
   printf("OPTIONS:\n"
          "  -set-name NAME             Set the application name\n"
          "  -set-exe EXE               Set the application execute line\n"
          "  -set-setup EXE             Set the setup application execute line\n"
          "  -set-gtk-im-module         Set the gtk_im_module env var\n"
          "  -set-qt-im-module          Set the qt_im_module env var\n"
          "  -set-ecore-imf-module      Set the ecore_imf_module env var\n"
          "  -set-xmodifiers            Set the xmodifiers env var\n"
          "  -list                      List Contents of Input Method Config file\n"
          );
}

#include "e.h"

#define IMPORT_STRETCH          0
#define IMPORT_TILE             1
#define IMPORT_CENTER           2
#define IMPORT_SCALE_ASPECT_IN  3
#define IMPORT_SCALE_ASPECT_OUT 4
#define IMPORT_PAN              5

static void      _import_edj_gen(E_Import_Config_Dialog *import);
static Eina_Bool _import_cb_edje_cc_exit(void *data, int type, void *event);

static void
_import_edj_gen(E_Import_Config_Dialog *import)
{
   const char *file, *fill, *s;
   char buf[PATH_MAX], fbuf[PATH_MAX], cmd[PATH_MAX + PATH_MAX + 40];
   char *fstrip, *infile, *outfile;
   int num, cr, cg, cb;
   size_t len, off;

   file = ecore_file_file_get(import->file);
   fstrip = ecore_file_strip_ext(file);
   if (!fstrip) return;
   len = e_user_dir_snprintf(buf, sizeof(buf), "backgrounds/.tmp.%s.edj", fstrip);
   if (len >= sizeof(buf))
     {
        free(fstrip);
        return;
     }
   off = len - (sizeof(".edj") - 1);
   for (num = 1; ecore_file_exists(buf) && num < 100; num++)
     snprintf(buf + off, sizeof(buf) - off, "-%d.edj", num);
   free(fstrip);
   cr = import->color.r;
   cg = import->color.g;
   cb = import->color.b;

   if (num == 100)
     {
        printf("Couldn't come up with another filename for %s\n", buf);
        return;
     }
   switch (import->method)
     {
      case IMPORT_STRETCH:          fill = "stretch"; break;
      case IMPORT_TILE:             fill = "tile"; break;
      case IMPORT_CENTER:           fill = "center"; break;
      case IMPORT_SCALE_ASPECT_IN:  fill = "scale_in"; break;
      case IMPORT_SCALE_ASPECT_OUT: fill = "scale_out"; break;
      case IMPORT_PAN:              fill = "pan"; break;
      default: return; break;
     }
   s = e_util_filename_escape(import->file);
   if (s) infile = strdup(s);
   else return;
   s = e_util_filename_escape(buf);
   if (s) outfile = strdup(s);
   else
     {
        free(infile);
        return;
     }
   snprintf(fbuf, sizeof(fbuf), "%s/edje_cc", e_prefix_bin_get());
   if (!ecore_file_can_exec(fbuf))
     snprintf(fbuf, sizeof(fbuf), "edje_cc");
   snprintf(cmd, sizeof(cmd),
            "%s/enlightenment/utils/enlightenment_wallpaper_gen "
            "%s %s %s %s %i %i %i %i",
            e_prefix_lib_get(),
            fbuf, fill, infile, outfile, import->quality, cr, cg, cb);
   free(infile);
   free(outfile);
   import->fdest = eina_stringshare_add(buf);
   import->exe_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                                                 _import_cb_edje_cc_exit,
                                                 import);
   import->exe = ecore_exe_run(cmd, import);
}

static Eina_Bool
_import_cb_edje_cc_exit(void *data, EINA_UNUSED int type, void *event)
{
   E_Import_Config_Dialog *import;
   Ecore_Exe_Event_Del *ev;
   int r = 1;

   ev = event;
   import = data;
   if (ecore_exe_data_get(ev->exe) != import) return ECORE_CALLBACK_PASS_ON;

   if (ev->exit_code != 0)
     {
        e_util_dialog_show(_("Picture Import Error"),
                           _("Enlightenment was unable to import the picture<ps/>"
                             "due to conversion errors."));
        r = 0;
     }

   if (r && import->ok)
     {
        char *p, *newfile = strdup(import->fdest);

        if (!newfile)
          {
             e_object_del(E_OBJECT(import));
             return ECORE_CALLBACK_DONE;
          }
        p = strrchr(newfile, '/');
        if (!p)
          {
             e_object_del(E_OBJECT(import));
             free(newfile);
             return ECORE_CALLBACK_DONE;
          }
        // strip out the .tmp. before the name
        for (p = p + 1; ; p++)
          {
             *p = p[5];
             if (*p == 0) break;
          }
        ecore_file_mv(import->fdest, newfile);
        eina_stringshare_replace(&(import->fdest), newfile);
        free(newfile);

        e_object_ref(E_OBJECT(import));
        import->ok((void *)import->fdest, import);
        e_object_del(E_OBJECT(import));
        e_object_unref(E_OBJECT(import));
     }
   else
     e_object_del(E_OBJECT(import));

   return ECORE_CALLBACK_DONE;
}

static void
_import_cb_close(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Import_Config_Dialog *import = data;

   e_object_ref(data);
   if (import->cancel) import->cancel(import);
   e_object_del(data);
   e_object_unref(data);
}

static void
_import_cb_ok(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Import_Config_Dialog *import = data;
   const char *file;
   char buf[PATH_MAX];
   int is_bg, is_theme, r;

   r = 0;
   if (!import->file) return;
   file = ecore_file_file_get(import->file);
   if (!eina_str_has_extension(file, "edj"))
     {
        _import_edj_gen(import);
        evas_object_hide(import->dia->win);
        return;
     }
   e_user_dir_snprintf(buf, sizeof(buf), "backgrounds/%s", file);

   is_bg = edje_file_group_exists(import->file, "e/desktop/background");
   is_theme = edje_file_group_exists(import->file,
                                     "e/widgets/border/default/border");

   if ((is_bg) && (!is_theme))
     {
        if (!ecore_file_cp(import->file, buf))
          {
             e_util_dialog_show(_("Import Error"),
                                _("Enlightenment was unable to "
                                  "import the image<ps/>due to a "
                                  "copy error."));
          }
        else
          r = 1;
     }
   else
     {
        e_util_dialog_show(_("Import Error"),
                           _("Enlightenment was unable to "
                             "import the image.<ps/><ps/>"
                             "Are you sure this is a valid "
                             "image?"));
     }

   if (r)
     {
        e_object_ref(E_OBJECT(import));
        if (import->ok) import->ok((void *)buf, import);
        e_object_del(E_OBJECT(import));
        e_object_unref(E_OBJECT(import));
     }
   else
     _import_cb_close(import, NULL);
}

static void
_e_import_config_preview_size_get(int size, int w, int h, int *tw, int *th)
{
   if (size <= 0) return;
   double aspect;
   aspect = (double)w / h;

   if (w > size)
     {
        w = size;
        h = (w / aspect);
     }
   *tw = w;
   *th = h;
}

static void
_e_import_config_dia_del(void *data)
{
   E_Dialog *dia = data;
   E_Import_Config_Dialog *import;

   import = dia->data;
   dia->data = NULL;
   e_object_del(E_OBJECT(import));
}

static void
_e_import_config_dialog_del(void *data)
{
   E_Import_Config_Dialog *import = data;

   if (import->exe_handler) ecore_event_handler_del(import->exe_handler);
   import->exe_handler = NULL;
   eina_stringshare_del(import->fdest);
   import->exe = NULL;
   eina_stringshare_del(import->file);
   e_object_del(E_OBJECT(import->dia));
   free(import);
}

static void
_e_import_config_dialog_win_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Dialog *dia = data;
   E_Import_Config_Dialog *import;

   import = dia->data;
   if (!import) return;
   e_object_ref(E_OBJECT(import));
   if (import->cancel) import->cancel(import);
   e_object_del(E_OBJECT(import));
   e_object_unref(E_OBJECT(import));
}

///////////////////////////////////////////////////////////////////////////////////

E_API E_Import_Config_Dialog *
e_import_config_dialog_show(Evas_Object *parent, const char *path, Ecore_End_Cb ok, Ecore_Cb cancel)
{
   Evas *evas;
   E_Dialog *dia;
   E_Import_Config_Dialog *import;
   Evas_Object *o, *of, *ord, *ot, *ol, *preview, *frame;
   E_Radio_Group *rg;
   int w, h, tw, th;

   if (!path) return NULL;

   import = E_OBJECT_ALLOC(E_Import_Config_Dialog, E_IMPORT_CONFIG_DIALOG_TYPE, _e_import_config_dialog_del);
   if (!import) return NULL;

   dia = e_dialog_new(parent, "E", "_import_config_dialog");
   if (!dia)
     {
        e_object_del(E_OBJECT(import));
        return NULL;
     }
   e_dialog_resizable_set(dia, 1);

   e_dialog_title_set(dia, _("Import Settings..."));
   dia->data = import;
   import->dia = dia;
   import->ok = ok, import->cancel = cancel;
   e_object_del_attach_func_set(E_OBJECT(dia), _e_import_config_dia_del);
   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL, _e_import_config_dialog_win_del, dia);

   import->method = IMPORT_SCALE_ASPECT_OUT;
   import->quality = 90;
   import->file = eina_stringshare_add(path);

   evas = evas_object_evas_get(dia->win);

   o = e_widget_list_add(evas, 0, 0);

   ot = e_widget_list_add(evas, 0, 0);
   frame = e_widget_frametable_add(evas, _("Preview"), 1);

   preview = evas_object_image_add(evas);
   evas_object_image_file_set(preview, path, NULL);
   evas_object_image_size_get(preview, &w, &h);
   evas_object_del(preview);

   _e_import_config_preview_size_get(320, w, h, &tw, &th);

   preview = e_widget_preview_add(evas, tw, th);
   e_widget_preview_thumb_set(preview, path, NULL, tw, th);

   e_widget_frametable_object_append(frame, preview, 0, 0, 1, 1, 0, 0, 1, 1);
   e_widget_list_object_append(ot, frame, 1, 1, 0.5);
   of = e_widget_frametable_add(evas, _("Fill and Stretch Options"), 1);
   rg = e_widget_radio_group_new(&import->method);
   ord = e_widget_radio_icon_add(evas, _("Stretch"),
                                 "enlightenment/wallpaper_stretch",
                                 24, 24, IMPORT_STRETCH, rg);
   e_widget_frametable_object_append(of, ord, 0, 0, 1, 1, 1, 0, 1, 0);
   ord = e_widget_radio_icon_add(evas, _("Center"),
                                 "enlightenment/wallpaper_center",
                                 24, 24, IMPORT_CENTER, rg);
   e_widget_frametable_object_append(of, ord, 1, 0, 1, 1, 1, 0, 1, 0);
   ord = e_widget_radio_icon_add(evas, _("Tile"),
                                 "enlightenment/wallpaper_tile",
                                 24, 24, IMPORT_TILE, rg);
   e_widget_frametable_object_append(of, ord, 2, 0, 1, 1, 1, 0, 1, 0);

   ord = e_widget_radio_icon_add(evas, _("Within"),
                                 "enlightenment/wallpaper_scale_aspect_in",
                                 24, 24, IMPORT_SCALE_ASPECT_IN, rg);
   e_widget_frametable_object_append(of, ord, 0, 1, 1, 1, 1, 0, 1, 0);
   ord = e_widget_radio_icon_add(evas, _("Fill"),
                                 "enlightenment/wallpaper_scale_aspect_out",
                                 24, 24, IMPORT_SCALE_ASPECT_OUT, rg);
   e_widget_frametable_object_append(of, ord, 1, 1, 1, 1, 1, 0, 1, 0);
   ord = e_widget_radio_icon_add(evas, _("Pan"),
                                 "enlightenment/wallpaper_pan",
                                 24, 24, IMPORT_PAN, rg);
   e_widget_frametable_object_append(of, ord, 2, 1, 1, 1, 1, 0, 1, 0);
   e_widget_list_object_append(ot, of, 1, 1, 0.5);

   ol = e_widget_list_add(evas, 0, 1);

   of = e_widget_frametable_add(evas, _("File Quality"), 0);
   ord = e_widget_slider_add(evas, 1, 0, _("%3.0f%%"), 0.0, 100.0, 1.0, 0,
                             NULL, &(import->quality), 150);
   e_widget_frametable_object_append(of, ord, 0, 0, 1, 1, 1, 0, 1, 0);
   e_widget_list_object_append(ol, of, 1, 1, 0);

   of = e_widget_framelist_add(evas, _("Fill Color"), 0);
   ord = e_widget_color_well_add(evas, &import->color, 1);
   e_widget_framelist_object_append(of, ord);
   e_widget_list_object_append(ol, of, 1, 0, 1);
   e_widget_list_object_append(ot, ol, 1, 1, 0);

   e_widget_list_object_append(o, ot, 0, 0, 0.5);

   e_widget_size_min_get(o, &w, &h);
   e_dialog_content_set(dia, o, w, h);
   e_dialog_button_add(dia, _("OK"), NULL, _import_cb_ok, import);
   e_dialog_button_add(dia, _("Cancel"), NULL, _import_cb_close, import);
   elm_win_center(dia->win, 1, 1);
   e_dialog_border_icon_set(dia, "folder-image");
   e_dialog_button_focus_num(dia, 0);
   e_dialog_show(dia);

   return import;
}

#include "e.h"

static void
_fsel_path_save(E_Import_Dialog *id)
{
   const char *fdev = NULL, *fpath = NULL;

   e_widget_fsel_path_get(id->fsel_obj, &fdev, &fpath);
   if ((fdev) || (fpath))
     {
        eina_stringshare_replace(&e_config->wallpaper_import_last_dev, fdev);
        eina_stringshare_replace(&e_config->wallpaper_import_last_path, fpath);
        e_config_save_queue();
     }
}

static void
_fsel_cb_close(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Import_Dialog *id = data;

   e_object_ref(data);
   if (id->cancel) id->cancel(id);
   e_object_del(data);
   e_object_unref(data);
}

static void
_import_ok(void *data, void *data2)
{
   E_Import_Dialog *id;

   id = e_object_data_get(data2);
   e_object_ref(E_OBJECT(id));
   if (id->ok) id->ok(data, id);
   e_object_del(E_OBJECT(id));
   e_object_unref(E_OBJECT(id));
}

static void
_fsel_cb_ok(void *data, E_Dialog *dia EINA_UNUSED)
{
   E_Import_Dialog *id;
   const char *path, *p;
   int is_bg, is_theme, r;
   const char *file;
   char buf[PATH_MAX];

   id = data;
   path = e_widget_fsel_selection_path_get(id->fsel_obj);
   if (!path) return;

   p = strrchr(path, '.');
   if ((p) && (strcasecmp(p, ".edj")))
     {
        E_Import_Config_Dialog *import;
        import = e_import_config_dialog_show(id->dia->win, path, _import_ok, NULL);
        e_object_data_set(E_OBJECT(import), id);
        return;
     }
   r = 0;
   file = ecore_file_file_get(path);
   e_user_dir_snprintf(buf, sizeof(buf), "backgrounds/%s", file);

   is_bg = edje_file_group_exists(path, "e/desktop/background");
   is_theme =
     edje_file_group_exists(path, "e/widgets/border/default/border");

   if ((is_bg) && (!is_theme))
     {
        if (!ecore_file_cp(path, buf))
          {
             e_util_dialog_show(_("Import Error"),
                                _("Enlightenment was unable to "
                                  "import the image<ps/>due to a "
                                  "copy error."));
          }
        else
          r = 1;
     }
   else
     {
        e_util_dialog_show(_("Import Error"),
                           _("Enlightenment was unable to "
                             "import the image.<ps/><ps/>"
                             "Are you sure this is a valid "
                             "image?"));
     }

   if (r)
     {
        e_object_ref(E_OBJECT(id));
        if (id->ok) id->ok(buf, id);
        e_object_del(E_OBJECT(id));
        e_object_unref(E_OBJECT(id));
     }
   else
     _fsel_cb_close(id, NULL);
}

static void
_e_import_dia_del(void *data)
{
   E_Dialog *dia = data;
   E_Import_Dialog *id;

   id = dia->data;
   dia->data = NULL;
   e_object_del(E_OBJECT(id));
}

static void
_e_import_dialog_del(void *data)
{
   E_Import_Dialog *id = data;

   _fsel_path_save(id);
   e_object_del(E_OBJECT(id->dia));
   free(id);
}

static void
_e_import_dialog_win_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Dialog *dia = data;
   E_Import_Dialog *id;

   id = dia->data;
   if (id)
     e_object_del(E_OBJECT(id));
}

//////////////////////////////////////////////////////////////////////////////////

E_API E_Import_Dialog *
e_import_dialog_show(Evas_Object *parent EINA_UNUSED, const char *dev, const char *path, Ecore_End_Cb ok, Ecore_Cb cancel)
{
   Evas *evas;
   E_Import_Dialog *id;
   Evas_Object *ofm;
   int w, h;
   const char *fdev, *fpath;
   char buf[PATH_MAX], *rpath;
   E_Dialog *dia;

   id = E_OBJECT_ALLOC(E_Import_Dialog, E_IMPORT_DIALOG_TYPE, _e_import_dialog_del);
   if (!id) return NULL;

   dia = e_dialog_new(NULL, "E", "_import_fsel_dialog");
   if (!dia)
     {
        e_object_del(E_OBJECT(id));
        return NULL;
     }
   e_dialog_resizable_set(dia, 1);

   dia->data = id;
   id->dia = dia;
   id->ok = ok, id->cancel = cancel;
   e_object_del_attach_func_set(E_OBJECT(dia), _e_import_dia_del);
   evas_object_event_callback_add(dia->win, EVAS_CALLBACK_DEL, _e_import_dialog_win_del, dia);

   evas = evas_object_evas_get(dia->win);
   e_dialog_title_set(dia, _("Select a Picture..."));

   fdev = dev ? : e_config->wallpaper_import_last_dev;
   fpath = path ? : e_config->wallpaper_import_last_path;
   if (fdev)
     snprintf(buf, sizeof(buf), "%s/%s",
              fdev, fpath);
   else
     snprintf(buf, sizeof(buf), "%s", fpath);

   rpath = ecore_file_realpath(buf);
   if (!ecore_file_exists(rpath))
     fpath = "/";
   free(rpath);

   if ((!fdev) && (!fpath))
     {
        fdev = "~/";
        fpath = "/";
     }

   //printf("LAST: [%s] '%s' '%s'\n", buf, fdev, fpath);
   /* if we ever use the second param in _fsel_cb_ok() then we will have to create a second function
    * for this callback, but as of now it's okay
    */
   ofm = e_widget_fsel_add(evas, fdev, fpath, NULL, NULL, (void *)_fsel_cb_ok, id,
                           NULL, NULL, 1);
   e_widget_fsel_window_set(ofm, dia->win);
   id->fsel_obj = ofm;
   e_widget_size_min_get(ofm, &w, &h);
   e_dialog_content_set(dia, ofm, w, h);

   e_dialog_button_add(dia, _("Use"), NULL, _fsel_cb_ok, id);
   e_dialog_button_add(dia, _("Cancel"), NULL, _fsel_cb_close, id);
   e_dialog_border_icon_set(dia, "enlightenment/background");
   e_dialog_show(dia);
   elm_win_center(dia->win, 1, 1);
   e_widget_focus_set(ofm, 1);

   return id;
}

#include "e.h"

E_API int E_EVENT_INIT_DONE = 0;

/* local variables */
static int done = 0;
static int undone = 0;
static Evas_Object *_e_init_object = NULL;
static Eina_List *splash_objs = NULL;
static Ecore_Timer *_e_init_timeout_timer = NULL;
static Ecore_Job *_e_init_update_job = NULL;
static Ecore_Event_Handler *_e_init_event_zone_add = NULL;
static Ecore_Event_Handler *_e_init_event_zone_del = NULL;
static Ecore_Event_Handler *_e_init_event_zone_move_resize = NULL;
static Eina_Bool _pre_called = EINA_FALSE;

static Eina_Bool
_e_init_cb_timeout(void *data EINA_UNUSED)
{
   _e_init_timeout_timer = NULL;
   e_init_hide();
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_init_cb_signal_done_ok(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   e_init_hide();
}

static void
_e_init_render_pre(void *data EINA_UNUSED, Evas *e, void *info EINA_UNUSED)
{
   Eina_List *l;
   Evas_Object *o;

   _pre_called = EINA_TRUE;
   evas_event_callback_del(e, EVAS_CALLBACK_RENDER_PRE, _e_init_render_pre);
   EINA_LIST_FOREACH(splash_objs, l, o)
     {
        evas_object_show(o);
     }
}

static E_Zone *
_get_zone_num(int num)
{
   E_Zone *zone;
   Eina_List *l;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if ((int)zone->num == num) return zone;
     }
   return NULL;
}

static Evas_Object *
_get_splash_num(int num)
{
   Evas_Object *o;
   Eina_List *l;

   EINA_LIST_FOREACH(splash_objs, l, o)
     {
        int n = (intptr_t)evas_object_data_get(o, "num");
        if (n > 0)
          {
             n--;
             if (num == n) return o;
          }
     }
   return NULL;
}

static void
_e_init_splash_obj_zone_update(Evas_Object *o, E_Zone *zone)
{
   evas_object_clip_set(o, zone->bg_clip_object);
   evas_object_move(o, zone->x, zone->y);
   evas_object_resize(o, zone->w, zone->h);
   evas_object_layer_set(o, E_LAYER_MAX - 1000);
}

static Evas_Object *
_e_init_splash_obj_new(E_Zone *zone)
{
   Evas_Object *o;

   o = edje_object_add(e_comp->evas);
   evas_object_data_set(o, "num", (void *)(intptr_t)(zone->num + 1));
   if (!zone->num)
     {
        e_theme_edje_object_set(o, NULL, "e/init/splash");
        edje_object_part_text_set(o, "e.text.disable_text", "");
        edje_object_signal_callback_add(o, "e,state,done_ok", "e",
                                        _e_init_cb_signal_done_ok, NULL);
     }
   else
     e_theme_edje_object_set(o, NULL, "e/init/extra_screen");
   _e_init_splash_obj_zone_update(o, zone);
   splash_objs = eina_list_append(splash_objs, o);
   return o;
}

static void
_e_init_zone_change_job(void *data EINA_UNUSED)
{
   Evas_Object *o;
   E_Zone *zone;
   Eina_List *l, *ll;

   _e_init_update_job = NULL;
   if (done > 0) return;
   // pass 1 - delete splash objects for zones that have gone OR
   // update the zone obj to have the right clip and geometry
   EINA_LIST_FOREACH_SAFE(splash_objs, l, ll, o)
     {
        int num = (intptr_t)evas_object_data_get(o, "num");
        if (num > 0)
          {
             num--;
             zone = _get_zone_num(num);
             if (!zone)
               {
                  if (o == _e_init_object)
                    {
                       if (_e_init_timeout_timer)
                         {
                            ecore_timer_del(_e_init_timeout_timer);
                            _e_init_timeout_timer =
                              ecore_timer_add(2.0, _e_init_cb_timeout, NULL);
                         }
                       _e_init_object = NULL;
                    }
                  splash_objs = eina_list_remove_list(splash_objs, l);
                  evas_object_del(o);
               }
             else _e_init_splash_obj_zone_update(o, zone);
          }
        // something went wrong - so delete it
        else
          {
             if (o == _e_init_object) _e_init_object = NULL;
             splash_objs = eina_list_remove_list(splash_objs, l);
             evas_object_del(o);
          }
     }
   // pass 2 - add splash objects for new zones
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        o = _get_splash_num(zone->num);
        if (!o) // no splash obj for this zone, add one
          {
             o = _e_init_splash_obj_new(zone);
             if (_pre_called) evas_object_show(o);
          }
     }
}

static Eina_Bool
_e_init_zone_change(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   if (_e_init_update_job) ecore_job_del(_e_init_update_job);
   _e_init_update_job = ecore_job_add(_e_init_zone_change_job, NULL);
   return ECORE_CALLBACK_PASS_ON;
}

/* public functions */
EINTERN int
e_init_init(void)
{
   E_EVENT_INIT_DONE = ecore_event_type_new();

   done = 0;
   return 1;
}

EINTERN int
e_init_shutdown(void)
{
   /* if not killed, kill init */
   ecore_event_handler_del(_e_init_event_zone_add);
   ecore_event_handler_del(_e_init_event_zone_del);
   if (_e_init_event_zone_move_resize)
     {
        ecore_event_handler_del(_e_init_event_zone_move_resize);
        _e_init_event_zone_move_resize = NULL;
     }
   _e_init_event_zone_add = NULL;
   _e_init_event_zone_del = NULL;
   e_init_hide();
   return 1;
}

E_API void
e_init_show(void)
{
   Evas_Object *o;
   E_Zone *zone;
   Eina_List *l;
   /* exec init */

   _e_init_event_zone_add =
     ecore_event_handler_add(E_EVENT_ZONE_ADD, _e_init_zone_change, NULL);
   _e_init_event_zone_del =
     ecore_event_handler_add(E_EVENT_ZONE_DEL, _e_init_zone_change, NULL);
   _e_init_event_zone_move_resize =
     ecore_event_handler_add(E_EVENT_ZONE_MOVE_RESIZE, _e_init_zone_change, NULL);
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        o = _e_init_splash_obj_new(zone);
        if (!zone->num) _e_init_object = o;
     }
   evas_event_callback_add
     (e_comp->evas, EVAS_CALLBACK_RENDER_PRE, _e_init_render_pre, NULL);
   _e_init_timeout_timer = ecore_timer_loop_add(60.0, _e_init_cb_timeout, NULL);
   e_init_title_set(_("Enlightenment"));
   e_init_version_set(VERSION);
}

E_API void
e_init_hide(void)
{
   if (_e_init_event_zone_move_resize)
     {
        ecore_event_handler_del(_e_init_event_zone_move_resize);
        _e_init_event_zone_move_resize = NULL;
     }
   E_FREE_LIST(splash_objs, evas_object_del);
   e_comp_shape_queue();
   _e_init_object = NULL;
   E_FREE_FUNC(_e_init_timeout_timer, ecore_timer_del);
   E_FREE_FUNC(_e_init_update_job, ecore_job_del);
}

E_API void
e_init_title_set(const char *str)
{
   if (!_e_init_object) return;
   edje_object_part_text_set(_e_init_object, "e.text.title", str);
}

E_API void
e_init_version_set(const char *str)
{
   if (!_e_init_object) return;
   edje_object_part_text_set(_e_init_object, "e.text.version", str);
}

E_API void
e_init_status_set(const char *str)
{
   if (!_e_init_object) return;
   edje_object_part_text_set(_e_init_object, "e.text.status", str);
}

E_API void
e_init_done(void)
{
   Eina_List *l;
   Evas_Object *o;

   undone--;
   if (undone > 0) return;
   if (!done)
     {
        done = 1;
        ecore_event_add(E_EVENT_INIT_DONE, NULL, NULL, NULL);
        EINA_LIST_FOREACH(splash_objs, l, o)
          {
             edje_object_signal_emit(o, "e,state,done", "e");
          }
     }
}

E_API void
e_init_undone(void)
{
   undone++;
}

E_API int
e_init_count_get(void)
{
   return undone;
}
#include "e.h"

/* PROTOTYPES - same all the time */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _basic_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _advanced_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Evas_Object *_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);

/* Actual config data we will be playing with while the dialog is active */
struct _E_Config_Dialog_Data
{
   E_Client *client;
   /*- BASIC -*/
   int       do_what_i_say;
   int       protect_from_me;
   int       keep_my_border;
   int       important_window;
   int       remember_locks;
   /*- ADVANCED -*/
   struct
   {
      struct
      {
         int location;
         int size;
         int stacking;
         int iconify;
         int desk; /* unused atm */
         int sticky;
         int shade;
         int maximize;
         int fullscreen;
      } user, client;
      int border;
      int close;
      int focus_in; /* unused atm */
      int focus_out; /* unused atm */
      int life;

      int remember;
   } lock;
};

/* a nice easy setup function that does the dirty work */
E_API void
e_int_client_locks(E_Client *ec)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);
   if (v)
     {
        /* methods */
        v->create_cfdata = _create_data;
        v->free_cfdata = _free_data;
        v->basic.apply_cfdata = _basic_apply_data;
        v->basic.create_widgets = _basic_create_widgets;
        v->advanced.apply_cfdata = _advanced_apply_data;
        v->advanced.create_widgets = _advanced_create_widgets;
        v->override_auto_apply = 1;
        /* create config diaolg for bd object/data */
        cfd = e_config_dialog_new(NULL,
                                  _("Window Locks"),
                                  "E", "_border_locks_dialog",
                                  NULL, 0, v, ec);
        ec->border_locks_dialog = cfd;
        evas_object_layer_set(e_win_client_get(cfd->dia->win)->frame, ec->layer);
     }
}

/**--CREATE--**/
static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   cfdata->lock.user.location = (int)cfdata->client->lock_user_location & 0x1;
   cfdata->lock.user.size = (int)cfdata->client->lock_user_size & 0x1;
   cfdata->lock.user.stacking = (int)cfdata->client->lock_user_stacking & 0x1;
   cfdata->lock.user.iconify = (int)cfdata->client->lock_user_iconify & 0x1;
   cfdata->lock.user.desk = (int)cfdata->client->lock_user_desk & 0x1;
   cfdata->lock.user.sticky = (int)cfdata->client->lock_user_sticky & 0x1;
   cfdata->lock.user.shade = (int)cfdata->client->lock_user_shade & 0x1;
   cfdata->lock.user.maximize = (int)cfdata->client->lock_user_maximize & 0x1;
   cfdata->lock.user.fullscreen = (int)cfdata->client->lock_user_fullscreen & 0x1;
   cfdata->lock.client.location = (int)cfdata->client->lock_client_location & 0x1;
   cfdata->lock.client.size = (int)cfdata->client->lock_client_size & 0x1;
   cfdata->lock.client.stacking = (int)cfdata->client->lock_client_stacking & 0x1;
   cfdata->lock.client.iconify = (int)cfdata->client->lock_client_iconify & 0x1;
   cfdata->lock.client.desk = (int)cfdata->client->lock_client_desk & 0x1;
   cfdata->lock.client.sticky = (int)cfdata->client->lock_client_sticky & 0x1;
   cfdata->lock.client.shade = (int)cfdata->client->lock_client_shade & 0x1;
   cfdata->lock.client.maximize = (int)cfdata->client->lock_client_maximize & 0x1;
   cfdata->lock.client.fullscreen = (int)cfdata->client->lock_client_fullscreen & 0x1;
   cfdata->lock.border = (int)cfdata->client->lock_border & 0x1;
   cfdata->lock.close = (int)cfdata->client->lock_close & 0x1;
   cfdata->lock.focus_in = (int)cfdata->client->lock_focus_in & 0x1;
   cfdata->lock.focus_out = (int)cfdata->client->lock_focus_out & 0x1;
   cfdata->lock.life = (int)cfdata->client->lock_life & 0x1;
   if ((cfdata->client->remember) &&
       (cfdata->client->remember->apply & E_REMEMBER_APPLY_LOCKS))
     cfdata->lock.remember = 1;

   /* figure out basic config from the current locks */
   if (cfdata->lock.user.location || cfdata->lock.user.size ||
       cfdata->lock.user.stacking || cfdata->lock.user.iconify ||
       cfdata->lock.user.desk || cfdata->lock.user.sticky ||
       cfdata->lock.user.shade || cfdata->lock.user.maximize ||
       cfdata->lock.user.fullscreen)
     cfdata->protect_from_me = 1;
   if (cfdata->lock.client.location || cfdata->lock.client.size ||
       cfdata->lock.client.stacking || cfdata->lock.client.iconify ||
       cfdata->lock.client.desk || cfdata->lock.client.sticky ||
       cfdata->lock.client.shade || cfdata->lock.client.maximize ||
       cfdata->lock.client.fullscreen)
     cfdata->do_what_i_say = 1;
   if (cfdata->lock.border)
     cfdata->keep_my_border = 1;
   if (cfdata->lock.close || cfdata->lock.life)
     cfdata->important_window = 1;
   cfdata->remember_locks = cfdata->lock.remember;
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   /* Create cfdata - cfdata is a temporary block of config data that this
    * dialog will be dealing with while configuring. it will be applied to
    * the running systems/config in the apply methods
    */
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->client = cfd->data;
   _fill_data(cfdata);
   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   /* Free the cfdata */
   cfdata->client->border_locks_dialog = NULL;
   free(cfdata);
}

/**--APPLY--**/
static int
_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   int flag;
   /* Actually take our cfdata settings and apply them in real life */

   flag = cfdata->do_what_i_say;
   cfdata->client->lock_client_location = flag;
   cfdata->client->lock_client_size = flag;
   cfdata->client->lock_client_stacking = flag;
   cfdata->client->lock_client_iconify = flag;
   cfdata->client->lock_client_desk = 0;
   cfdata->client->lock_client_sticky = flag;
   cfdata->client->lock_client_shade = flag;
   cfdata->client->lock_client_maximize = flag;
   cfdata->client->lock_client_fullscreen = flag;

   flag = cfdata->protect_from_me;
   cfdata->client->lock_user_location = flag;
   cfdata->client->lock_user_size = flag;
   cfdata->client->lock_user_stacking = flag;
   cfdata->client->lock_user_iconify = flag;
   cfdata->client->lock_user_desk = 0;
   cfdata->client->lock_user_sticky = flag;
   cfdata->client->lock_user_shade = flag;
   cfdata->client->lock_user_maximize = flag;
   cfdata->client->lock_user_fullscreen = flag;

   flag = cfdata->important_window;
   cfdata->client->lock_close = flag;
   cfdata->client->lock_life = flag;

   flag = cfdata->keep_my_border;
   cfdata->client->lock_border = flag;

   if (cfdata->remember_locks)
     {
        if (!cfdata->client->remember)
          {
             cfdata->client->remember = e_remember_new();
             if (cfdata->client->remember)
               e_remember_use(cfdata->client->remember);
          }
        if (cfdata->client->remember)
          {
             cfdata->client->remember->apply |= E_REMEMBER_APPLY_LOCKS;
             e_remember_default_match_set(cfdata->client->remember, cfdata->client);
             e_remember_update(cfdata->client);
          }
     }
   else
     {
        if (cfdata->client->remember)
          {
             cfdata->client->remember->apply &= ~E_REMEMBER_APPLY_LOCKS;
             if (cfdata->client->remember->apply == 0)
               {
                  e_remember_unuse(cfdata->client->remember);
                  e_remember_del(cfdata->client->remember);
                  cfdata->client->remember = NULL;
               }
          }
     }
   e_config_save_queue();
   return 1; /* Apply was OK */
}

static int
_advanced_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   /* Actually take our cfdata settings and apply them in real life */
   cfdata->client->lock_user_location = cfdata->lock.user.location;
   cfdata->client->lock_user_size = cfdata->lock.user.size;
   cfdata->client->lock_user_stacking = cfdata->lock.user.stacking;
   cfdata->client->lock_user_iconify = cfdata->lock.user.iconify;
   cfdata->client->lock_user_desk = cfdata->lock.user.desk;
   cfdata->client->lock_user_sticky = cfdata->lock.user.sticky;
   cfdata->client->lock_user_shade = cfdata->lock.user.shade;
   cfdata->client->lock_user_maximize = cfdata->lock.user.maximize;
   cfdata->client->lock_user_fullscreen = cfdata->lock.user.fullscreen;
   cfdata->client->lock_client_location = cfdata->lock.client.location;
   cfdata->client->lock_client_size = cfdata->lock.client.size;
   cfdata->client->lock_client_stacking = cfdata->lock.client.stacking;
   cfdata->client->lock_client_iconify = cfdata->lock.client.iconify;
   cfdata->client->lock_client_desk = cfdata->lock.client.desk;
   cfdata->client->lock_client_sticky = cfdata->lock.client.sticky;
   cfdata->client->lock_client_shade = cfdata->lock.client.shade;
   cfdata->client->lock_client_maximize = cfdata->lock.client.maximize;
   cfdata->client->lock_client_fullscreen = cfdata->lock.client.fullscreen;
   cfdata->client->lock_border = cfdata->lock.border;
   cfdata->client->lock_close = cfdata->lock.close;
   cfdata->client->lock_focus_in = cfdata->lock.focus_in;
   cfdata->client->lock_focus_out = cfdata->lock.focus_out;
   cfdata->client->lock_life = cfdata->lock.life;

   if (cfdata->lock.remember)
     {
        if (!cfdata->client->remember)
          {
             cfdata->client->remember = e_remember_new();
             if (cfdata->client->remember)
               e_remember_use(cfdata->client->remember);
          }
        if (cfdata->client->remember)
          {
             cfdata->client->remember->apply |= E_REMEMBER_APPLY_LOCKS;
             e_remember_default_match_set(cfdata->client->remember, cfdata->client);
             e_remember_update(cfdata->client);
          }
     }
   else
     {
        if (cfdata->client->remember)
          {
             cfdata->client->remember->apply &= ~E_REMEMBER_APPLY_LOCKS;
             if (cfdata->client->remember->apply == 0)
               {
                  e_remember_unuse(cfdata->client->remember);
                  e_remember_del(cfdata->client->remember);
                  cfdata->client->remember = NULL;
               }
          }
     }
   e_config_save_queue();
   return 1; /* Apply was OK */
}

/**--GUI--**/
static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for a basic dialog */
   Evas_Object *o, *ob, *of;

   o = e_widget_list_add(evas, 0, 0);

   of = e_widget_framelist_add(evas, _("Generic Locks"), 0);
   ob = e_widget_check_add(evas, _("Prevent this window from moving on its own"), &(cfdata->do_what_i_say));
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_check_add(evas, _("Prevent this window from being changed by me"), &(cfdata->protect_from_me));
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_check_add(evas, _("Prevent this window from being closed"), &(cfdata->important_window));
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_check_add(evas, _("Do not allow the border to change on this window"), &(cfdata->keep_my_border));
   e_widget_framelist_object_append(of, ob);
   e_widget_list_object_append(o, of, 1, 1, 0.5);
   ob = e_widget_check_add(evas, _("Remember the locks for this window"), &(cfdata->remember_locks));
   e_widget_list_object_append(o, ob, 0, 0, 0.0);
   return o;
}

static Evas_Object *
_advanced_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for an advanced dialog */
   Evas_Object *o, *ob, *of, *ol;

   ol = e_widget_list_add(evas, 0, 0);
   o = e_widget_toolbook_add(evas, (48 * e_scale), (48 * e_scale));

   of = e_widget_frametable_add(evas, _("Prevent Changes In:"), 0);
   ob = e_widget_check_add(evas, _("Position"), &(cfdata->lock.client.location));
   e_widget_frametable_object_append(of, ob, 0, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Size"), &(cfdata->lock.client.size));
   e_widget_frametable_object_append(of, ob, 0, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Stacking"), &(cfdata->lock.client.stacking));
   e_widget_frametable_object_append(of, ob, 0, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Iconified state"), &(cfdata->lock.client.iconify));
   e_widget_frametable_object_append(of, ob, 0, 3, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Stickiness"), &(cfdata->lock.client.sticky));
   e_widget_frametable_object_append(of, ob, 1, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Shaded state"), &(cfdata->lock.client.shade));
   e_widget_frametable_object_append(of, ob, 1, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Maximized state"), &(cfdata->lock.client.maximize));
   e_widget_frametable_object_append(of, ob, 1, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Fullscreen state"), &(cfdata->lock.client.fullscreen));
   e_widget_frametable_object_append(of, ob, 1, 3, 1, 1, 1, 1, 1, 1);
   e_widget_toolbook_page_append(o, NULL, _("Program Locks"), of, 1, 1, 1, 1, 0.5, 0.0);

   of = e_widget_frametable_add(evas, _("Prevent Changes In:"), 0);
   ob = e_widget_check_add(evas, _("Position"), &(cfdata->lock.user.location));
   e_widget_frametable_object_append(of, ob, 0, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Size"), &(cfdata->lock.user.size));
   e_widget_frametable_object_append(of, ob, 0, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Stacking"), &(cfdata->lock.user.stacking));
   e_widget_frametable_object_append(of, ob, 0, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Iconified state"), &(cfdata->lock.user.iconify));
   e_widget_frametable_object_append(of, ob, 0, 3, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Stickiness"), &(cfdata->lock.user.sticky));
   e_widget_frametable_object_append(of, ob, 0, 4, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Shaded state"), &(cfdata->lock.user.shade));
   e_widget_frametable_object_append(of, ob, 1, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Maximized state"), &(cfdata->lock.user.maximize));
   e_widget_frametable_object_append(of, ob, 1, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Fullscreen state"), &(cfdata->lock.user.fullscreen));
   e_widget_frametable_object_append(of, ob, 1, 2, 1, 1, 1, 1, 1, 1);
   ob = e_widget_check_add(evas, _("Border style"), &(cfdata->lock.border));
   e_widget_frametable_object_append(of, ob, 1, 3, 1, 1, 1, 1, 1, 1);
   e_widget_toolbook_page_append(o, NULL, _("User Locks"), of, 1, 1, 1, 1, 0.5, 0.0);

   of = e_widget_framelist_add(evas, _("Prevent:"), 0);
   ob = e_widget_check_add(evas, _("Closing the window"), &(cfdata->lock.close));
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_check_add(evas, _("Logging out while this window is open"), &(cfdata->lock.life));
   e_widget_framelist_object_append(of, ob);
   e_widget_toolbook_page_append(o, NULL, _("Behavior Locks"), of, 1, 1, 1, 1, 0.5, 0.0);
   e_widget_toolbook_page_show(o, 0);
   e_widget_list_object_append(ol, o, 1, 1, 0.5);

   ob = e_widget_check_add(evas, _("Remember these Locks"), &(cfdata->lock.remember));
   e_widget_list_object_append(ol, ob, 1, 1, 0.5);

   return ol;
}

#include "e.h"

static void _e_client_cb_border_menu_end(void *data, E_Menu *m);
static void _e_client_menu_cb_locks(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_remember(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_borderless(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_border(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_redirect_set(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED);
static void _e_client_menu_cb_close(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_iconify(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_kill(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_move(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_resize(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize_vertically(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize_horizontally(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize_left(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_maximize_right(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_unmaximize(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_shade(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_resistance(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_icon_edit(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_application_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_window_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi);
static void _e_client_menu_cb_prop(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_stick(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_stacking_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_on_top(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_normal(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_below(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_fullscreen(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_skip_winlist(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_skip_pager(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_skip_taskbar(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_sendto_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_align_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_sendto(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_pin(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_unpin(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_raise(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_lower(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_skip_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_fav_add(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_kbdshrtct_add(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_ibar_add_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_ibar_add(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_border_pre(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_iconpref_e(void *data, E_Menu *m, E_Menu_Item *mi);
#ifndef HAVE_WAYLAND_ONLY
static void _e_client_menu_cb_iconpref_netwm(void *data, E_Menu *m, E_Menu_Item *mi);
#endif
static void _e_client_menu_cb_iconpref_user(void *data, E_Menu *m, E_Menu_Item *mi);
static void _e_client_menu_cb_default_icon(void *data, E_Menu *m, E_Menu_Item *mi);

static Eina_List *menu_hooks = NULL;

E_API E_Client_Menu_Hook *
e_int_client_menu_hook_add(E_Client_Menu_Hook_Cb cb, const void *data)
{
   E_Client_Menu_Hook *h;

   if (!cb) return NULL;
   h = E_NEW(E_Client_Menu_Hook, 1);
   if (!h) return NULL;

   h->cb = cb;
   h->data = (void *)data;
   menu_hooks = eina_list_append(menu_hooks, h);
   return h;
}

E_API void
e_int_client_menu_hook_del(E_Client_Menu_Hook *hook)
{
   E_Client_Menu_Hook *h;
   Eina_List *l;

   if (!hook) return;

   EINA_LIST_FOREACH(menu_hooks, l, h)
     if (h == hook)
       {
          menu_hooks = eina_list_remove_list(menu_hooks, l);
          free(h);
          return;
       }
}

E_API void
e_int_client_menu_hooks_clear(void)
{
   E_Client_Menu_Hook *h;
   EINA_LIST_FREE(menu_hooks, h)
     free(h);
}

E_API void
e_int_client_menu_create(E_Client *ec)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *l;
   E_Client_Menu_Hook *h;
   char buf[128];
   Eina_Bool borderless;

   if (ec->border_menu) return;

   m = e_menu_new();
   e_menu_category_set(m, "border");
   e_menu_category_data_set("border", ec);
   e_object_data_set(E_OBJECT(m), ec);
   ec->border_menu = m;
   e_menu_post_deactivate_callback_set(m, _e_client_cb_border_menu_end, NULL);

   if (!ec->internal)
     {
        if (ec->icccm.class)
          snprintf(buf, sizeof(buf), "%s", ec->icccm.class);
        else
          snprintf(buf, sizeof(buf), _("Application"));
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, buf);
        e_menu_item_submenu_pre_callback_set(mi, _e_client_menu_cb_application_pre, ec);
        if (ec->desktop)
          e_util_desktop_menu_item_icon_add(ec->desktop, 16, mi);
     }
   borderless = e_client_util_borderless(ec);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Window"));
   e_menu_item_submenu_pre_callback_set(mi, _e_client_menu_cb_window_pre, ec);
   e_menu_item_icon_edje_set(mi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/window"),
                             "e/widgets/border/default/window");

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   if ((!ec->sticky) && ((eina_list_count(e_comp->zones) > 1) || (ec->zone->desk_x_count > 1) || (ec->zone->desk_y_count > 1)))
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Move to"));
        e_menu_item_submenu_pre_callback_set(mi, _e_client_menu_cb_sendto_pre, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/sendto"),
                                  "e/widgets/border/default/sendto");
     }

   if ((!ec->lock_user_location) && (!ec->iconic) && (!ec->maximized) && (!ec->fullscreen))
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Align"));
        e_menu_item_submenu_pre_callback_set(mi, _e_client_menu_cb_align_pre, ec);
     }

   if (ec->layer <= E_LAYER_CLIENT_ABOVE)
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Always on Top"));
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, (ec->layer == E_LAYER_CLIENT_ABOVE ? 1 : 0));
        if (ec->layer == E_LAYER_CLIENT_ABOVE)
          e_menu_item_callback_set(mi, _e_client_menu_cb_normal, ec);
        else
          e_menu_item_callback_set(mi, _e_client_menu_cb_on_top, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/stack_on_top"),
                                  "e/widgets/border/default/stack_on_top");
     }

   if (!ec->lock_user_sticky)
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Sticky"));
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, (ec->sticky ? 1 : 0));
        e_menu_item_callback_set(mi, _e_client_menu_cb_stick, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/stick"),
                                  "e/widgets/border/default/stick");
     }

   if ((!ec->lock_user_shade) && (!ec->fullscreen) && (!ec->maximized) &&
       e_comp_object_frame_allowed(ec->frame) &&
       ((!ec->border.name) || (!borderless)))
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Shade"));
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, (ec->shaded ? 1 : 0));
        e_menu_item_callback_set(mi, _e_client_menu_cb_shade, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/shade"),
                                  "e/widgets/border/default/shade");
     }

   if ((!ec->fullscreen) && (!ec->lock_border) && (!ec->shading) &&
       (!ec->shaded) && e_comp_object_frame_allowed(ec->frame))
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Borderless"));
        e_menu_item_check_set(mi, 1);
        e_menu_item_toggle_set(mi, borderless);
        e_menu_item_callback_set(mi, _e_client_menu_cb_borderless, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/borderless"),
                                  "e/widgets/border/default/borderless");
     }

   if (e_comp_config_get()->enable_advanced_features)
     {
        E_Menu *subm;
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Composite"));
        e_util_menu_item_theme_icon_set(mi, "preferences-composite");
        subm = e_menu_new();
        e_menu_item_submenu_set(mi, subm);
        e_object_unref(E_OBJECT(subm));
        e_object_data_set(E_OBJECT(subm), e_comp);
        if (e_pixmap_is_x(ec->pixmap) && (e_comp->nocomp_ec != ec))
          {
             mi = e_menu_item_new(subm);
             e_menu_item_check_set(mi, 1);
             e_menu_item_label_set(mi, _("Unredirected"));
             e_menu_item_toggle_set(mi, !ec->redirected);
             e_menu_item_callback_set(mi, _e_client_menu_cb_redirect_set, ec);
          }
     }

   if (!ec->lock_close)
     {
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);

        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Close"));
        e_menu_item_callback_set(mi, _e_client_menu_cb_close, ec);
        e_menu_item_icon_edje_set(mi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/close"),
                                  "e/widgets/border/default/close");
     }
   EINA_LIST_FOREACH(menu_hooks, l, h)
     h->cb(h->data, ec);
}

E_API void
e_int_client_menu_show(E_Client *ec, Evas_Coord x, Evas_Coord y, int key, unsigned int timestamp)
{
   e_int_client_menu_create(ec);
   if (key)
     e_menu_activate_key(ec->border_menu, ec->zone, x, y, 1, 1,
                         E_MENU_POP_DIRECTION_DOWN);
   else
     e_menu_activate_mouse(ec->border_menu, e_zone_current_get(), x, y, 1, 1,
                           E_MENU_POP_DIRECTION_DOWN, timestamp);
}

E_API void
e_int_client_menu_del(E_Client *ec)
{
   if (!ec->border_menu) return;
   e_menu_post_deactivate_callback_set(ec->border_menu, NULL, NULL);
   E_FREE_FUNC(ec->border_menu, e_object_del);
   if (ec->cur_mouse_action && eina_streq(ec->cur_mouse_action->name, "window_menu"))
     E_FREE_FUNC(ec->cur_mouse_action, e_object_unref);
}

static void
_e_client_cb_border_menu_end(void *data EINA_UNUSED, E_Menu *m)
{
   E_Client *ec;

   ec = e_object_data_get(E_OBJECT(m));
   if (ec)
     {
        /* If the client exists, delete all associated menus */
        e_int_client_menu_del(ec);
     }
   else
     {
        /* Just delete this menu */
        e_object_del(E_OBJECT(m));
     }
}

static void
_e_client_menu_cb_locks(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->border_locks_dialog)
     e_client_activate(e_win_client_get(ec->border_locks_dialog->dia->win), 1);
   else
     e_int_client_locks(ec);
}

static void
_e_client_menu_cb_remember(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->border_remember_dialog)
     e_client_activate(e_win_client_get(ec->border_remember_dialog->dia->win), 1);
   else
     e_int_client_remember(ec);
}

static void
_e_client_menu_cb_border(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;
   char buf[256];

   ec = data;
   if (ec->border_border_dialog)
     e_client_activate(e_win_client_get(ec->border_border_dialog->dia->win), 1);
   else
     {
        snprintf(buf, sizeof(buf), "%p", ec);
        e_configure_registry_call("internal/borders_border", NULL, buf);
     }
}

static void
_e_client_menu_cb_borderless(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec = data;

   EC_CHANGED(ec);
   ec->border.changed = 1;
   ec->borderless = mi->toggle;
}


static void
_e_client_menu_cb_redirect_set(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_comp_client_redirect_toggle(data);
}

static void
_e_client_menu_cb_close(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_close) e_client_act_close_begin(ec);
}

static void
_e_client_menu_cb_iconify(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_iconify)
     {
        if (ec->iconic)
          e_client_uniconify(ec);
        else
          e_client_iconify(ec);
     }
}

static void
_e_client_menu_cb_kill(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *a;
   E_Client *ec;

   ec = data;
   if ((ec->lock_close) || (ec->internal)) return;

   a = e_action_find("window_kill");
   if ((a) && (a->func.go)) a->func.go(E_OBJECT(ec), NULL);
}

static void
_e_client_menu_cb_move(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;

   if (!ec->lock_user_location)
     e_client_act_move_keyboard(ec);
}

static void
_e_client_menu_cb_resize(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;

   if (!ec->lock_user_size)
     e_client_act_resize_keyboard(ec);
}

static void
_e_client_menu_cb_maximize_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   if ((!ec->lock_user_fullscreen) && (!ec->shaded))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Fullscreen"));
        e_menu_item_check_set(submi, 1);
        e_menu_item_toggle_set(submi, ec->fullscreen);
        e_menu_item_callback_set(submi, _e_client_menu_cb_fullscreen, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/fullscreen"),
                                  "e/widgets/border/default/fullscreen");
     }

   if (!ec->fullscreen)
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Maximize"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_BOTH);
        e_menu_item_callback_set(submi, _e_client_menu_cb_maximize, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Maximize Vertically"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_VERTICAL);
        e_menu_item_callback_set(submi, _e_client_menu_cb_maximize_vertically, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Maximize Horizontally"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_HORIZONTAL);
        e_menu_item_callback_set(submi, _e_client_menu_cb_maximize_horizontally, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Maximize Left"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_LEFT);
        e_menu_item_callback_set(submi, _e_client_menu_cb_maximize_left, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Maximize Right"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_RIGHT);
        e_menu_item_callback_set(submi, _e_client_menu_cb_maximize_right, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Unmaximize"));
        e_menu_item_radio_set(submi, 1);
        e_menu_item_radio_group_set(submi, 3);
        e_menu_item_toggle_set(submi, (ec->maximized & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_NONE);
        e_menu_item_callback_set(submi, _e_client_menu_cb_unmaximize, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/maximize"),
                                  "e/widgets/border/default/maximize");
     }
}

static void
_e_client_menu_cb_maximize(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_maximize)
     e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) |
                       E_MAXIMIZE_BOTH);
}

static void
_e_client_menu_cb_maximize_vertically(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_maximize)
     {
        if ((ec->maximized & E_MAXIMIZE_HORIZONTAL))
          e_client_unmaximize(ec, E_MAXIMIZE_HORIZONTAL);
        e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) |
                          E_MAXIMIZE_VERTICAL);
     }
}

static void
_e_client_menu_cb_maximize_horizontally(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_maximize)
     {
        if ((ec->maximized & E_MAXIMIZE_VERTICAL))
          e_client_unmaximize(ec, E_MAXIMIZE_VERTICAL);
        e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) |
                          E_MAXIMIZE_HORIZONTAL);
     }
}

static void
_e_client_menu_cb_maximize_left(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_maximize)
     {
        if ((ec->maximized & E_MAXIMIZE_DIRECTION))
          e_client_unmaximize(ec, ec->maximized & E_MAXIMIZE_DIRECTION);
        e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) |
                          E_MAXIMIZE_LEFT);
     }
}

static void
_e_client_menu_cb_maximize_right(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_maximize)
     {
        if ((ec->maximized & E_MAXIMIZE_DIRECTION))
          e_client_unmaximize(ec, ec->maximized & E_MAXIMIZE_DIRECTION);
        e_client_maximize(ec, (e_config->maximize_policy & E_MAXIMIZE_TYPE) |
                          E_MAXIMIZE_RIGHT);
     }
}

static void
_e_client_menu_cb_unmaximize(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
}

static void
_e_client_menu_cb_shade(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_shade)
     {
        if (ec->shaded)
          e_client_unshade(ec, ec->shade_dir);
        else
          e_client_shade(ec, E_DIRECTION_UP);
     }
}

static void
_e_client_menu_cb_resistance(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   ec->offer_resistance = !ec->offer_resistance;
}

static void
_e_client_menu_cb_icon_edit(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   e_desktop_client_edit(ec);
}
#if 0
static void
_e_client_menu_cb_colors_edit_moveresize(E_Client *ec, ...)
{
   evas_object_geometry_set(ec->color_editor, ec->client.x, ec->client.y, ec->client.w, ec->client.h);
   e_comp_shape_queue();
}

static void
_e_client_menu_cb_colors_edit_del(void *data, ...)
{
   E_Client *ec = data;

   E_FREE_FUNC(ec->color_editor, evas_object_del);
   evas_object_event_callback_del_full(ec->frame, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_e_client_menu_cb_colors_edit_moveresize, ec);
   evas_object_event_callback_del_full(ec->frame, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_e_client_menu_cb_colors_edit_moveresize, ec);
   e_client_comp_hidden_set(ec, 0);
   e_comp_ungrab_input(1, 1);
   e_comp_shape_queue();
}

static void
_e_client_menu_cb_colors_edit(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   Evas_Object *o;
   E_Client *ec = data;

   ec->color_editor = o = elm_color_class_editor_add(e_comp->elm, e_client_util_win_get(data));
   if (!o) return;
   e_comp_shape_queue();
   evas_object_geometry_set(o, ec->client.x, ec->client.y, ec->client.w, ec->client.h);
   evas_object_layer_set(o, E_LAYER_POPUP);
   evas_object_show(o);
   e_client_comp_hidden_set(ec, 1);
   e_comp_grab_input(1, 1);
   evas_object_smart_callback_add(o, "application_closed", (Evas_Smart_Cb)_e_client_menu_cb_colors_edit_del, ec);
   evas_object_smart_callback_add(o, "dismissed", (Evas_Smart_Cb)_e_client_menu_cb_colors_edit_del, ec);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, (Evas_Object_Event_Cb)_e_client_menu_cb_colors_edit_del, ec);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_MOVE, (Evas_Object_Event_Cb)_e_client_menu_cb_colors_edit_moveresize, ec);
   evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_RESIZE, (Evas_Object_Event_Cb)_e_client_menu_cb_colors_edit_moveresize, ec);
}
#endif
static void
_e_client_menu_cb_application_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   if (ec->desktop)
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Edit Icon"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_icon_edit, ec);
        e_util_desktop_menu_item_icon_add(ec->desktop, 16, submi);
     }
   else if (ec->icccm.class)
     {
        /* icons with no class useless to borders */
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Create Icon"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_icon_edit, ec);
     }

   submi = e_menu_item_new(subm);
   e_menu_item_separator_set(submi, 1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Add to Favorites Menu"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_fav_add, ec);
   e_util_menu_item_theme_icon_set(submi, "user-bookmarks");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Add to IBar"));
   e_menu_item_submenu_pre_callback_set(submi,
                                        _e_client_menu_cb_ibar_add_pre, ec);
   e_util_menu_item_theme_icon_set(submi, "preferences-applications-ibar");

   if (e_configure_registry_exists("keyboard_and_mouse/key_bindings"))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Create Keyboard Shortcut"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_kbdshrtct_add, ec);
        e_util_menu_item_theme_icon_set(submi, "preferences-desktop-keyboard");
     }
#if 0
   if (ec->color_editor || (!e_pixmap_is_x(ec->pixmap))) return;
   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Edit Color Scheme"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_colors_edit, ec);
   e_util_menu_item_theme_icon_set(submi, "preferences-desktop-color");
#endif
}

static void
_e_client_menu_cb_window_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;
   Eina_Bool resize = EINA_FALSE;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   /* internal dialog which is resizable */
   if (ec->internal && (ec->netwm.type == E_WINDOW_TYPE_DIALOG))
     resize = (ec->icccm.max_w != ec->icccm.min_w);
   if (resize || (ec->netwm.type == E_WINDOW_TYPE_NORMAL) ||
       (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN))
     {
        if (!(((ec->icccm.min_w == ec->icccm.max_w) &&
               (ec->icccm.min_h == ec->icccm.max_h)) ||
              (ec->lock_user_maximize)))
          {
             if ((!ec->lock_user_maximize) && (!ec->shaded))
               {
                  submi = e_menu_item_new(subm);
                  e_menu_item_label_set(submi, _("Maximize"));
                  e_menu_item_submenu_pre_callback_set(submi, _e_client_menu_cb_maximize_pre, ec);
                  e_menu_item_icon_edje_set(submi,
                                            e_theme_edje_file_get("base/theme/borders",
                                                                  "e/widgets/border/default/maximize"),
                                            "e/widgets/border/default/maximize");
               }
          }

        if ((!ec->lock_user_iconify) && (!ec->fullscreen))
          {
             submi = e_menu_item_new(subm);
             e_menu_item_label_set(submi, _("Iconify"));
             e_menu_item_callback_set(submi, _e_client_menu_cb_iconify, ec);
             e_menu_item_icon_edje_set(submi,
                                       e_theme_edje_file_get("base/theme/borders",
                                                             "e/widgets/border/default/minimize"),
                                       "e/widgets/border/default/minimize");
          }
     }

   if ((!ec->lock_user_location) && (!ec->fullscreen) &&
       (((ec->maximized & E_MAXIMIZE_DIRECTION) != E_MAXIMIZE_BOTH) || e_config->allow_manip))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Move with keyboard"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_move, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/move_icon"),
                                  "e/widgets/border/default/move_icon");
     }

   if (((!ec->lock_user_size) && (!ec->fullscreen) &&
        (((ec->maximized & E_MAXIMIZE_DIRECTION) != E_MAXIMIZE_BOTH) || e_config->allow_manip)) &&
       ((ec->netwm.type == E_WINDOW_TYPE_NORMAL) ||
        (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN)))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Resize with keyboard"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_resize, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/resize_icon"),
                                  "e/widgets/border/default/resize_icon");
     }

   submi = e_menu_item_new(subm);
   e_menu_item_separator_set(submi, 1);

   if ((!ec->lock_user_stacking) && (!ec->fullscreen) && (ec->layer <= E_LAYER_CLIENT_ABOVE))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Stacking"));
        e_menu_item_submenu_pre_callback_set(submi, _e_client_menu_cb_stacking_pre, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/stacking"),
                                  "e/widgets/border/default/stacking");
     }

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Skip"));
   e_menu_item_submenu_pre_callback_set(submi, _e_client_menu_cb_skip_pre, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/skip"),
                             "e/widgets/border/default/skip");

   if ((!ec->lock_border) && (!ec->mwm.borderless))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Border"));
        e_menu_item_submenu_pre_callback_set(submi, _e_client_menu_cb_border_pre, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/borderless"),
                                  "e/widgets/border/default/borderless");
     }

   submi = e_menu_item_new(subm);
   e_menu_item_separator_set(submi, 1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Locks"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_locks, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/locks"),
                             "e/widgets/border/default/locks");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Remember"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_remember, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/remember"),
                             "e/widgets/border/default/remember");

   submi = e_menu_item_new(subm);
   e_menu_item_separator_set(submi, 1);

   if ((!ec->internal) && (!ec->lock_close))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Kill"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_kill, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/kill"),
                                  "e/widgets/border/default/kill");
     }

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("ICCCM/NetWM"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_prop, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/properties"),
                             "e/widgets/border/default/properties");
}

static void
_e_client_menu_cb_prop(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   e_int_client_prop(ec);
}

static void
_e_client_menu_cb_stick(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (!ec->lock_user_sticky)
     {
        if (ec->sticky)
          e_client_unstick(ec);
        else
          e_client_stick(ec);
     }
}

static void
_e_client_menu_cb_on_top(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->layer != E_LAYER_CLIENT_ABOVE)
     evas_object_layer_set(ec->frame, E_LAYER_CLIENT_ABOVE);
}

static void
_e_client_menu_cb_below(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->layer != E_LAYER_CLIENT_BELOW)
     evas_object_layer_set(ec->frame, E_LAYER_CLIENT_BELOW);
}

static void
_e_client_menu_cb_normal(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   if (ec->layer != E_LAYER_CLIENT_NORMAL)
     evas_object_layer_set(ec->frame, E_LAYER_CLIENT_NORMAL);
}

static void
_e_client_menu_cb_fullscreen(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec;
   int toggle;

   if (!(ec = data)) return;

   if (!ec->lock_user_fullscreen)
     {
        toggle = e_menu_item_toggle_get(mi);
        if (toggle)
          e_client_fullscreen(ec, e_config->fullscreen_policy);
        else
          e_client_unfullscreen(ec);
     }
}

static void
_e_client_menu_cb_skip_winlist(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec;

   if (!(ec = data)) return;

   if (((ec->icccm.accepts_focus) || (ec->icccm.take_focus)) &&
       (!ec->netwm.state.skip_taskbar))
     {
        ec->user_skip_winlist = e_menu_item_toggle_get(mi);
        e_client_prop_misc_changed(ec);
     }
   else
     ec->user_skip_winlist = 0;
   EC_CHANGED(ec);
   e_remember_update(ec);
}

static void
_e_client_menu_cb_skip_pager(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec;

   if (!(ec = data)) return;

   if ((ec->icccm.accepts_focus) || (ec->icccm.take_focus))
     {
        ec->netwm.state.skip_pager = e_menu_item_toggle_get(mi);
        e_client_prop_misc_changed(ec);
     }
   else
     ec->netwm.state.skip_pager = 0;
   EC_CHANGED(ec);
   e_remember_update(ec);
}

static void
_e_client_menu_cb_skip_taskbar(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec;

   if (!(ec = data)) return;

   if ((ec->icccm.accepts_focus) || (ec->icccm.take_focus))
     {
        ec->netwm.state.skip_taskbar = e_menu_item_toggle_get(mi);
        e_client_prop_misc_changed(ec);
     }
   else
     ec->netwm.state.skip_taskbar = 0;
   EC_CHANGED(ec);
   e_remember_update(ec);
}

#ifndef DESKMIRROR_TEST
 static void
_e_client_menu_cb_sendto_icon_pre(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Desk *desk = NULL;
   Evas_Object *o = NULL;
   const char *bgfile = NULL;
   int tw = 0, th = 0;

   desk = data;
   E_OBJECT_CHECK(desk);

   tw = 50;
   th = (tw * desk->zone->h) / desk->zone->w;
   bgfile = e_bg_file_get(desk->zone->num,
                          desk->x, desk->y);
   o = e_thumb_icon_add(m->evas);
   e_thumb_icon_file_set(o, bgfile, "e/desktop/background");
   eina_stringshare_del(bgfile);
   e_thumb_icon_size_set(o, tw, th);
   e_thumb_icon_begin(o);
   mi->icon_object = o;
}
#endif

static void
_e_client_menu_cb_align_setup(E_Client *ec, Evas_Object_Event_Cb cb)
{
   E_Notification_Notify n;
   Evas_Object *o;

   memset(&n, 0, sizeof(E_Notification_Notify));
   n.timeout = 3000;
   n.summary = _("Alignment");
   n.body = _("Click an object to align with.");
   n.urgency = E_NOTIFICATION_NOTIFY_URGENCY_NORMAL;
   e_notification_client_send(&n, NULL, NULL);

   o = evas_object_rectangle_add(e_comp->evas);
   evas_object_resize(o, e_comp->w, e_comp->h);
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_layer_set(o, E_LAYER_POPUP);
   evas_object_show(o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, cb, ec);
   e_comp_shape_queue();
}

static Evas_Object *
_e_client_menu_cb_align_cb(Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   evas_object_hide(obj);
   evas_object_del(obj);
   e_comp_shape_queue();
   return evas_object_top_at_xy_get(e, ev->output.x, ev->output.y, 0, 0);
}

static void
_e_client_menu_cb_align_on_center(E_Client *ec, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   Evas_Object *o;

   o = _e_client_menu_cb_align_cb(e, obj, ev);
   e_comp_object_util_center_on(ec->frame, o);
}

static void
_e_client_menu_cb_align_center(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = e_object_data_get(E_OBJECT(m));

   if (data)
     _e_client_menu_cb_align_setup(ec, (Evas_Object_Event_Cb)_e_client_menu_cb_align_on_center);
   else
     e_comp_object_util_center(ec->frame);
}

static void
_e_client_menu_cb_align_on_top(E_Client *ec, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   Evas_Object *o;
   int y;

   o = _e_client_menu_cb_align_cb(e, obj, ev);
   evas_object_geometry_get(o, NULL, &y, NULL, NULL);
   evas_object_move(ec->frame, ec->x, y);
}

static void
_e_client_menu_cb_align_top(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = e_object_data_get(E_OBJECT(m));

   if (data)
     _e_client_menu_cb_align_setup(ec, (Evas_Object_Event_Cb)_e_client_menu_cb_align_on_top);
   else
     {
        int y;

        e_zone_useful_geometry_get(ec->zone, NULL, &y, NULL, NULL);
        evas_object_move(ec->frame, ec->x, y);
     }
}

static void
_e_client_menu_cb_align_on_left(E_Client *ec, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   Evas_Object *o;
   int x;

   o = _e_client_menu_cb_align_cb(e, obj, ev);
   evas_object_geometry_get(o, &x, NULL, NULL, NULL);
   evas_object_move(ec->frame, x, ec->y);
}

static void
_e_client_menu_cb_align_left(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = e_object_data_get(E_OBJECT(m));

   if (data)
     _e_client_menu_cb_align_setup(ec, (Evas_Object_Event_Cb)_e_client_menu_cb_align_on_left);
   else
     {
        int x;

        e_zone_useful_geometry_get(ec->zone, &x, NULL, NULL, NULL);
        evas_object_move(ec->frame, x, ec->y);
     }
}

static void
_e_client_menu_cb_align_on_right(E_Client *ec, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   Evas_Object *o;
   int x, w;

   o = _e_client_menu_cb_align_cb(e, obj, ev);
   evas_object_geometry_get(o, &x, NULL, &w, NULL);
   evas_object_move(ec->frame, x + w - ec->w, ec->y);
}

static void
_e_client_menu_cb_align_right(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = e_object_data_get(E_OBJECT(m));

   if (data)
     _e_client_menu_cb_align_setup(ec, (Evas_Object_Event_Cb)_e_client_menu_cb_align_on_right);
   else
     {
        int x, w;

        e_zone_useful_geometry_get(ec->zone, &x, NULL, &w, NULL);
        evas_object_move(ec->frame, x + w - ec->w, ec->y);
     }
}

static void
_e_client_menu_cb_align_on_bottom(E_Client *ec, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Down *ev)
{
   Evas_Object *o;
   int y, h;

   o = _e_client_menu_cb_align_cb(e, obj, ev);
   evas_object_geometry_get(o, NULL, &y, NULL, &h);
   evas_object_move(ec->frame, ec->x, y + h - ec->h);
}

static void
_e_client_menu_cb_align_bottom(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = e_object_data_get(E_OBJECT(m));

   if (data)
     _e_client_menu_cb_align_setup(ec, (Evas_Object_Event_Cb)_e_client_menu_cb_align_on_bottom);
   else
     {
        int y, h;

        e_zone_useful_geometry_get(ec->zone, NULL, &y, NULL, &h);
        evas_object_move(ec->frame, ec->x, y + h - ec->h);
     }
}

static void
_e_client_menu_cb_align_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm, *mm;
   E_Menu_Item *submi;
   E_Client *ec = data;

   subm = e_menu_new();
   e_menu_title_set(subm, _("Alignment"));
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Center"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_center, NULL);
   mm = e_menu_new();
   e_object_data_set(E_OBJECT(mm), ec);
   e_menu_item_submenu_set(submi, mm);
   e_object_unref(E_OBJECT(mm));
   submi = e_menu_item_new(mm);
   e_menu_item_label_set(submi, _("On window..."));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_center, (void*)1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Top"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_top, NULL);
   mm = e_menu_new();
   e_object_data_set(E_OBJECT(mm), ec);
   e_menu_item_submenu_set(submi, mm);
   e_object_unref(E_OBJECT(mm));
   submi = e_menu_item_new(mm);
   e_menu_item_label_set(submi, _("Of window..."));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_top, (void*)1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Left"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_left, NULL);
   mm = e_menu_new();
   e_object_data_set(E_OBJECT(mm), ec);
   e_menu_item_submenu_set(submi, mm);
   e_object_unref(E_OBJECT(mm));
   submi = e_menu_item_new(mm);
   e_menu_item_label_set(submi, _("Of window..."));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_left, (void*)1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Right"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_right, NULL);
   mm = e_menu_new();
   e_object_data_set(E_OBJECT(mm), ec);
   e_menu_item_submenu_set(submi, mm);
   e_object_unref(E_OBJECT(mm));
   submi = e_menu_item_new(mm);
   e_menu_item_label_set(submi, _("Of window..."));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_right, (void*)1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Bottom"));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_bottom, NULL);
   mm = e_menu_new();
   e_object_data_set(E_OBJECT(mm), ec);
   e_menu_item_submenu_set(submi, mm);
   e_object_unref(E_OBJECT(mm));
   submi = e_menu_item_new(mm);
   e_menu_item_label_set(submi, _("Of window..."));
   e_menu_item_callback_set(submi, _e_client_menu_cb_align_bottom, (void*)1);
}

static void
_e_client_menu_cb_sendto_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;
   E_Zone *zone;
   Eina_List *l = NULL;
   char buf[128];
   int zones, i;

   ec = data;
   zones = eina_list_count(e_comp->zones);

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (zones > 1)
          {
             snprintf(buf, sizeof(buf), _("Screen %d"), zone->num);
             submi = e_menu_item_new(subm);
             e_menu_item_label_set(submi, buf);
             e_menu_item_disabled_set(submi, EINA_TRUE);
          }

        for (i = 0; i < zone->desk_x_count * zone->desk_y_count; i++)
          {
             E_Desk *desk;
#ifdef DESKMIRROR_TEST
             int tw = 50, th;
#endif
             desk = zone->desks[i];
#ifdef DESKMIRROR_TEST
             th = (tw * desk->zone->h) / desk->zone->w;
#endif
             submi = e_menu_item_new(subm);
             e_menu_item_label_set(submi, desk->name);
             e_menu_item_radio_set(submi, 1);
             e_menu_item_radio_group_set(submi, 2);
#ifdef DESKMIRROR_TEST
             e_menu_item_icon_file_set(submi, "sup");
#endif
             if ((ec->zone == zone) && (!ec->iconic) && (ec->desk == desk))
               e_menu_item_toggle_set(submi, 1);
             else
               e_menu_item_callback_set(submi, _e_client_menu_cb_sendto, desk);
#ifdef DESKMIRROR_TEST
             submi->icon_object = e_deskmirror_add(desk, 0, 0);
             evas_object_size_hint_min_set(submi->icon_object, tw, th);
             evas_object_show(submi->icon_object);
#else
             e_menu_item_realize_callback_set(submi, _e_client_menu_cb_sendto_icon_pre,
                                              desk);
#endif
          }
     }
}

static void
_e_client_menu_cb_sendto(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Desk *desk;
   E_Client *ec;

   desk = data;
   ec = e_object_data_get(E_OBJECT(m));
   if ((ec) && (desk) && (ec->desk != desk))
     {
        E_Desk *old_desk = ec->desk;
        Eina_Bool was_focused = e_client_stack_focused_get(ec);

        ec->hidden = 0;
        e_client_desk_set(ec, desk);
        if (was_focused)
          e_desk_last_focused_focus(old_desk);
     }
}

static void
_e_client_menu_cb_pin(void *data EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = e_object_data_get(E_OBJECT(m));
   if (ec) e_client_pinned_set(ec, 1);
}

static void
_e_client_menu_cb_unpin(void *data EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = e_object_data_get(E_OBJECT(m));
   if (ec) e_client_pinned_set(ec, 0);
}

static void
_e_client_menu_cb_stacking_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   /* Only allow to change layer for windows in "normal" layers */
   e_menu_category_set(subm, "border/stacking");
   e_menu_category_data_set("border/stacking", ec);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Always on Top"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->layer == E_LAYER_CLIENT_ABOVE ? 1 : 0));
   e_menu_item_callback_set(submi, _e_client_menu_cb_on_top, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/stack_on_top"),
                             "e/widgets/border/default/stack_on_top");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Normal"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->layer == E_LAYER_CLIENT_NORMAL ? 1 : 0));
   e_menu_item_callback_set(submi, _e_client_menu_cb_normal, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/stack_normal"),
                             "e/widgets/border/default/stack_normal");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Always Below"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->layer == E_LAYER_CLIENT_BELOW ? 1 : 0));
   e_menu_item_callback_set(submi, _e_client_menu_cb_below, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/stack_below"),
                             "e/widgets/border/default/stack_below");

   // Only allow to change layer for windows in "normal" layers
   if ((!ec->lock_user_stacking) &&
       ((ec->layer == 50) || (ec->layer == 100) || (ec->layer == 150)))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_separator_set(submi, 1);

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Raise"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_raise, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/stack_on_top"),
                                  "e/widgets/border/default/stack_on_top");

        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Lower"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_lower, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/stack_below"),
                                  "e/widgets/border/default/stack_below");
     }

   submi = e_menu_item_new(subm);
   e_menu_item_separator_set(submi, 1);

   if ((ec->netwm.type == E_WINDOW_TYPE_NORMAL) ||
       (ec->netwm.type == E_WINDOW_TYPE_UNKNOWN))
     {
        if ((ec->netwm.state.stacking != E_STACKING_BELOW) ||
            (!ec->user_skip_winlist) || (!ec->borderless))
          {
             submi = e_menu_item_new(subm);
             e_menu_item_label_set(submi, _("Pin to Desktop"));
             e_menu_item_callback_set(submi, _e_client_menu_cb_pin, ec);
             e_menu_item_icon_edje_set(submi,
                                       e_theme_edje_file_get("base/theme/borders",
                                                             "e/widgets/border/default/stick"),
                                       "e/widgets/border/default/stick");
          }
        if ((ec->netwm.state.stacking == E_STACKING_BELOW) &&
            (ec->user_skip_winlist) && (ec->borderless))
          {
             submi = e_menu_item_new(subm);
             e_menu_item_label_set(submi, _("Unpin from Desktop"));
             e_menu_item_callback_set(submi, _e_client_menu_cb_unpin, ec);
             e_menu_item_icon_edje_set(submi,
                                       e_theme_edje_file_get("base/theme/borders",
                                                             "e/widgets/border/default/stick"),
                                       "e/widgets/border/default/stick");
          }
     }
}

static void
_e_client_menu_cb_raise(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = data;

   if ((!ec->lock_user_stacking) && (!ec->internal) &&
       ((ec->layer >= E_LAYER_CLIENT_DESKTOP) && (ec->layer <= E_LAYER_CLIENT_NORMAL)))
     {
        evas_object_raise(ec->frame);
     }
}

static void
_e_client_menu_cb_lower(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = data;

   if ((!ec->lock_user_stacking) && (!ec->internal) &&
       ((ec->layer >= E_LAYER_CLIENT_DESKTOP) && (ec->layer <= E_LAYER_CLIENT_NORMAL)))
     {
        evas_object_lower(ec->frame);
     }
}

static void
_e_client_menu_cb_default_icon(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Client *ec;
   Evas_Object *o;
   unsigned char prev_icon_pref;

   ec = data;
   E_OBJECT_CHECK(ec);

   prev_icon_pref = ec->icon_preference;
   ec->icon_preference = E_ICON_PREF_E_DEFAULT;
   o = e_client_icon_add(ec, m->evas);
   ec->icon_preference = prev_icon_pref;
   mi->icon_object = o;
}
#ifndef HAVE_WAYLAND_ONLY
static void
_e_client_menu_cb_netwm_icon(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Client *ec;
   Evas_Object *o;

   ec = data;
   E_OBJECT_CHECK(ec);

   if (ec->netwm.icons)
     {
        o = e_icon_add(m->evas);
        e_icon_data_set(o, ec->netwm.icons[0].data,
                        ec->netwm.icons[0].width,
                        ec->netwm.icons[0].height);
        e_icon_alpha_set(o, 1);
        mi->icon_object = o;
     }
}
#endif
static void
_e_client_menu_cb_border_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *subm;
   E_Menu_Item *submi;
   E_Client *ec;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   if (e_comp_object_frame_allowed(ec->frame) && e_configure_registry_exists("internal/borders_border"))
     {
        submi = e_menu_item_new(subm);
        e_menu_item_label_set(submi, _("Select Border Style"));
        e_menu_item_callback_set(submi, _e_client_menu_cb_border, ec);
        e_menu_item_icon_edje_set(submi,
                                  e_theme_edje_file_get("base/theme/borders",
                                                        "e/widgets/border/default/borderless"),
                                  "e/widgets/border/default/borderless");

        submi = e_menu_item_new(subm);
        e_menu_item_separator_set(submi, 1);
     }

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Use Enlightenment Default Icon Preference"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->icon_preference == E_ICON_PREF_E_DEFAULT ? 1 : 0));
   e_menu_item_realize_callback_set(submi, _e_client_menu_cb_default_icon, ec);
   e_menu_item_callback_set(submi, _e_client_menu_cb_iconpref_e, ec);
#ifndef HAVE_WAYLAND_ONLY
   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Use Application Provided Icon"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->icon_preference == E_ICON_PREF_NETWM ? 1 : 0));
   e_menu_item_realize_callback_set(submi, _e_client_menu_cb_netwm_icon, ec);
   e_menu_item_callback_set(submi, _e_client_menu_cb_iconpref_netwm, ec);
#endif
   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Use User Defined Icon"));
   e_menu_item_radio_set(submi, 1);
   e_menu_item_radio_group_set(submi, 2);
   e_menu_item_toggle_set(submi, (ec->icon_preference == E_ICON_PREF_USER ? 1 : 0));
   e_util_desktop_menu_item_icon_add(ec->desktop, 16, submi);
   e_menu_item_callback_set(submi, _e_client_menu_cb_iconpref_user, ec);
   e_menu_item_separator_set(submi, 1);

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Offer Resistance"));
   e_menu_item_check_set(submi, 1);
   e_menu_item_toggle_set(submi, (ec->offer_resistance ? 1 : 0));
   e_menu_item_callback_set(submi, _e_client_menu_cb_resistance, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/borderless"),
                             "e/widgets/border/default/borderless");
}

static void
_e_client_menu_cb_iconpref_e(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;

   ec->icon_preference = E_ICON_PREF_E_DEFAULT;
   ec->changes.icon = 1;
   EC_CHANGED(ec);
}

static void
_e_client_menu_cb_iconpref_user(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;

   ec->icon_preference = E_ICON_PREF_USER;
   ec->changes.icon = 1;
   EC_CHANGED(ec);
}
#ifndef HAVE_WAYLAND_ONLY
static void
_e_client_menu_cb_iconpref_netwm(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   if (!(ec = data)) return;

   ec->icon_preference = E_ICON_PREF_NETWM;
   ec->changes.icon = 1;
   EC_CHANGED(ec);
}
#endif
static void
_e_client_menu_cb_skip_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Client *ec;
   E_Menu *subm;
   E_Menu_Item *submi;

   if (!(ec = data)) return;

   subm = e_menu_new();
   e_object_data_set(E_OBJECT(subm), ec);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Window List"));
   e_menu_item_check_set(submi, 1);
   e_menu_item_toggle_set(submi, ec->user_skip_winlist);
   e_menu_item_callback_set(submi, _e_client_menu_cb_skip_winlist, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/skip_winlist"),
                             "e/widgets/border/default/skip_winlist");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Pager"));
   e_menu_item_check_set(submi, 1);
   e_menu_item_toggle_set(submi, ec->netwm.state.skip_pager);
   e_menu_item_callback_set(submi, _e_client_menu_cb_skip_pager, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/skip_pager"),
                             "e/widgets/border/default/skip_pager");

   submi = e_menu_item_new(subm);
   e_menu_item_label_set(submi, _("Taskbar"));
   e_menu_item_check_set(submi, 1);
   e_menu_item_toggle_set(submi, ec->netwm.state.skip_taskbar);
   e_menu_item_callback_set(submi, _e_client_menu_cb_skip_taskbar, ec);
   e_menu_item_icon_edje_set(submi,
                             e_theme_edje_file_get("base/theme/borders",
                                                   "e/widgets/border/default/skip_taskbar"),
                             "e/widgets/border/default/skip_taskbar");
}

static void
_e_client_menu_cb_fav_add(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;
   Efreet_Menu *menu;
   char buf[PATH_MAX];

   if (!(ec = data)) return;
   e_user_dir_concat_static(buf, "applications/menu/favorite.menu");
   menu = efreet_menu_parse(buf);
   if (!menu)
     menu = efreet_menu_new("Favorites");
   if (!menu) return;
   efreet_menu_desktop_insert(menu, ec->desktop, -1);
   efreet_menu_save(menu, buf);
   efreet_menu_free(menu);
   e_int_menus_cache_clear();
}

static void
_e_client_menu_cb_kbdshrtct_add(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;
   E_Zone *zone;

   if (!(ec = data)) return;
   zone = e_zone_current_get();
   if (!zone) return;
   e_configure_registry_call("keyboard_and_mouse/key_bindings",
                             NULL, ec->desktop->exec);
}

static void
_e_client_menu_cb_ibar_add_pre(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   E_Menu *sm;
   E_Client *ec;
   Eina_List *dirs;
   Eina_List *l;
   char buf[PATH_MAX], *file;
   size_t len;

   if (!(ec = data)) return;
   len = e_user_dir_concat_static(buf, "applications/bar");
   if (len + 1 >= sizeof(buf)) return;
   dirs = ecore_file_ls(buf);
   if (!dirs) return;

   buf[len] = '/';
   len++;

   sm = e_menu_new();
   EINA_LIST_FOREACH(dirs, l, file)
     {
        E_Menu_Item *smi;

        if (file[0] == '.') continue;

        eina_strlcpy(buf + len, file, sizeof(buf) - len);
        if (ecore_file_is_dir(buf))
          {
             smi = e_menu_item_new(sm);
             e_menu_item_label_set(smi, file);
             e_menu_item_callback_set(smi, _e_client_menu_cb_ibar_add, file);
          }
     }
   e_object_data_set(E_OBJECT(sm), ec);
   e_menu_item_submenu_set(mi, sm);
   e_object_unref(E_OBJECT(sm));
}

static void
_e_client_menu_cb_ibar_add(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Order *od;
   E_Client *ec;
   char buf[PATH_MAX];

   ec = e_object_data_get(E_OBJECT(m));
   if ((!ec) || (!ec->desktop)) return;

   e_user_dir_snprintf(buf, sizeof(buf), "applications/bar/%s/.order",
                       (const char *)data);
   od = e_order_new(buf);
   if (!od) return;
   e_order_append(od, ec->desktop);
   e_object_del(E_OBJECT(od));
}

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

#define MODE_NOTHING        0
#define MODE_GEOMETRY       1
#define MODE_LOCKS          2
#define MODE_GEOMETRY_LOCKS 3
#define MODE_ALL            4

static void         _ec_cb_dialog_del(void *obj);
static void         _ec_cb_dialog_close(void *data, E_Dialog *dia);
static Evas_Object *_ec_icccm_create(E_Dialog *dia, void *data);
static Evas_Object *_ec_netwm_create(E_Dialog *dia, void *data);
static void         _ec_go(void *data, void *data2);
static void         _create_data(E_Dialog *cfd, E_Client *ec);
static void         _free_data(E_Dialog *cfd, E_Config_Dialog_Data *cfdata);

struct _E_Config_Dialog_Data
{
   E_Client *client;
   /*- BASIC -*/
   struct
   {
      char *title;
      char *name;
      char *class;
      char *icon_name;
      char *machine;
      char *role;

      char *min;
      char *max;
      char *base;
      char *step;

      char *aspect;
      char *initial_state;
      char *state;
      char *window_id;
      char *window_group;
      char *transient_for;
      char *client_leader;
      char *gravity;
      char *command;

      int   take_focus;
      int   accepts_focus;
      int   urgent;
      int   delete_request;
      int   request_pos;
   } icccm;

   struct
   {
      char *name;
      char *icon_name;
      int   modal;
      int   sticky;
      int   shaded;
      int   skip_taskbar;
      int   skip_pager;
      int   hidden;
      int   fullscreen;
      char *stacking;
   } netwm;
};

E_API void
e_int_client_prop(E_Client *ec)
{
   E_Dialog *dia;

   if (ec->border_prop_dialog) return;

   dia = e_dialog_new(NULL, "E", "_window_props");
   e_object_del_attach_func_set(E_OBJECT(dia), _ec_cb_dialog_del);

   _create_data(dia, ec);

   _ec_go(dia, (void *)0);

   e_dialog_button_add(dia, _("Close"), NULL, _ec_cb_dialog_close, dia);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   e_dialog_border_icon_set(dia, "preferences-system-windows");
   evas_object_layer_set(e_win_client_get(dia->win)->frame, ec->layer);
}

static void
_create_data(E_Dialog *cfd, E_Client *ec)
{
   E_Config_Dialog_Data *cfdata;
   char buf[4096];

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->client = ec;
   ec->border_prop_dialog = cfd;

#define IFDUP(prop, dest)   \
  if (cfdata->client->prop) \
    cfdata->dest = strdup(cfdata->client->prop)

   IFDUP(icccm.title, icccm.title);
   IFDUP(icccm.name, icccm.name);
   IFDUP(icccm.class, icccm.class);
   IFDUP(icccm.icon_name, icccm.icon_name);
   IFDUP(icccm.machine, icccm.machine);
   IFDUP(icccm.window_role, icccm.role);

   if (cfdata->client->icccm.min_w >= 0)
     {
        snprintf(buf, sizeof(buf), _("%i×%i"),
                 cfdata->client->icccm.min_w,
                 cfdata->client->icccm.min_h);
        cfdata->icccm.min = strdup(buf);
     }
   if (cfdata->client->icccm.max_w >= 0)
     {
        snprintf(buf, sizeof(buf), _("%i×%i"),
                 cfdata->client->icccm.max_w,
                 cfdata->client->icccm.max_h);
        cfdata->icccm.max = strdup(buf);
     }
   if (cfdata->client->icccm.base_w >= 0)
     {
        snprintf(buf, sizeof(buf), _("%i×%i"),
                 cfdata->client->icccm.base_w,
                 cfdata->client->icccm.base_h);
        cfdata->icccm.base = strdup(buf);
     }
   if (cfdata->client->icccm.step_w >= 0)
     {
        snprintf(buf, sizeof(buf), _("%i,%i"),
                 cfdata->client->icccm.step_w,
                 cfdata->client->icccm.step_h);
        cfdata->icccm.step = strdup(buf);
     }
   if ((cfdata->client->icccm.min_aspect > 0.0) &&
       (cfdata->client->icccm.max_aspect > 0.0))
     {
        if (EINA_DBL_EQ(cfdata->client->icccm.min_aspect, cfdata->client->icccm.max_aspect))
          snprintf(buf, sizeof(buf), _("%1.3f"),
                   cfdata->client->icccm.min_aspect);
        else
          snprintf(buf, sizeof(buf), _("%1.3f–%1.3f"),
                   cfdata->client->icccm.min_aspect,
                   cfdata->client->icccm.max_aspect);
        cfdata->icccm.aspect = strdup(buf);
     }
#ifndef HAVE_WAYLAND_ONLY
   if (cfdata->client->icccm.initial_state != ECORE_X_WINDOW_STATE_HINT_NONE)
     {
        switch (cfdata->client->icccm.initial_state)
          {
           case ECORE_X_WINDOW_STATE_HINT_WITHDRAWN:
             snprintf(buf, sizeof(buf), _("Withdrawn"));
             break;

           case ECORE_X_WINDOW_STATE_HINT_NORMAL:
             snprintf(buf, sizeof(buf), _("Normal"));
             break;

           case ECORE_X_WINDOW_STATE_HINT_ICONIC:
             snprintf(buf, sizeof(buf), _("Iconic"));
             break;

           default:
             buf[0] = 0;
             break;
          }
        cfdata->icccm.initial_state = strdup(buf);
     }
   if (cfdata->client->icccm.state != ECORE_X_WINDOW_STATE_HINT_NONE)
     {
        switch (cfdata->client->icccm.state)
          {
           case ECORE_X_WINDOW_STATE_HINT_WITHDRAWN:
             snprintf(buf, sizeof(buf), _("Withdrawn"));
             break;

           case ECORE_X_WINDOW_STATE_HINT_NORMAL:
             snprintf(buf, sizeof(buf), _("Normal"));
             break;

           case ECORE_X_WINDOW_STATE_HINT_ICONIC:
             snprintf(buf, sizeof(buf), _("Iconic"));
             break;

           default:
             buf[0] = 0;
             break;
          }
        cfdata->icccm.state = strdup(buf);
     }
#endif
   snprintf(buf, sizeof(buf), "0x%08x",
            (unsigned int)e_client_util_win_get(cfdata->client));
   cfdata->icccm.window_id = strdup(buf);
   if (cfdata->client->icccm.window_group != 0)
     {
        snprintf(buf, sizeof(buf), "0x%08x",
                 (unsigned int)cfdata->client->icccm.window_group);
        cfdata->icccm.window_group = strdup(buf);
     }
   if (cfdata->client->icccm.transient_for != 0)
     {
        snprintf(buf, sizeof(buf), "0x%08x",
                 (unsigned int)cfdata->client->icccm.transient_for);
        cfdata->icccm.transient_for = strdup(buf);
     }
   if (cfdata->client->icccm.client_leader != 0)
     {
        snprintf(buf, sizeof(buf), "0x%08x",
                 (unsigned int)cfdata->client->icccm.client_leader);
        cfdata->icccm.client_leader = strdup(buf);
     }
#ifndef HAVE_WAYLAND_ONLY
   switch (cfdata->client->icccm.gravity)
     {
      case ECORE_X_GRAVITY_FORGET:
        snprintf(buf, sizeof(buf), _("Forget/Unmap"));
        break;

      case ECORE_X_GRAVITY_NW:
        snprintf(buf, sizeof(buf), _("Northwest"));
        break;

      case ECORE_X_GRAVITY_N:
        snprintf(buf, sizeof(buf), _("North"));
        break;

      case ECORE_X_GRAVITY_NE:
        snprintf(buf, sizeof(buf), _("Northeast"));
        break;

      case ECORE_X_GRAVITY_W:
        snprintf(buf, sizeof(buf), _("West"));
        break;

      case ECORE_X_GRAVITY_CENTER:
        snprintf(buf, sizeof(buf), _("Center"));
        break;

      case ECORE_X_GRAVITY_E:
        snprintf(buf, sizeof(buf), _("East"));
        break;

      case ECORE_X_GRAVITY_SW:
        snprintf(buf, sizeof(buf), _("Southwest"));
        break;

      case ECORE_X_GRAVITY_S:
        snprintf(buf, sizeof(buf), _("South"));
        break;

      case ECORE_X_GRAVITY_SE:
        snprintf(buf, sizeof(buf), _("Southeast"));
        break;

      case ECORE_X_GRAVITY_STATIC:
        snprintf(buf, sizeof(buf), _("Static"));
        break;

      default:
        buf[0] = 0;
        break;
     }
   cfdata->icccm.gravity = strdup(buf);
#endif
   if (cfdata->client->icccm.command.argv)
     {
        int i;

        buf[0] = 0;
        for (i = 0; i < cfdata->client->icccm.command.argc; i++)
          {
             if ((sizeof(buf) - strlen(buf)) <
                 (strlen(cfdata->client->icccm.command.argv[i]) - 2))
               break;
             strcat(buf, cfdata->client->icccm.command.argv[i]);
             strcat(buf, " ");
          }
        cfdata->icccm.command = strdup(buf);
     }

   cfdata->icccm.take_focus = cfdata->client->icccm.take_focus;
   cfdata->icccm.accepts_focus = cfdata->client->icccm.accepts_focus;
   cfdata->icccm.urgent = cfdata->client->icccm.urgent;
   cfdata->icccm.delete_request = cfdata->client->icccm.delete_request;
   cfdata->icccm.request_pos = cfdata->client->icccm.request_pos;

   IFDUP(netwm.name, netwm.name);
   IFDUP(netwm.icon_name, netwm.icon_name);
   cfdata->netwm.modal = cfdata->client->netwm.state.modal;
   cfdata->netwm.sticky = cfdata->client->netwm.state.sticky;
   cfdata->netwm.shaded = cfdata->client->netwm.state.shaded;
   cfdata->netwm.skip_taskbar = cfdata->client->netwm.state.skip_taskbar;
   cfdata->netwm.skip_pager = cfdata->client->netwm.state.skip_pager;
   cfdata->netwm.hidden = cfdata->client->netwm.state.hidden;
   cfdata->netwm.fullscreen = cfdata->client->netwm.state.fullscreen;
   switch (cfdata->client->netwm.state.stacking)
     {
      case 0:
        cfdata->netwm.stacking = strdup(_("None"));
        break;

      case 1:
        cfdata->netwm.stacking = strdup(_("Above"));
        break;

      case 2:
        cfdata->netwm.stacking = strdup(_("Below"));
        break;
     }

   cfd->data = cfdata;
}

static void
_free_data(E_Dialog *cfd, E_Config_Dialog_Data *cfdata)
{
   if (cfdata->client)
     cfdata->client->border_prop_dialog = NULL;

   /* Free the cfdata */
#define IFREE(x) E_FREE(cfdata->x)
   IFREE(icccm.title);
   IFREE(icccm.name);
   IFREE(icccm.class);
   IFREE(icccm.icon_name);
   IFREE(icccm.machine);
   IFREE(icccm.role);
   IFREE(icccm.min);
   IFREE(icccm.max);
   IFREE(icccm.base);
   IFREE(icccm.step);
   IFREE(icccm.aspect);
   IFREE(icccm.initial_state);
   IFREE(icccm.state);
   IFREE(icccm.window_id);
   IFREE(icccm.window_group);
   IFREE(icccm.transient_for);
   IFREE(icccm.client_leader);
   IFREE(icccm.gravity);
   IFREE(icccm.command);

   IFREE(netwm.name);
   IFREE(netwm.icon_name);
   IFREE(netwm.stacking);

   free(cfdata);
   cfd->data = NULL;
}

static void
_ec_cb_dialog_del(void *obj)
{
   E_Dialog *dia;

   dia = obj;
   if (dia->data)
     _free_data(dia, dia->data);
}

static void
_ec_cb_dialog_close(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (dia->data)
     _free_data(dia, dia->data);
   e_object_del(E_OBJECT(dia));
}

static void
_ec_go(void *data, void *data2)
{
   E_Dialog *dia;
   Evas_Object *c, *o, *ob;
   Evas_Coord w, h;

   dia = data;
   if (!dia) return;

   if (dia->content_object)
     evas_object_del(dia->content_object);

   c = e_widget_list_add(evas_object_evas_get(dia->win), 0, 0);

   if (!data2)
     {
        o = _ec_icccm_create(dia, NULL);
        e_dialog_title_set(dia, _("ICCCM Properties"));
        e_widget_list_object_append(c, o, 1, 1, 0.0);
        ob = e_widget_button_add(evas_object_evas_get(dia->win), _("NetWM"), "go-next",
                                 _ec_go, dia, (void *)1);
     }
   else
     {
        o = _ec_netwm_create(dia, NULL);
        e_dialog_title_set(dia, _("NetWM Properties"));
        e_widget_list_object_append(c, o, 1, 1, 0.0);
        ob = e_widget_button_add(evas_object_evas_get(dia->win), _("ICCCM"), "go-next",
                                 _ec_go, dia, (void *)0);
     }

   e_widget_list_object_append(c, ob, 0, 0, 1.0);

   e_widget_size_min_get(c, &w, &h);
   e_dialog_content_set(dia, c, w, h);
   e_dialog_show(dia);
}

#define STR_ENTRY(label, x, y, val)                                    \
  {                                                                    \
     Evas_Coord mw, mh;                                                \
     ob = e_widget_label_add(evas, label);                             \
     if (!cfdata->val) e_widget_disabled_set(ob, 1);                   \
     e_widget_table_object_append(o, ob, x, y, 1, 1, 1, 1, 0, 1);      \
     ob = e_widget_entry_add(dia->win, & (cfdata->val), NULL, NULL, NULL); \
     e_widget_entry_readonly_set(ob, 1);                               \
     e_widget_size_min_get(ob, &mw, &mh);                              \
     e_widget_size_min_set(ob, 160, mh);                               \
     e_widget_table_object_append(o, ob, x + 1, y, 1, 1, 1, 0, 1, 0);  \
  }
#define CHK_ENTRY(label, x, y, val)                                   \
  {                                                                   \
     ob = e_widget_label_add(evas, label);                            \
     e_widget_table_object_append(o, ob, x, y, 1, 1, 1, 1, 0, 1);     \
     ob = e_widget_check_add(evas, "", & (cfdata->val));              \
     e_widget_disabled_set(ob, 1);                                    \
     e_widget_table_object_append(o, ob, x + 1, y, 1, 1, 1, 0, 1, 0); \
  }

static Evas_Object *
_ec_icccm_create(E_Dialog *dia, void *data EINA_UNUSED)
{
   Evas *evas;
   Evas_Object *o, *ob, *otb;
   E_Config_Dialog_Data *cfdata;

   if (!dia) return NULL;
   cfdata = dia->data;

   if (dia->content_object)
     evas_object_del(dia->content_object);

   evas = evas_object_evas_get(dia->win);
   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   STR_ENTRY(_("Title"), 0, 0, icccm.title);
   STR_ENTRY(_("Name"), 0, 1, icccm.name);
   STR_ENTRY(_("Class"), 0, 2, icccm.class);
   STR_ENTRY(_("Icon Name"), 0, 3, icccm.icon_name);
   STR_ENTRY(_("Machine"), 0, 4, icccm.machine);
   STR_ENTRY(_("Role"), 0, 5, icccm.role);
   e_widget_toolbook_page_append(otb, NULL, _("General"), o, 1, 1, 1, 1, 0.5, 0.0);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   STR_ENTRY(_("Minimum Size"), 0, 6, icccm.min);
   STR_ENTRY(_("Maximum Size"), 0, 7, icccm.max);
   STR_ENTRY(_("Base Size"), 0, 8, icccm.base);
   STR_ENTRY(_("Resize Steps"), 0, 9, icccm.step);
   e_widget_toolbook_page_append(otb, NULL, _("Sizing"), o, 1, 1, 1, 1, 0.5, 0.0);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   STR_ENTRY(_("Aspect Ratio"), 2, 0, icccm.aspect);
   STR_ENTRY(_("Initial State"), 2, 1, icccm.initial_state);
   STR_ENTRY(_("State"), 2, 2, icccm.state);
   STR_ENTRY(_("Window ID"), 2, 3, icccm.window_id);
   STR_ENTRY(_("Window Group"), 2, 4, icccm.window_group);
   STR_ENTRY(_("Transient For"), 2, 5, icccm.transient_for);
   STR_ENTRY(_("Client Leader"), 2, 6, icccm.client_leader);
   STR_ENTRY(_("Gravity"), 2, 7, icccm.gravity);
   STR_ENTRY(_("Command"), 2, 8, icccm.command);
   e_widget_toolbook_page_append(otb, NULL, _("States"), o, 1, 1, 1, 1, 0.5, 0.0);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   CHK_ENTRY(_("Take Focus"), 0, 11, icccm.take_focus);
   CHK_ENTRY(_("Accepts Focus"), 0, 12, icccm.accepts_focus);
   CHK_ENTRY(_("Urgent"), 0, 13, icccm.urgent);
   CHK_ENTRY(_("Request Delete"), 2, 11, icccm.delete_request);
   CHK_ENTRY(_("Request Position"), 2, 12, icccm.request_pos);
   e_widget_toolbook_page_append(otb, NULL, _("Settings"), o, 1, 1, 1, 1, 0.5, 0.0);
   e_widget_toolbook_page_show(otb, 0);

   return otb;
}

static Evas_Object *
_ec_netwm_create(E_Dialog *dia, void *data EINA_UNUSED)
{
   Evas *evas;
   Evas_Object *o, *ob, *otb;
   E_Config_Dialog_Data *cfdata;

   if (!dia) return NULL;
   cfdata = dia->data;

   if (dia->content_object)
     evas_object_del(dia->content_object);

   evas = evas_object_evas_get(dia->win);
   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);
   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   STR_ENTRY(_("Name"), 0, 1, netwm.name);
   STR_ENTRY(_("Icon Name"), 0, 2, netwm.icon_name);
   STR_ENTRY(_("Stacking"), 0, 3, netwm.stacking);
   e_widget_toolbook_page_append(otb, NULL, _("General"), o, 1, 1, 1, 1, 0.5, 0.0);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   CHK_ENTRY(_("Modal"), 0, 4, netwm.modal);
   CHK_ENTRY(_("Sticky"), 0, 5, netwm.sticky);
   CHK_ENTRY(_("Shaded"), 0, 6, netwm.shaded);
   CHK_ENTRY(_("Skip Taskbar"), 0, 7, netwm.skip_taskbar);
   CHK_ENTRY(_("Skip Pager"), 0, 8, netwm.skip_pager);
   CHK_ENTRY(_("Hidden"), 0, 9, netwm.hidden);
   CHK_ENTRY(_("Fullscreen"), 0, 10, netwm.fullscreen);
   e_widget_toolbook_page_append(otb, NULL, _("Settings"), o, 1, 1, 1, 1, 0.5, 0.0);
   e_widget_toolbook_page_show(otb, 0);

   return otb;
}

#include "e.h"

/* PROTOTYPES - same all the time */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _basic_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _advanced_apply_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Evas_Object *_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);

/* Actual config data we will be playing with whil the dialog is active */
#define MODE_NOTHING        0
#define MODE_GEOMETRY       E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE
#define MODE_LOCKS          E_REMEMBER_APPLY_LOCKS
#define MODE_VOLUME         E_REMEMBER_APPLY_VOLUME
#define MODE_GEOMETRY_LOCKS E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE | E_REMEMBER_APPLY_LOCKS
#define MODE_ALL            E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE | E_REMEMBER_APPLY_LAYER | \
  E_REMEMBER_APPLY_LOCKS | E_REMEMBER_APPLY_BORDER | E_REMEMBER_APPLY_STICKY |                      \
  E_REMEMBER_APPLY_DESKTOP | E_REMEMBER_APPLY_SHADE | E_REMEMBER_APPLY_ZONE |                       \
  E_REMEMBER_APPLY_SKIP_WINLIST | E_REMEMBER_APPLY_SKIP_PAGER |                                     \
  E_REMEMBER_APPLY_SKIP_TASKBAR | E_REMEMBER_APPLY_FULLSCREEN | E_REMEMBER_APPLY_ICON_PREF |        \
  E_REMEMBER_APPLY_OFFER_RESISTANCE | E_REMEMBER_APPLY_OPACITY
struct _E_Config_Dialog_Data
{
   E_Client *client;
   E_Remember *rem;
   /*- BASIC -*/
   int       mode;
   int       warned;
   /*- ADVANCED -*/
   char     *name;
   char     *class;
   char     *title;
   char     *role;
   char     *command;
   char     *desktop;
   struct
   {
      int match_name;
      int match_class;
      int match_title;
      int match_role;
      int match_type;
      int match_transient;
      int apply_first_only;
      int apply_pos;
      int apply_size;
      int apply_layer;
      int apply_locks;
      int apply_border;
      int apply_sticky;
      int apply_desktop;
      int apply_shade;
      int apply_fullscreen;
      int apply_zone;
      int apply_skip_winlist;
      int apply_skip_pager;
      int apply_skip_taskbar;
      int apply_run;
      int apply_icon_pref;
      int apply_desktop_file;
      int set_focus_on_start;
      int keep_settings;
      int offer_resistance;
      int apply_opacity;
      int apply_volume;
   } remember;

   int applied;
};

/* a nice easy setup function that does the dirty work */
E_API E_Config_Dialog *
e_int_client_remember_edit(E_Remember *rem)
{
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);
   /* methods */
   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _advanced_apply_data;
   v->basic.create_widgets = _advanced_create_widgets;
   v->override_auto_apply = 1;

   /* create config dialog for ec object/data */
   return e_config_dialog_new(NULL, _("Window Remember"),
                             "E", "_border_remember_edit_dialog",
                             NULL, 0, v, rem);
}

E_API void
e_int_client_remember(E_Client *ec)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);
   if (v)
     {
        /* methods */
        v->create_cfdata = _create_data;
        v->free_cfdata = _free_data;
        v->basic.apply_cfdata = _basic_apply_data;
        v->basic.create_widgets = _basic_create_widgets;
        v->advanced.apply_cfdata = _advanced_apply_data;
        v->advanced.create_widgets = _advanced_create_widgets;
        v->override_auto_apply = 1;

        /* create config dialog for ec object/data */
        cfd = e_config_dialog_new(NULL,
                                  _("Window Remember"),
                                  "E", "_border_remember_dialog",
                                  NULL, 0, v, ec);
        ec->border_remember_dialog = cfd;
        evas_object_layer_set(e_win_client_get(cfd->dia->win)->frame, ec->layer);
     }
}

/**--CREATE--**/
static void
_clear_data(E_Config_Dialog_Data *cfdata)
{
   E_FREE(cfdata->name);
   E_FREE(cfdata->class);
   E_FREE(cfdata->title);
   E_FREE(cfdata->role);
   E_FREE(cfdata->command);
   E_FREE(cfdata->desktop);
   cfdata->mode = 0;
   memset(&cfdata->remember, 0, sizeof(cfdata->remember));
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   E_Client *ec;
   E_Remember *rem;

   ec = cfdata->client;
   if (ec)
     rem = ec->remember;
   else
     rem = cfdata->rem;

   if (rem)
     {
        if (rem->name && rem->name[0])
          cfdata->name = strdup(rem->name);
        if (rem->class && rem->class[0])
          cfdata->class = strdup(rem->class);
        if (rem->title && rem->title[0])
          cfdata->title = strdup(rem->title);
        if (rem->role && rem->role[0])
          cfdata->role = strdup(rem->role);
        if (rem->prop.command && rem->prop.command[0])
          cfdata->command = strdup(rem->prop.command);
        if (rem->prop.desktop_file && rem->prop.desktop_file[0])
          cfdata->desktop = strdup(rem->prop.desktop_file);
        if (cfdata->desktop)
          cfdata->remember.apply_desktop_file = 1;
     }

   if (ec)
     {
        if (!cfdata->name &&
            ec->icccm.name &&
            ec->icccm.name[0])
          cfdata->name = strdup(ec->icccm.name);
        if (!cfdata->class &&
            ec->icccm.class &&
            ec->icccm.class[0])
          cfdata->class = strdup(ec->icccm.class);
        if (!cfdata->role &&
            ec->icccm.window_role &&
            ec->icccm.window_role[0])
          cfdata->role = strdup(ec->icccm.window_role);
        if (!cfdata->title)
          {
             const char *title = e_client_util_name_get(ec);
             if (title && title[0])
               cfdata->title = strdup(title);
          }
        if (!cfdata->desktop && ec->desktop)
          cfdata->desktop = strdup(ec->desktop->name);

        if (!cfdata->command &&
            (ec->icccm.command.argc > 0) &&
            (ec->icccm.command.argv))
          {
             char buf[4096];
             int i, j, k;

             buf[0] = 0;
             k = 0;
             for (i = 0; i < ec->icccm.command.argc; i++)
               {
                  if (i > 0)
                    {
                       buf[k] = ' ';
                       k++;
                    }
                  for (j = 0; ec->icccm.command.argv[i][j]; j++)
                    {
                       if (k >= (int)(sizeof(buf) - 10))
                         {
                            buf[k] = 0;
                            goto done;
                         }
                       if ((ec->icccm.command.argv[i][j] == ' ') ||
                           (ec->icccm.command.argv[i][j] == '\t') ||
                           (ec->icccm.command.argv[i][j] == '\\') ||
                           (ec->icccm.command.argv[i][j] == '\"') ||
                           (ec->icccm.command.argv[i][j] == '\'') ||
                           (ec->icccm.command.argv[i][j] == '$') ||
                           (ec->icccm.command.argv[i][j] == '%'))
                         {
                            buf[k] = '\\';
                            k++;
                         }
                       buf[k] = ec->icccm.command.argv[i][j];
                       k++;
                    }
               }
             buf[k] = 0;
     done:
             cfdata->command = strdup(buf);
          }
     }

   if (rem)
     {
        if (rem->apply_first_only) cfdata->remember.apply_first_only = 1;
        if (rem->keep_settings) cfdata->remember.keep_settings = 1;

        if (rem->match & E_REMEMBER_MATCH_NAME)
          cfdata->remember.match_name = 1;
        if (rem->match & E_REMEMBER_MATCH_CLASS)
          cfdata->remember.match_class = 1;
        if (rem->match & E_REMEMBER_MATCH_TITLE)
          cfdata->remember.match_title = 1;
        if (rem->match & E_REMEMBER_MATCH_ROLE)
          cfdata->remember.match_role = 1;
        if (rem->match & E_REMEMBER_MATCH_TYPE)
          cfdata->remember.match_type = 1;
        if (rem->match & E_REMEMBER_MATCH_TRANSIENT)
          cfdata->remember.match_transient = 1;
        if (rem->apply & E_REMEMBER_APPLY_POS)
          cfdata->remember.apply_pos = 1;
        if (rem->apply & E_REMEMBER_APPLY_SIZE)
          cfdata->remember.apply_size = 1;
        if (rem->apply & E_REMEMBER_APPLY_LAYER)
          cfdata->remember.apply_layer = 1;
        if (rem->apply & E_REMEMBER_APPLY_LOCKS)
          cfdata->remember.apply_locks = 1;
        if (rem->apply & E_REMEMBER_APPLY_BORDER)
          cfdata->remember.apply_border = 1;
        if (rem->apply & E_REMEMBER_APPLY_STICKY)
          cfdata->remember.apply_sticky = 1;
        if (rem->apply & E_REMEMBER_APPLY_DESKTOP)
          cfdata->remember.apply_desktop = 1;
        if (rem->apply & E_REMEMBER_APPLY_SHADE)
          cfdata->remember.apply_shade = 1;
        if (rem->apply & E_REMEMBER_APPLY_FULLSCREEN)
          cfdata->remember.apply_fullscreen = 1;
        if (rem->apply & E_REMEMBER_APPLY_ZONE)
          cfdata->remember.apply_zone = 1;
        if (rem->apply & E_REMEMBER_APPLY_SKIP_WINLIST)
          cfdata->remember.apply_skip_winlist = 1;
        if (rem->apply & E_REMEMBER_APPLY_SKIP_PAGER)
          cfdata->remember.apply_skip_pager = 1;
        if (rem->apply & E_REMEMBER_APPLY_SKIP_TASKBAR)
          cfdata->remember.apply_skip_taskbar = 1;
        if (rem->apply & E_REMEMBER_APPLY_RUN)
          cfdata->remember.apply_run = 1;
        if (rem->apply & E_REMEMBER_APPLY_ICON_PREF)
          cfdata->remember.apply_icon_pref = 1;
        if (rem->apply & E_REMEMBER_SET_FOCUS_ON_START)
          cfdata->remember.set_focus_on_start = 1;
        if (rem->apply & E_REMEMBER_APPLY_OFFER_RESISTANCE)
          cfdata->remember.offer_resistance = 1;
        if (rem->apply & E_REMEMBER_APPLY_OPACITY)
          cfdata->remember.apply_opacity = 1;
        if (rem->apply & E_REMEMBER_APPLY_VOLUME)
          cfdata->remember.apply_volume = 1;
     }

   if (!rem) cfdata->mode = MODE_NOTHING;
   else cfdata->mode = rem->apply;
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   /* Create cfdata - cfdata is a temporary block of config data that this
    * dialog will be dealing with while configuring. it will be applied to
    * the running systems/config in the apply methods
    */
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   if (!strcmp(cfd->class, "_border_remember_dialog"))
     cfdata->client = cfd->data;
   else
     cfdata->rem = cfd->data;
   cfdata->applied = 1;
   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   /* Free the cfdata */
   free(cfdata->name);
   free(cfdata->class);
   free(cfdata->title);
   free(cfdata->role);
   free(cfdata->command);
   free(cfdata->desktop);

   if (cfdata->client)
     {
        if (!cfdata->applied && cfdata->client->remember)
          {
             e_remember_unuse(cfdata->client->remember);
             e_remember_del(cfdata->client->remember);
             e_config_save_queue();
          }

        cfdata->client->border_remember_dialog = NULL;
     }
   free(cfdata);
}

static void
_warning_dialog_show(void)
{
   E_Dialog *dia;

   dia = e_dialog_new(NULL, "E", "_border_remember_error_multi_dialog");
   e_dialog_title_set(dia, _("Window properties are not a unique match"));
   e_dialog_text_set
     (dia,
     _("You are trying to ask Enlightenment to remember to apply<ps/>"
       "properties (such as size, location, border style etc.) to<ps/>"
       "a window that <hilight>does not have unique properties</hilight>.<ps/>"
       "<ps/>"
       "This means it shares Name/Class, Transience, Role etc. properties<ps/>"
       "with more than 1 other window on the screen and remembering<ps/>"
       "properties for this window will apply to all other windows<ps/>"
       "that match these properties.<ps/>"
       "<ps/>"
       "This is just a warning in case you did not intend this to happen.<ps/>"
       "If you did, simply press <hilight>Apply</hilight> or <hilight>OK</hilight> buttons<ps/>"
       "and your settings will be accepted. Press <hilight>Cancel</hilight> if you<ps/>"
       "are not sure and nothing will be affected.")
     );
   e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
}

static int
_check_matches(E_Remember *rem, int update)
{
   const Eina_List *l;
   E_Client *ec;
   const char *title;
   int n = 0;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        int match = rem->match;
        title = e_client_util_name_get(ec);

        if ((match & E_REMEMBER_MATCH_NAME) &&
            (e_util_glob_match(ec->icccm.name, rem->name)))
          match &= ~E_REMEMBER_MATCH_NAME;

        if ((match & E_REMEMBER_MATCH_CLASS) &&
            (e_util_glob_match(ec->icccm.class, rem->class)))
          match &= ~E_REMEMBER_MATCH_CLASS;

        if ((match & E_REMEMBER_MATCH_TITLE) &&
            (e_util_glob_match(title, rem->title)))
          match &= ~E_REMEMBER_MATCH_TITLE;

        if ((match & E_REMEMBER_MATCH_ROLE) &&
            ((!e_util_strcmp(rem->role, ec->icccm.window_role)) ||
             (e_util_both_str_empty(rem->role, ec->icccm.window_role))))
          match &= ~E_REMEMBER_MATCH_ROLE;

        if ((match & E_REMEMBER_MATCH_TYPE) &&
            (rem->type == (int)ec->netwm.type))
          match &= ~E_REMEMBER_MATCH_TYPE;

        if ((match & E_REMEMBER_MATCH_TRANSIENT) &&
            ((rem->transient && ec->icccm.transient_for != 0) ||
             (!rem->transient && (ec->icccm.transient_for == 0))))
          match &= ~E_REMEMBER_MATCH_TRANSIENT;

        if (match == 0) n++;

        if (update)
          {
             ec->changed = 1;
             ec->changes.icon = 1;
          }
        else if (n > 1)
          break;
     }
   return n;
}

static int
_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   /* Actually take our cfdata settings and apply them in real life */
   E_Client *ec = cfdata->client;
   E_Remember *rem = ec->remember;

   if (cfdata->mode == MODE_NOTHING)
     {
        if (rem)
          {
             e_remember_unuse(rem);
             e_remember_del(rem);
          }
        e_config_save_queue();
        return 1;
     }

   if (!rem)
     {
        rem = e_remember_new();
        if (rem)
          {
             ec->remember = rem;
             cfdata->applied = 0;
          }
        else
          return 0;
     }

   e_remember_default_match_set(rem, cfdata->client);

   if ((!cfdata->warned) && (_check_matches(rem, 0) > 1))
     {
        _warning_dialog_show();
        cfdata->warned = 1;
        return 0;
     }

   rem->apply = cfdata->mode;
   rem->apply_first_only = 0;

   e_remember_use(rem);
   e_remember_update(ec);
   cfdata->applied = 1;
   e_config_save_queue();
   return 1; /* Apply was OK */
}

static int
_advanced_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   E_Client *ec = cfdata->client;
   E_Remember *rem;

   if (ec) rem = ec->remember;
   else rem = cfdata->rem;

   if (!rem)
     {
        cfdata->applied = 0;
        rem = e_remember_new();
        if (rem)
          e_remember_use(rem);
        else
          return 0;
     }
   else
     {
        if (rem->name) eina_stringshare_del(rem->name);
        if (rem->class) eina_stringshare_del(rem->class);
        if (rem->title) eina_stringshare_del(rem->title);
        if (rem->role) eina_stringshare_del(rem->role);
        if (rem->prop.command) eina_stringshare_del(rem->prop.command);
        if (rem->prop.desktop_file) eina_stringshare_del(rem->prop.desktop_file);
        rem->name = NULL;
        rem->class = NULL;
        rem->title = NULL;
        rem->role = NULL;
        rem->prop.command = NULL;
        rem->prop.desktop_file = NULL;
     }

   rem->match = 0;
   rem->apply_first_only = cfdata->remember.apply_first_only;

   if (cfdata->remember.match_name && cfdata->name && cfdata->name[0])
     {
        rem->match |= E_REMEMBER_MATCH_NAME;
        rem->name = eina_stringshare_add(cfdata->name);
     }
   if (cfdata->remember.match_class && cfdata->class && cfdata->class[0])
     {
        rem->match |= E_REMEMBER_MATCH_CLASS;
        rem->class = eina_stringshare_add(cfdata->class);
     }
   if (cfdata->remember.match_title && cfdata->title && cfdata->title[0])
     {
        rem->match |= E_REMEMBER_MATCH_TITLE;
        rem->title = eina_stringshare_add(cfdata->title);
     }
   if (cfdata->remember.match_role && cfdata->role && cfdata->role[0])
     {
        rem->match |= E_REMEMBER_MATCH_ROLE;
        rem->role = eina_stringshare_add(cfdata->role);
     }
   if (ec)
     {
        if (cfdata->remember.match_type)
          {
             rem->match |= E_REMEMBER_MATCH_TYPE;
             rem->type = ec->netwm.type;
          }

        if (cfdata->remember.match_transient)
          {
             rem->match |= E_REMEMBER_MATCH_TRANSIENT;
             if (ec->icccm.transient_for != 0)
               rem->transient = 1;
             else
               rem->transient = 0;
          }
     }

   if (!rem->match)
     {
        E_Dialog *dia;

        dia = e_dialog_new(NULL, "E", "_border_remember_error_noprop_dialog");
        e_dialog_title_set(dia, _("No match properties set"));
        e_dialog_text_set
          (dia,
          _("You are trying to ask Enlightenment to remember to apply<ps/>"
            "properties (such as size, location, border style etc.) to<ps/>"
            "a window <hilight>without specifying how to remember it</hilight>.<ps/>"
            "<ps/>"
            "You must specify at least 1 way of remembering this window.")
          );
        e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
        elm_win_center(dia->win, 1, 1);
        e_dialog_show(dia);
        if (ec)
          cfdata->client->remember = rem;
        return 0;
     }

   /* TODO warn when match doesn't match the current window
    * (when using globs) */

   if (!cfdata->warned)
     {
        if ((!cfdata->remember.apply_first_only) &&
            (_check_matches(rem, 0) > 1))
          {
             _warning_dialog_show();
             cfdata->warned = 1;
             return 0;
          }
     }

   if (cfdata->command && cfdata->command[0])
     rem->prop.command = eina_stringshare_add(cfdata->command);
   if (cfdata->remember.apply_desktop_file && cfdata->desktop)
     rem->prop.desktop_file = eina_stringshare_add(cfdata->desktop);

   rem->apply = 0;
   if (cfdata->remember.apply_pos)
     rem->apply |= E_REMEMBER_APPLY_POS;
   if (cfdata->remember.apply_size)
     rem->apply |= E_REMEMBER_APPLY_SIZE;
   if (cfdata->remember.apply_layer)
     rem->apply |= E_REMEMBER_APPLY_LAYER;
   if (cfdata->remember.apply_locks)
     rem->apply |= E_REMEMBER_APPLY_LOCKS;
   if (cfdata->remember.apply_border)
     rem->apply |= E_REMEMBER_APPLY_BORDER;
   if (cfdata->remember.apply_sticky)
     rem->apply |= E_REMEMBER_APPLY_STICKY;
   if (cfdata->remember.apply_desktop)
     rem->apply |= E_REMEMBER_APPLY_DESKTOP;
   if (cfdata->remember.apply_shade)
     rem->apply |= E_REMEMBER_APPLY_SHADE;
   if (cfdata->remember.apply_fullscreen)
     rem->apply |= E_REMEMBER_APPLY_FULLSCREEN;
   if (cfdata->remember.apply_zone)
     rem->apply |= E_REMEMBER_APPLY_ZONE;
   if (cfdata->remember.apply_skip_winlist)
     rem->apply |= E_REMEMBER_APPLY_SKIP_WINLIST;
   if (cfdata->remember.apply_skip_pager)
     rem->apply |= E_REMEMBER_APPLY_SKIP_PAGER;
   if (cfdata->remember.apply_skip_taskbar)
     rem->apply |= E_REMEMBER_APPLY_SKIP_TASKBAR;
   if (cfdata->remember.apply_run)
     rem->apply |= E_REMEMBER_APPLY_RUN;
   if (cfdata->remember.apply_icon_pref)
     rem->apply |= E_REMEMBER_APPLY_ICON_PREF;
   if (cfdata->remember.set_focus_on_start)
     rem->apply |= E_REMEMBER_SET_FOCUS_ON_START;
   if (cfdata->remember.offer_resistance)
     rem->apply |= E_REMEMBER_APPLY_OFFER_RESISTANCE;
   if (cfdata->remember.apply_opacity)
     rem->apply |= E_REMEMBER_APPLY_OPACITY;
   if (cfdata->remember.apply_volume)
     rem->apply |= E_REMEMBER_APPLY_VOLUME;

   if (ec && (!rem->apply && !rem->prop.desktop_file))
     {
        e_remember_unuse(rem);
        e_remember_del(rem);
        if (cfdata->client->remember)
          e_config_save_queue();
        return 1;
     }

   if (ec)
     {
        _check_matches(rem, 1);
        rem->keep_settings = 0;
        cfdata->client->remember = rem;
        e_remember_update(cfdata->client);
        cfdata->applied = 1;
     }
   rem->keep_settings = cfdata->remember.keep_settings;

   e_config_save_queue();
   return 1; /* Apply was OK */
}

/**--GUI--**/
static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for a basic dialog */
   Evas_Object *o, *ob;
   E_Radio_Group *rg;

   _clear_data(cfdata);
   _fill_data(cfdata);
   o = e_widget_list_add(evas, 0, 0);
   rg = e_widget_radio_group_new(&(cfdata->mode));
   ob = e_widget_radio_add(evas, _("Nothing"), MODE_NOTHING, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   ob = e_widget_radio_add(evas, _("Size and Position"), MODE_GEOMETRY, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   ob = e_widget_radio_add(evas, _("Locks"), MODE_LOCKS, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   ob = e_widget_radio_add(evas, _("Size, Position and Locks"), MODE_GEOMETRY_LOCKS, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   ob = e_widget_radio_add(evas, _("Volume"), MODE_VOLUME, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   ob = e_widget_radio_add(evas, _("All"), MODE_ALL, rg);
   e_widget_list_object_append(o, ob, 1, 1, 0.5);
   return o;
}

static Evas_Object *
_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   /* generate the core widget layout for an advanced dialog */
   Evas_Object *o, *ob, *of, *oc;

   _clear_data(cfdata);
   _fill_data(cfdata);
   o = e_widget_toolbook_add(evas, (48 * e_scale), (48 * e_scale));

   of = e_widget_list_add(evas, 0, 0);
   if (cfdata->name)
     {
        ob = e_widget_check_add(evas, _("Window name"),
                                &(cfdata->remember.match_name));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
        ob = e_widget_entry_add(cfd->dia->win, &cfdata->name, NULL, NULL, NULL);
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
     }
   else
     {
        cfdata->remember.match_name = 0;
     }
   if (cfdata->class)
     {
        ob = e_widget_check_add(evas, _("Window class"),
                                &(cfdata->remember.match_class));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
        ob = e_widget_entry_add(cfd->dia->win, &cfdata->class, NULL, NULL, NULL);
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
     }
   else
     {
        cfdata->remember.match_class = 0;
     }
   if (cfdata->title)
     {
        ob = e_widget_check_add(evas, _("Title"),
                                &(cfdata->remember.match_title));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
        ob = e_widget_entry_add(cfd->dia->win, &cfdata->title, NULL, NULL, NULL);
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
     }
   else
     {
        cfdata->remember.match_title = 0;
     }
   if (cfdata->role)
     {
        ob = e_widget_check_add(evas, _("Window Role"),
                                &(cfdata->remember.match_role));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
        ob = e_widget_entry_add(cfd->dia->win, &cfdata->role, NULL, NULL, NULL);
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
     }
   else
     {
        cfdata->remember.match_role = 0;
     }
   if (cfdata->client)
     {
        if (cfdata->client->netwm.type != E_WINDOW_TYPE_UNKNOWN)
          {
             ob = e_widget_check_add(evas, _("Window type"),
                                     &(cfdata->remember.match_type));
             e_widget_list_object_append(of, ob, 1, 0, 0.5);
          }
        else
          {
             cfdata->remember.match_type = 0;
          }
        ob = e_widget_label_add(evas, _("wildcard matches are allowed"));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
        ob = e_widget_check_add(evas, _("Transience"),
                                &(cfdata->remember.match_transient));
        e_widget_list_object_append(of, ob, 1, 0, 0.5);
     }
   e_widget_toolbook_page_append(o, NULL, _("Identifiers"), of, 1, 1, 1, 1, 0.5, 0.0);

   of = e_widget_table_add(e_win_evas_win_get(evas), 0);
   ob = e_widget_check_add(evas, _("Position"),
                           &(cfdata->remember.apply_pos));
   e_widget_table_object_append(of, ob, 0, 0, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Size"),
                           &(cfdata->remember.apply_size));
   e_widget_table_object_append(of, ob, 0, 1, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Stacking"),
                           &(cfdata->remember.apply_layer));
   e_widget_table_object_append(of, ob, 0, 2, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Locks"),
                           &(cfdata->remember.apply_locks));
   e_widget_table_object_append(of, ob, 0, 3, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Border style"),
                           &(cfdata->remember.apply_border));
   e_widget_table_object_append(of, ob, 0, 4, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Icon Preference"),
                           &(cfdata->remember.apply_icon_pref));
   e_widget_table_object_append(of, ob, 0, 5, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Stickiness"),
                           &(cfdata->remember.apply_sticky));
   e_widget_table_object_append(of, ob, 0, 6, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Virtual Desktop"),
                           &(cfdata->remember.apply_desktop));
   e_widget_table_object_append(of, ob, 1, 0, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Shaded state"),
                           &(cfdata->remember.apply_shade));
   e_widget_table_object_append(of, ob, 1, 1, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Fullscreen state"),
                           &(cfdata->remember.apply_fullscreen));
   e_widget_table_object_append(of, ob, 1, 2, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Current Screen"),
                           &(cfdata->remember.apply_zone));
   e_widget_table_object_append(of, ob, 1, 3, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Skip Window List"),
                           &(cfdata->remember.apply_skip_winlist));
   e_widget_table_object_append(of, ob, 1, 4, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Skip Pager"),
                           &(cfdata->remember.apply_skip_pager));
   e_widget_table_object_append(of, ob, 1, 5, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Skip Taskbar"),
                           &(cfdata->remember.apply_skip_taskbar));
   e_widget_table_object_append(of, ob, 1, 6, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Offer Resistance"),
                           &(cfdata->remember.offer_resistance));
   e_widget_table_object_append(of, ob, 1, 7, 1, 1, 1, 0, 1, 0);
   ob = e_widget_check_add(evas, _("Opacity"),
                           &(cfdata->remember.apply_opacity));
   e_widget_table_object_append(of, ob, 0, 7, 1, 1, 1, 0, 1, 0);
   oc = e_widget_check_add(evas, _("Application file or name (.desktop)"),
                           &(cfdata->remember.apply_desktop_file));
   e_widget_table_object_append(of, oc, 0, 8, 1, 1, 1, 0, 1, 0);
   oc = e_widget_check_add(evas, _("Volume"),
                           &(cfdata->remember.apply_volume));
   e_widget_table_object_append(of, oc, 0, 9, 1, 1, 1, 0, 1, 0);
   ob = e_widget_entry_add(cfd->dia->win, &cfdata->desktop, NULL, NULL, NULL);
   e_widget_check_widget_disable_on_unchecked_add(oc, ob);
   e_widget_table_object_append(of, ob, 0, 10, 2, 1, 1, 0, 1, 0);
   e_widget_toolbook_page_append(o, NULL, _("Properties"), of, 1, 1, 1, 1, 0.5, 0.0);

   of = e_widget_table_add(e_win_evas_win_get(evas), 0);
   ob = e_widget_check_add(evas, _("Match only one window"),
                           &(cfdata->remember.apply_first_only));
   e_widget_table_object_append(of, ob, 0, 0, 1, 1, 1, 0, 1, 0);

   ob = e_widget_check_add(evas, _("Always focus on start"),
                           &(cfdata->remember.set_focus_on_start));
   e_widget_table_object_append(of, ob, 0, 1, 1, 1, 1, 0, 1, 0);

   ob = e_widget_check_add(evas, _("Keep current properties"),
                           &(cfdata->remember.keep_settings));
   e_widget_table_object_append(of, ob, 0, 2, 1, 1, 1, 0, 1, 0);

   if (cfdata->command)
     {
        ob = e_widget_check_add(evas, _("Start this program on login"),
                                &(cfdata->remember.apply_run));
        e_widget_table_object_append(of, ob, 0, 3, 1, 1, 1, 0, 1, 0);
     }
   e_widget_toolbook_page_append(o, NULL, _("Options"), of, 1, 1, 1, 1, 0.5, 0.0);
   e_widget_toolbook_page_show(o, 0);

   return o;
}

#include "e.h"

struct _E_Config_Dialog_Data
{
   int         engine;
   int         indirect;
   int         texture_from_pixmap;
   int         smooth_windows;
   int         grab;
   int         vsync;
   int         dither;
   int         swap_mode;

   const char *shadow_style;

   struct
   {
      int disable_popups;
      int disable_borders;
      int disable_overrides;
      int disable_menus;
      int disable_objects;
      int disable_all;
      int toggle_changed E_BITFIELD;
   } match;

   Evas_Object *styles_il;
   Evas_Object *vsync_check;
   Evas_Object *dither_check;
   Evas_Object *texture_check;

   Evas_Object *swap_auto_radio;
   Evas_Object *swap_full_radio;
   Evas_Object *swap_copy_radio;
   Evas_Object *swap_double_radio;
   Evas_Object *swap_triple_radio;

   int          keep_unmapped;
   int          max_unmapped_pixels;
   int          max_unmapped_time;
   int          min_unmapped_time;
   int          send_flush;
   int          send_dump;
   int          nocomp_fs;
   int          nofade;

   int          fps_show;
   int          fps_corner;
   double       first_draw_delay;
   int enable_advanced_features;
};

/* Protos */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd,
                               E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create_widgets(E_Config_Dialog *cfd,
                                          Evas *evas,
                                          E_Config_Dialog_Data *cfdata);
static int          _basic_apply_data(E_Config_Dialog *cfd,
                                      E_Config_Dialog_Data *cfdata);
static Evas_Object *_advanced_create_widgets(E_Config_Dialog *cfd,
                                          Evas *evas,
                                          E_Config_Dialog_Data *cfdata);
static int          _advanced_apply_data(E_Config_Dialog *cfd,
                                      E_Config_Dialog_Data *cfdata);

E_API E_Config_Dialog *
e_int_config_comp(Evas_Object *parent EINA_UNUSED, const char *params EINA_UNUSED)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   if (e_config_dialog_find("E", "appearance/comp")) return NULL;
   v = E_NEW(E_Config_Dialog_View, 1);

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply_data;
   v->basic.create_widgets = _basic_create_widgets;
   v->advanced.apply_cfdata = _advanced_apply_data;
   v->advanced.create_widgets = _advanced_create_widgets;

   cfd = e_config_dialog_new(NULL, _("Composite Settings"),
                             "E", "appearance/comp", "preferences-composite", 0, v, NULL);
   return cfd;
}

static void *
_create_data(E_Config_Dialog *cfd EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;
   E_Comp_Config *conf = e_comp_config_get();

   cfdata = E_NEW(E_Config_Dialog_Data, 1);

   cfdata->engine = conf->engine;
   if ((cfdata->engine != E_COMP_ENGINE_SW) &&
       (cfdata->engine != E_COMP_ENGINE_GL))
     cfdata->engine = E_COMP_ENGINE_SW;

   cfdata->enable_advanced_features = conf->enable_advanced_features;

   cfdata->indirect = conf->indirect;
   cfdata->texture_from_pixmap = conf->texture_from_pixmap;
   cfdata->smooth_windows = conf->smooth_windows;
   cfdata->grab = conf->grab;
   cfdata->vsync = conf->vsync;
   cfdata->dither = !(!!conf->no_dither);
   cfdata->swap_mode = conf->swap_mode;
   cfdata->shadow_style = eina_stringshare_add(conf->shadow_style);

   cfdata->keep_unmapped = conf->keep_unmapped;
   cfdata->max_unmapped_pixels = conf->max_unmapped_pixels;
   cfdata->max_unmapped_time = conf->max_unmapped_time;
   cfdata->min_unmapped_time = conf->min_unmapped_time;
   cfdata->send_flush = conf->send_flush;
   cfdata->send_dump = conf->send_dump;
   cfdata->nocomp_fs = conf->nocomp_fs;
   cfdata->nofade = conf->nofade;

   cfdata->fps_show = conf->fps_show;
   cfdata->fps_corner = conf->fps_corner;

   cfdata->first_draw_delay = conf->first_draw_delay;

   return cfdata;
}

static void
_comp_engine_toggle(void *data, Evas_Object *o EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;

   e_widget_disabled_set(cfdata->vsync_check,
                         (cfdata->engine == E_COMP_ENGINE_SW));
   e_widget_disabled_set(cfdata->dither_check,
                         (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->texture_check)
     e_widget_disabled_set(cfdata->texture_check,
                           (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->swap_auto_radio)
     e_widget_disabled_set(cfdata->swap_auto_radio,
                           (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->swap_full_radio)
     e_widget_disabled_set(cfdata->swap_full_radio,
                           (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->swap_copy_radio)
     e_widget_disabled_set(cfdata->swap_copy_radio,
                           (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->swap_double_radio)
     e_widget_disabled_set(cfdata->swap_double_radio,
                           (cfdata->engine == E_COMP_ENGINE_SW));

   if (cfdata->swap_triple_radio)
     e_widget_disabled_set(cfdata->swap_triple_radio,
                           (cfdata->engine == E_COMP_ENGINE_SW));
}

static void
_advanced_features_changed(E_Comp_Config *conf)
{
   conf->enable_advanced_features = !conf->enable_advanced_features;
   if (conf->enable_advanced_features)
     e_util_dialog_internal(_("WARNING"),
                            _("This option WILL break your desktop if you don't know what you're doing.<ps/>"
                              "Do not file bugs about anything that occurs with this option enabled.<ps/>"
                              "You have been warned."));
}

static void
_free_data(E_Config_Dialog *cfd  EINA_UNUSED,
           E_Config_Dialog_Data *cfdata)
{
   eina_stringshare_del(cfdata->shadow_style);
   free(cfdata);
}

static void
_advanced_matches_edit(void *data, void *d EINA_UNUSED)
{
   E_Config_Dialog *cfd = data;

   e_int_config_comp_match(cfd->dia->win, NULL);
}

static Evas_Object *
_advanced_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *ob,*ol, *of, *otb, *oi, *orec0;
   E_Radio_Group *rg;

   elm_win_center(cfd->dia->win, 1, 1);
   orec0 = evas_object_rectangle_add(evas);
   evas_object_name_set(orec0, "style_shadows");

   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);

   ob = e_widget_button_add(evas, _("Edit window matches"), NULL, _advanced_matches_edit, cfd, NULL);
   e_widget_list_object_append(ol, ob, 0, 0, 0.5);

   of = e_widget_frametable_add(evas, _("Select default style"), 0);
   e_widget_frametable_content_align_set(of, 0.5, 0.5);
   cfdata->styles_il = oi = e_comp_style_selector_create(evas, &(cfdata->shadow_style));
   e_widget_frametable_object_append(of, oi, 0, 0, 1, 1, 1, 1, 1, 1);
   e_widget_list_object_append(ol, of, 1, 1, 0.5);

   e_widget_toolbook_page_append(otb, NULL, _("Styles"), ol, 1, 1, 1, 1, 0.5, 0.0);

   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);
   of = e_widget_framelist_add(evas, _("Behavior"), 0);
   ob = e_widget_check_add(evas, _("Smooth scaling"), &(cfdata->smooth_windows));
   e_widget_framelist_object_append(of, ob);
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        ob = e_widget_check_add(evas, _("Don't composite fullscreen windows"), &(cfdata->nocomp_fs));
        e_widget_framelist_object_append(of, ob);
     }
   ob = e_widget_check_add(evas, _("Don't fade backlight"), &(cfdata->nofade));
   e_widget_framelist_object_append(of, ob);
   e_widget_list_object_append(ol, of, 1, 1, 0.5);

   of = e_widget_framelist_add(evas, _("Engine"), 0);
   rg = e_widget_radio_group_new(&(cfdata->engine));
   ob = e_widget_radio_add(evas, _("Software"), E_COMP_ENGINE_SW, rg);
   evas_object_smart_callback_add(ob, "changed", _comp_engine_toggle, cfdata);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("OpenGL"), E_COMP_ENGINE_GL, rg);
   evas_object_smart_callback_add(ob, "changed", _comp_engine_toggle, cfdata);
   e_widget_framelist_object_append(of, ob);
   if ((e_comp->comp_type == E_PIXMAP_TYPE_X) && (!getenv("ECORE_X_NO_XLIB")))
     {
        if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_OPENGL_X11))
          {
             ob = e_widget_label_add(evas, _("OpenGL options:"));
             e_widget_framelist_object_append(of, ob);

             ob = e_widget_check_add(evas, _("Tear-free updates (VSynced)"), &(cfdata->vsync));
             e_widget_framelist_object_append(of, ob);
             cfdata->vsync_check = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_check_add(evas, _("Texture from pixmap"), &(cfdata->texture_from_pixmap));
             e_widget_framelist_object_append(of, ob);
             cfdata->texture_check = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_label_add(evas, _("Assume swapping method:"));
             e_widget_framelist_object_append(of, ob);

             rg = e_widget_radio_group_new(&(cfdata->swap_mode));

             ob = e_widget_radio_add(evas, _("Auto"), ECORE_EVAS_GL_X11_SWAP_MODE_AUTO, rg);
             e_widget_framelist_object_append(of, ob);
             cfdata->swap_auto_radio = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_radio_add(evas, _("Invalidate (full redraw)"), ECORE_EVAS_GL_X11_SWAP_MODE_FULL, rg);
             e_widget_framelist_object_append(of, ob);
             cfdata->swap_full_radio = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_radio_add(evas, _("Copy from back to front"), ECORE_EVAS_GL_X11_SWAP_MODE_COPY, rg);
             e_widget_framelist_object_append(of, ob);
             cfdata->swap_copy_radio = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_radio_add(evas, _("Double buffered swaps"), ECORE_EVAS_GL_X11_SWAP_MODE_DOUBLE, rg);
             e_widget_framelist_object_append(of, ob);
             cfdata->swap_double_radio = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

             ob = e_widget_radio_add(evas, _("Triple buffered swaps"), ECORE_EVAS_GL_X11_SWAP_MODE_TRIPLE, rg);
             e_widget_framelist_object_append(of, ob);
             cfdata->swap_triple_radio = ob;
             e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

// lets not offer this anymore
//             ob = e_widget_check_add(evas, _("Indirect OpenGL (EXPERIMENTAL)"), &(cfdata->indirect));
//             e_widget_framelist_object_append(of, ob);
          }
     }
   ob = e_widget_check_add(evas, _("Dithering"), &(cfdata->dither));
   e_widget_framelist_object_append(of, ob);
   cfdata->dither_check = ob;
   e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));
   e_widget_list_object_append(ol, of, 1, 1, 0.5);

   e_widget_toolbook_page_append(otb, NULL, _("Rendering"), ol, 0, 0, 0, 0, 0.5, 0.0);

   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        of = e_widget_framelist_add(evas, _("X Messages"), 0);
        ob = e_widget_check_add(evas, _("Send flush"), &(cfdata->send_flush));
        e_widget_framelist_object_append(of, ob);
        ob = e_widget_check_add(evas, _("Send dump"), &(cfdata->send_dump));
        e_widget_framelist_object_append(of, ob);
        e_widget_list_object_append(ol, of, 1, 1, 0.5);

        of = e_widget_framelist_add(evas, _("Sync"), 0);
        ob = e_widget_check_add(evas, _("Grab Server during draw"), &(cfdata->grab));
        e_widget_framelist_object_append(of, ob);
        ob = e_widget_label_add(evas, _("Initial draw timeout for newly mapped windows"));
        e_widget_framelist_object_append(of, ob);
        ob = e_widget_slider_add(evas, 1, 0, _("%1.2f Seconds"), 0.01, 0.5, 0.01, 0, &(cfdata->first_draw_delay), NULL, 150);
        e_widget_framelist_object_append(of, ob);
        e_widget_list_object_append(ol, of, 1, 1, 0.5);
     }
   of = e_widget_framelist_add(evas, _("DANGEROUS"), 0);
   ob = e_widget_check_add(evas, _("Enable advanced compositing features"), &(cfdata->enable_advanced_features));
   e_widget_framelist_object_append(of, ob);
   e_widget_list_object_append(ol, of, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Misc"), ol, 0, 0, 0, 0, 0.5, 0.0);

   ///////////////////////////////////////////
/*
   ol = e_widget_list_add(evas, 0, 0);
   ol2 = e_widget_list_add(evas, 1, 1);
   of = e_widget_framelist_add(evas, _("Min hidden"), 0);
   e_widget_framelist_content_align_set(of, 0.5, 0.0);
   rg = e_widget_radio_group_new(&(cfdata->min_unmapped_time));
   ob = e_widget_radio_add(evas, _("30 Seconds"), 30, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("1 Minute"), 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("5 Minutes"), 5 * 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("30 Minutes"), 30 * 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("2 Hours"), 2 * 3600, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("10 Hours"), 10 * 3600, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("Forever"), 0, rg);
   e_widget_framelist_object_append(of, ob);
   e_widget_list_object_append(ol2, of, 1, 1, 0.5);
   of = e_widget_framelist_add(evas, _("Max hidden"), 0);
   e_widget_framelist_content_align_set(of, 0.5, 0.0);
   rg = e_widget_radio_group_new(&(cfdata->max_unmapped_time));
   ob = e_widget_radio_add(evas, _("30 Seconds"), 30, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("1 Minute"), 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("5 Minutes"), 5 * 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("30 Minutes"), 30 * 60, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("2 Hours"), 2 * 3600, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("10 Hours"), 10 * 3600, rg);
   e_widget_framelist_object_append(of, ob);
   ob = e_widget_radio_add(evas, _("Forever"), 0, rg);
   e_widget_framelist_object_append(of, ob);
   e_widget_list_object_append(ol2, of, 1, 1, 0.5);
   e_widget_list_object_append(ol, ol2, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Timeouts"), ol, 0, 0, 0, 0, 0.5, 0.0);
 */
   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);

   ob = e_widget_check_add(evas, _("Show Framerate"), &(cfdata->fps_show));
   e_widget_list_object_append(ol, ob, 1, 1, 0.5);

   of = e_widget_frametable_add(evas, _("Corner"), 0);
   e_widget_frametable_content_align_set(of, 0.5, 0.5);
   rg = e_widget_radio_group_new(&(cfdata->fps_corner));
   ob = e_widget_radio_icon_add(evas, _("Top Left"), "preferences-position-top-left",
                                24, 24, 0, rg);
   e_widget_frametable_object_append(of, ob, 0, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_radio_icon_add(evas, _("Top Right"), "preferences-position-top-right",
                                24, 24, 1, rg);
   e_widget_frametable_object_append(of, ob, 1, 0, 1, 1, 1, 1, 1, 1);
   ob = e_widget_radio_icon_add(evas, _("Bottom Left"), "preferences-position-bottom-left",
                                24, 24, 2, rg);
   e_widget_frametable_object_append(of, ob, 0, 1, 1, 1, 1, 1, 1, 1);
   ob = e_widget_radio_icon_add(evas, _("Bottom Right"), "preferences-position-bottom-right",
                                24, 24, 3, rg);
   e_widget_frametable_object_append(of, ob, 1, 1, 1, 1, 1, 1, 1, 1);
   e_widget_list_object_append(ol, of, 1, 1, 0.5);

   e_widget_toolbook_page_append(otb, NULL, _("Debug"), ol, 0, 0, 0, 0, 0.5, 0.0);

   e_widget_toolbook_page_show(otb, 0);

   return otb;
}

static int
_advanced_apply_data(E_Config_Dialog *cfd  EINA_UNUSED,
                     E_Config_Dialog_Data *cfdata)
{
   E_Comp_Config *conf = e_comp_config_get();

   if ((cfdata->smooth_windows != conf->smooth_windows) ||
       (cfdata->grab != conf->grab) ||
       (cfdata->keep_unmapped != conf->keep_unmapped) ||
       (cfdata->nocomp_fs != conf->nocomp_fs) ||
       (cfdata->nofade != conf->nofade) ||
       (cfdata->shadow_style != conf->shadow_style) ||
       (cfdata->max_unmapped_pixels != conf->max_unmapped_pixels) ||
       (cfdata->max_unmapped_time != conf->max_unmapped_time) ||
       (cfdata->min_unmapped_time != conf->min_unmapped_time) ||
       (cfdata->send_flush != conf->send_flush) ||
       (cfdata->send_dump != conf->send_dump) ||
       (cfdata->fps_show != conf->fps_show) ||
       (cfdata->fps_corner != conf->fps_corner) ||
       (!EINA_DBL_EQ(cfdata->first_draw_delay, conf->first_draw_delay)) ||
       (conf->enable_advanced_features != cfdata->enable_advanced_features)
       )
     {
        if (conf->enable_advanced_features != cfdata->enable_advanced_features)
          _advanced_features_changed(conf);
        conf->smooth_windows = cfdata->smooth_windows;
        conf->grab = cfdata->grab;
        conf->keep_unmapped = cfdata->keep_unmapped;
        conf->nocomp_fs = cfdata->nocomp_fs;
        conf->nofade = cfdata->nofade;
        conf->max_unmapped_pixels = cfdata->max_unmapped_pixels;
        conf->max_unmapped_time = cfdata->max_unmapped_time;
        conf->min_unmapped_time = cfdata->min_unmapped_time;
        conf->send_flush = cfdata->send_flush;
        conf->send_dump = cfdata->send_dump;
        conf->fps_show = cfdata->fps_show;
        conf->fps_corner = cfdata->fps_corner;
        conf->first_draw_delay = cfdata->first_draw_delay;
        if (conf->shadow_style)
          eina_stringshare_del(conf->shadow_style);
        conf->shadow_style = eina_stringshare_ref(cfdata->shadow_style);
        e_comp_shadows_reset();
     }
   if ((cfdata->engine != conf->engine) ||
       (cfdata->indirect != conf->indirect) ||
       (cfdata->texture_from_pixmap != conf->texture_from_pixmap) ||
       (cfdata->vsync != conf->vsync) ||
       (cfdata->dither != !(!!conf->no_dither)) ||
       (cfdata->swap_mode != conf->swap_mode))
     {
        E_Action *a;

        conf->engine = cfdata->engine;
        conf->indirect = cfdata->indirect;
        conf->texture_from_pixmap = cfdata->texture_from_pixmap;
        conf->vsync = cfdata->vsync;
        conf->no_dither = !(!!cfdata->dither);
        conf->swap_mode = cfdata->swap_mode;

        a = e_action_find("restart");
        if ((a) && (a->func.go)) a->func.go(NULL, NULL);
     }
   return e_comp_internal_save();
}

static void
_basic_comp_style_toggle(void *data, Evas_Object *o)
{
   E_Config_Dialog_Data *cfdata = data;

   e_widget_disabled_set(cfdata->styles_il, e_widget_check_checked_get(o));
   cfdata->match.toggle_changed = 1;
}

static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd,
                      Evas *evas,
                      E_Config_Dialog_Data *cfdata)
{
   Evas_Object *ob,*ol, *of, *otb, *oi, *orec0, *tab;
   E_Radio_Group *rg;

   e_dialog_resizable_set(cfd->dia, 0);
   elm_win_center(cfd->dia->win, 1, 1);
   orec0 = evas_object_rectangle_add(evas);
   evas_object_name_set(orec0, "style_shadows");

   tab = e_widget_table_add(e_win_evas_win_get(evas), 0);
   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);

   ob = e_widget_check_add(evas, _("Don't fade backlight"), &(cfdata->nofade));
   e_widget_list_object_append(ol, ob, 1, 0, 0.5);

   of = e_widget_frametable_add(evas, _("Select default style"), 0);
   e_widget_frametable_content_align_set(of, 0.5, 0.5);
   cfdata->styles_il = oi = e_comp_style_selector_create(evas, &(cfdata->shadow_style));
   e_widget_frametable_object_append(of, oi, 0, 0, 1, 1, 1, 1, 1, 1);
   e_widget_list_object_append(ol, of, 1, 1, 0.5);

   e_widget_on_change_hook_set(ob, _basic_comp_style_toggle, cfdata);

   e_widget_toolbook_page_append(otb, NULL, _("General"), ol, 1, 1, 1, 1, 0.5, 0.0);

   ///////////////////////////////////////////
   ol = e_widget_list_add(evas, 0, 0);

   of = e_widget_framelist_add(evas, _("Behavior"), 0);

   if (ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_OPENGL_X11))
     {
        ob = e_widget_check_add(evas, _("Tear-free updates (VSynced)"), &(cfdata->vsync));
        e_widget_framelist_object_append(of, ob);
        cfdata->vsync_check = ob;
        e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));
     }
   ob = e_widget_check_add(evas, _("Dithering"), &(cfdata->dither));
   e_widget_framelist_object_append(of, ob);
   cfdata->dither_check = ob;
   e_widget_disabled_set(ob, (cfdata->engine == E_COMP_ENGINE_SW));

   ob = e_widget_check_add(evas, _("Smooth scaling of window content"), &(cfdata->smooth_windows));
   e_widget_framelist_object_append(of, ob);

   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        ob = e_widget_check_add(evas, _("Don't composite fullscreen windows"), &(cfdata->nocomp_fs));
        e_widget_framelist_object_append(of, ob);
     }

   e_widget_list_object_append(ol, of, 1, 0, 0.5);

   of = e_widget_framelist_add(evas, _("Engine"), 0);
   rg = e_widget_radio_group_new(&(cfdata->engine));
   ob = e_widget_radio_add(evas, _("Software"), E_COMP_ENGINE_SW, rg);
   e_widget_framelist_object_append(of, ob);
   evas_object_smart_callback_add(ob, "changed", _comp_engine_toggle, cfdata);
   ob = e_widget_radio_add(evas, _("OpenGL"), E_COMP_ENGINE_GL, rg);
   e_widget_framelist_object_append(of, ob);
   evas_object_smart_callback_add(ob, "changed", _comp_engine_toggle, cfdata);

   e_widget_list_object_append(ol, of, 1, 0, 0.5);

   e_widget_toolbook_page_append(otb, NULL, _("Rendering"), ol, 0, 0, 0, 0, 0.5, 0.0);

   e_widget_toolbook_page_show(otb, 0);


   e_widget_table_object_append(tab, otb, 0, 0, 1, 1, 1, 1, 1, 1);
   return tab;
}

static int
_basic_apply_data(E_Config_Dialog *cfd  EINA_UNUSED,
                  E_Config_Dialog_Data *cfdata)
{
   E_Comp_Config *conf = e_comp_config_get();

   if (cfdata->match.toggle_changed ||
       (cfdata->smooth_windows != conf->smooth_windows) ||
       (cfdata->grab != conf->grab) ||
       (cfdata->nofade != conf->nofade) ||
       (cfdata->keep_unmapped != conf->keep_unmapped) ||
       (cfdata->nocomp_fs != conf->nocomp_fs) ||
       (cfdata->shadow_style != conf->shadow_style) ||
       (cfdata->max_unmapped_pixels != conf->max_unmapped_pixels) ||
       (cfdata->max_unmapped_time != conf->max_unmapped_time) ||
       (cfdata->min_unmapped_time != conf->min_unmapped_time) ||
       (cfdata->send_flush != conf->send_flush) ||
       (cfdata->send_dump != conf->send_dump) ||
       (cfdata->fps_show != conf->fps_show) ||
       (cfdata->fps_corner != conf->fps_corner) ||
       (!EINA_DBL_EQ(cfdata->first_draw_delay, conf->first_draw_delay))
       )
     {
        conf->smooth_windows = cfdata->smooth_windows;
        conf->grab = cfdata->grab;
        conf->nofade = cfdata->nofade;
        conf->keep_unmapped = cfdata->keep_unmapped;
        conf->nocomp_fs = cfdata->nocomp_fs;
        conf->max_unmapped_pixels = cfdata->max_unmapped_pixels;
        conf->max_unmapped_time = cfdata->max_unmapped_time;
        conf->min_unmapped_time = cfdata->min_unmapped_time;
        if (conf->enable_advanced_features != cfdata->enable_advanced_features)
          _advanced_features_changed(conf);
        conf->send_flush = cfdata->send_flush;
        conf->send_dump = cfdata->send_dump;
        conf->fps_show = cfdata->fps_show;
        conf->fps_corner = cfdata->fps_corner;
        conf->first_draw_delay = cfdata->first_draw_delay;
        if (conf->shadow_style)
          eina_stringshare_del(conf->shadow_style);
        conf->shadow_style = NULL;
        if (cfdata->shadow_style)
          conf->shadow_style = eina_stringshare_add(cfdata->shadow_style);
        e_comp_shadows_reset();
     }
   if ((cfdata->engine != conf->engine) ||
       (cfdata->indirect != conf->indirect) ||
       (cfdata->texture_from_pixmap != conf->texture_from_pixmap) ||
       (cfdata->vsync != conf->vsync) ||
       (cfdata->dither != !(!!conf->no_dither)))
     {
        E_Action *a;

        conf->engine = cfdata->engine;
        conf->indirect = cfdata->indirect;
        conf->texture_from_pixmap = cfdata->texture_from_pixmap;
        conf->vsync = cfdata->vsync;
        conf->no_dither = !(!!cfdata->dither);

        a = e_action_find("restart");
        if ((a) && (a->func.go)) a->func.go(NULL, NULL);
     }
   return e_comp_internal_save();
}

#include "e.h"


typedef struct _Match_Config
{
   E_Comp_Match            match;
   E_Config_Dialog *cfd;
   char            *title, *name, *clas, *role;
   int              borderless, dialog, accepts_focus;
   int              argb, fullscreen, modal, primary_type;
   int effect_type;
} Match_Config;

struct _E_Config_Dialog_Data
{
   Eina_List *popups;    // used for e popups
   Eina_List *borders;    // used for borders
   Eina_List *overrides;    // used for client menus, tooltips etc.
   Eina_List *menus;    // used for e menus
   Eina_List *objects;    // used for e objects
   Eina_List *comp_effects;    // list of visibility effects
   int        changed;

   Evas_Object *edit_il;

   Evas_Object *popups_il;
   Evas_Object *borders_il;
   Evas_Object *overrides_il;
   Evas_Object *menus_il;
   Evas_Object *objects_il;
};

static void
_match_dup(E_Comp_Match *m, Match_Config *m2)
{
   m2->match = *m;
   m2->match.title = eina_stringshare_ref(m2->match.title);
   m2->match.name = eina_stringshare_ref(m2->match.name);
   m2->match.clas = eina_stringshare_ref(m2->match.clas);
   m2->match.role = eina_stringshare_ref(m2->match.role);
   m2->primary_type = m2->match.primary_type;

   m2->match.shadow_style = eina_stringshare_ref(m2->match.shadow_style);
   m2->match.visibility_effect = eina_stringshare_ref(m2->match.visibility_effect);
}

static void
_match_free(Match_Config *m)
{
   eina_stringshare_del(m->match.title);
   eina_stringshare_del(m->match.name);
   eina_stringshare_del(m->match.clas);
   eina_stringshare_del(m->match.role);
   eina_stringshare_del(m->match.shadow_style);
   eina_stringshare_del(m->match.visibility_effect);
   free(m->title);
   free(m->name);
   free(m->clas);
   free(m->role);
   free(m);
}

static void
_match_dup2(Match_Config *m2, E_Comp_Match *m)
{
   *m = m2->match;
   m->title = eina_stringshare_add(m->title);
   m->name = eina_stringshare_add(m->name);
   m->clas = eina_stringshare_add(m->clas);
   m->role = eina_stringshare_add(m->role);
   m->shadow_style = eina_stringshare_add(m->shadow_style);
   m->visibility_effect = eina_stringshare_add(m->visibility_effect);
}

static const char *
_match_type_label_get(int type)
{
   if (E_WINDOW_TYPE_UNKNOWN == type)
     return _("Unused");
   if (E_WINDOW_TYPE_COMBO == type)
     return _("Combo");
   if (E_WINDOW_TYPE_DESKTOP == type)
     return _("Desktop");
   if (E_WINDOW_TYPE_DIALOG == type)
     return _("Dialog");
   if (E_WINDOW_TYPE_DOCK == type)
     return _("Dock");
   if (E_WINDOW_TYPE_DND == type)
     return _("Drag and Drop");
   if (E_WINDOW_TYPE_MENU == type)
     return _("Menu");
   if (E_WINDOW_TYPE_DROPDOWN_MENU == type)
     return _("Menu (Dropdown)");
   if (E_WINDOW_TYPE_POPUP_MENU == type)
     return _("Menu (Popup)");
   if (E_WINDOW_TYPE_NORMAL == type)
     return _("Normal");
   if (E_WINDOW_TYPE_NOTIFICATION == type)
     return _("Notification");
   if (E_WINDOW_TYPE_SPLASH == type)
     return _("Splash");
   if (E_WINDOW_TYPE_TOOLBAR == type)
     return _("Toolbar");
   if (E_WINDOW_TYPE_TOOLTIP == type)
     return _("Tooltip");
   if (E_WINDOW_TYPE_UTILITY == type)
     return _("Utility");
   if (E_WINDOW_TYPE_REAL_UNKNOWN == type)
     return _("Unknown");

   return _("Unused");
}

static char *
_match_label_get(Match_Config *m)
{
   char *label;
   Eina_Strbuf *buf = eina_strbuf_new();

   if (m->match.title)
     {
        eina_strbuf_append(buf, _("Title:"));
        eina_strbuf_append(buf, m->match.title);
        eina_strbuf_append(buf, _(" / "));
     }
   if (m->match.primary_type)
     {
        eina_strbuf_append(buf, _("Type:"));
        eina_strbuf_append(buf, _match_type_label_get(m->match.primary_type));
        eina_strbuf_append(buf, _(" / "));
     }
   if (m->match.name)
     {
        eina_strbuf_append(buf, _("Name:"));
        eina_strbuf_append(buf, m->match.name);
        eina_strbuf_append(buf, _(" / "));
     }
   if (m->match.clas)
     {
        eina_strbuf_append(buf, _("Class:"));
        eina_strbuf_append(buf, m->match.clas);
        eina_strbuf_append(buf, _(" / "));
     }
   if (m->match.role)
     {
        eina_strbuf_append(buf, _("Role:"));
        eina_strbuf_append(buf, m->match.role);
        eina_strbuf_append(buf, _(" / "));
     }
   if (m->match.shadow_style)
     {
        eina_strbuf_append(buf, _("Style:"));
        eina_strbuf_append(buf, m->match.shadow_style);
     }
   if (m->match.visibility_effect)
     {
        eina_strbuf_append(buf, _(" / "));
        eina_strbuf_append(buf, _("Effect:"));
        eina_strbuf_append(buf, m->match.visibility_effect);
     }

   if (!eina_strbuf_length_get(buf))
     return _("Unknown");

   label = eina_strbuf_string_steal(buf);
   eina_strbuf_free(buf);

   return label;
}

static void
_match_ilist_append(Evas_Object *il, Match_Config *m, int pos, int pre)
{
   char *name = _match_label_get(m);

   if (pos == -1)
     e_widget_ilist_append(il, NULL, name, NULL, m, NULL);
   else
     {
        if (pre)
          e_widget_ilist_prepend_relative(il, NULL, name, NULL, m, NULL, pos);
        else
          e_widget_ilist_append_relative(il, NULL, name, NULL, m, NULL, pos);
     }
   free(name);
}

static void
_match_list_up(Eina_List **list, Match_Config *m)
{
   Eina_List *l, *lp;

   l = eina_list_data_find_list(*list, m);
   if (!l) return;
   lp = l->prev;
   *list = eina_list_remove_list(*list, l);
   if (lp) *list = eina_list_prepend_relative_list(*list, m, lp);
   else *list = eina_list_prepend(*list, m);
}

static void
_match_list_down(Eina_List **list, Match_Config *m)
{
   Eina_List *l, *lp;

   l = eina_list_data_find_list(*list, m);
   if (!l) return;
   lp = l->next;
   *list = eina_list_remove_list(*list, l);
   if (lp) *list = eina_list_append_relative_list(*list, m, lp);
   else *list = eina_list_append(*list, m);
}

static Eina_Bool
_match_list_del(Eina_List **list, Match_Config *m)
{
   Eina_List *l;

   l = eina_list_data_find_list(*list, m);
   if (!l) return EINA_FALSE;
   *list = eina_list_remove_list(*list, l);
   _match_free(m);
   return EINA_TRUE;
}


static void
_cb_dialog_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Evas_Object *bg, *of;
   int x, y, w, h;

   of = data;
   bg = evas_object_data_get(of, "bg");
   evas_object_geometry_get(obj, &x, &y, &w, &h);

   evas_object_move(bg, x, y);
   evas_object_resize(bg, w, h);
   evas_object_move(of, x, y);
   evas_object_resize(of, w, h);
}

static void
_edit_ok(void *d1, void *d2)
{
   Match_Config *m = d1;
   Evas_Object *dia, *bg, *of = d2;
   Evas_Object *il;

   if (m->title || m->name || m->clas || m->role || (m->primary_type != m->match.primary_type) ||
       (eina_list_nth(m->cfd->cfdata->comp_effects, m->effect_type) != m->match.visibility_effect))
     {
        m->cfd->cfdata->changed = 1;
        e_config_dialog_changed_set(m->cfd, 1);
        eina_stringshare_replace(&m->match.title, NULL);
        if (m->title)
          {
             if (m->title[0]) m->match.title = eina_stringshare_add(m->title);
             E_FREE(m->title);
          }
        eina_stringshare_replace(&m->match.name, NULL);
        if (m->name)
          {
             if (m->name[0]) m->match.name = eina_stringshare_add(m->name);
             E_FREE(m->name);
          }
        eina_stringshare_replace(&m->match.clas, NULL);
        if (m->clas)
          {
             if (m->clas[0]) m->match.clas = eina_stringshare_add(m->clas);
             E_FREE(m->clas);
          }
        eina_stringshare_replace(&m->match.role, NULL);
        if (m->role)
          {
             if (m->role[0]) m->match.role = eina_stringshare_add(m->role);
             E_FREE(m->role);
          }
        m->match.borderless = m->borderless;
        m->match.dialog = m->dialog;
        m->match.accepts_focus = m->accepts_focus;
        m->match.argb = m->argb;
        m->match.fullscreen = m->fullscreen;
        m->match.modal = m->modal;
        m->match.primary_type = m->primary_type;
        eina_stringshare_refplace(&m->match.visibility_effect, eina_list_nth(m->cfd->cfdata->comp_effects, m->effect_type));
        if (eina_streq(m->match.visibility_effect, "none"))
          eina_stringshare_replace(&m->match.visibility_effect, NULL);
        il = m->cfd->cfdata->edit_il;
        {
           const Eina_List *l;
           E_Ilist_Item *ili;
           Eina_Bool found = EINA_FALSE;

           EINA_LIST_FOREACH(e_widget_ilist_items_get(il), l, ili)
             {
                char *txt;

                if (e_widget_ilist_item_data_get(ili) != m) continue;
                txt = _match_label_get(m);
                e_ilist_item_label_set(ili, txt);
                free(txt);
                found = EINA_TRUE;
                break;
             }
           if (!found)
             {
                unsigned int n;

                if (il == m->cfd->cfdata->popups_il)
                  m->cfd->cfdata->popups = eina_list_append(m->cfd->cfdata->popups, m);
                else if (il == m->cfd->cfdata->borders_il)
                  m->cfd->cfdata->borders = eina_list_append(m->cfd->cfdata->borders, m);
                else if (il == m->cfd->cfdata->overrides_il)
                  m->cfd->cfdata->overrides = eina_list_append(m->cfd->cfdata->overrides, m);
                else if (il == m->cfd->cfdata->menus_il)
                  m->cfd->cfdata->menus = eina_list_append(m->cfd->cfdata->menus, m);
                else if (il == m->cfd->cfdata->objects_il)
                  m->cfd->cfdata->objects = eina_list_append(m->cfd->cfdata->objects, m);
                _match_ilist_append(il, m, -1, 0);
                n = e_widget_ilist_count(il);
                e_widget_ilist_nth_show(il, n - 1, 0);
                e_widget_ilist_selected_set(il, n - 1);
             }
        }
     }
   bg = evas_object_data_get(of, "bg");
   dia = evas_object_data_get(of, "dia");

   evas_object_event_callback_del(dia, EVAS_CALLBACK_RESIZE, _cb_dialog_resize);
   evas_object_del(bg);
   evas_object_del(of);
}

static void
_create_edit_frame(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata, Match_Config *m)
{
   Evas_Object *of, *oi, *lb, *en, *bt, *tb, *tab2, *o, *sf, *li;
   E_Radio_Group *rg;
   Eina_List *l;
   Eina_Stringshare *s;
   int row, mode = 0;
   int x, y, w, h, mw, mh;

   o = edje_object_add(evas);
   e_theme_edje_object_set(o, "base/theme/dialog", "e/widgets/dialog/main");
   evas_object_geometry_get(cfd->dia->bg_object, &x, &y, &w, &h);
   evas_object_move(o, x, y);
   evas_object_resize(o, w, h);
   evas_object_show(o);

   of = e_widget_frametable_add(evas, _("Edit E_Comp_Match"), 0);
   evas_object_data_set(of, "bg", o);
   evas_object_data_set(of, "dia", cfd->dia->bg_object);
   evas_object_move(of, x, y);
   evas_object_resize(of, w, h);
   evas_object_show(of);

   evas_object_event_callback_add(cfd->dia->bg_object, EVAS_CALLBACK_RESIZE,
                                  _cb_dialog_resize, of);

   tb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   tab2 = e_widget_table_add(e_win_evas_win_get(evas), 0);
   if (cfdata->edit_il == cfdata->borders_il)
     {
        if (m->match.title) m->title = strdup(m->match.title);
        else m->title = NULL;
        lb = e_widget_label_add(evas, _("Title"));
        e_widget_table_object_append(tab2, lb, 0, 0, 1, 1, 1, 0, 0, 0);
        en = e_widget_entry_add(cfd->dia->win, &(m->title), NULL, NULL, NULL);
        e_widget_table_object_append(tab2, en, 1, 0, 1, 1, 1, 0, 1, 0);
     }
   if ((cfdata->edit_il == cfdata->borders_il) ||
       (cfdata->edit_il == cfdata->overrides_il) ||
       (cfdata->edit_il == cfdata->popups_il))
     {
        if (m->match.name) m->name = strdup(m->match.name);
        else m->name = NULL;
        lb = e_widget_label_add(evas, _("Name"));
        e_widget_table_object_append(tab2, lb, 0, 1, 1, 1, 1, 0, 0, 0);
        en = e_widget_entry_add(cfd->dia->win, &(m->name), NULL, NULL, NULL);
        e_widget_table_object_append(tab2, en, 1, 1, 1, 1, 1, 0, 1, 0);
     }
   if ((cfdata->edit_il == cfdata->borders_il) ||
       (cfdata->edit_il == cfdata->overrides_il))
     {
        if (m->match.clas) m->clas = strdup(m->match.clas);
        else m->clas = NULL;
        lb = e_widget_label_add(evas, _("Class"));
        e_widget_table_object_append(tab2, lb, 0, 2, 1, 1, 1, 0, 0, 0);
        en = e_widget_entry_add(cfd->dia->win, &(m->clas), NULL, NULL, NULL);
        e_widget_table_object_append(tab2, en, 1, 2, 1, 1, 1, 0, 1, 0);
     }
   if (cfdata->edit_il == cfdata->borders_il)
     {
        if (m->match.role) m->role = strdup(m->match.role);
        else m->role = NULL;
        lb = e_widget_label_add(evas, _("Role"));
        e_widget_table_object_append(tab2, lb, 0, 3, 1, 1, 1, 0, 0, 0);
        en = e_widget_entry_add(cfd->dia->win, &(m->role), NULL, NULL, NULL);
        e_widget_table_object_append(tab2, en, 1, 3, 1, 1, 1, 0, 1, 0);
     }
   e_widget_toolbook_page_append(tb, NULL, _("Names"), tab2, 1, 1, 1, 1, 0.5, 0.0);

   if ((cfdata->edit_il == cfdata->borders_il) ||
       (cfdata->edit_il == cfdata->overrides_il))
     {
        rg = e_widget_radio_group_new(&m->primary_type);

        li = e_widget_list_add(evas, 1, 0);

        o = e_widget_radio_add(evas, _("Unused"), E_WINDOW_TYPE_UNKNOWN, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);

        o = e_widget_radio_add(evas, _("Combo"), E_WINDOW_TYPE_COMBO, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Desktop"), E_WINDOW_TYPE_DESKTOP, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Dialog"), E_WINDOW_TYPE_DIALOG, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Dock"), E_WINDOW_TYPE_DOCK, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Drag and Drop"), E_WINDOW_TYPE_DND, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Menu"), E_WINDOW_TYPE_MENU, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Menu (Dropdown)"), E_WINDOW_TYPE_DROPDOWN_MENU, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Menu (Popup)"), E_WINDOW_TYPE_POPUP_MENU, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Normal"), E_WINDOW_TYPE_NORMAL, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Notification"), E_WINDOW_TYPE_NOTIFICATION, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Splash"), E_WINDOW_TYPE_SPLASH, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Toolbar"), E_WINDOW_TYPE_TOOLBAR, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Tooltip"), E_WINDOW_TYPE_TOOLTIP, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Utility"), E_WINDOW_TYPE_UTILITY, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);
        o = e_widget_radio_add(evas, _("Unknown"), E_WINDOW_TYPE_REAL_UNKNOWN, rg);
        e_widget_list_object_append(li, o, 1, 0, 0.0);

        e_widget_size_min_get(li, &mw, &mh);
        evas_object_resize(li, mw, mh);

        sf = e_widget_scrollframe_simple_add(evas, li);
        e_widget_toolbook_page_append(tb, NULL, _("Types"), sf,
                                      1, 1, 1, 1, 0.5, 0.0);
     }

   m->borderless = m->match.borderless;
   m->dialog = m->match.dialog;
   m->accepts_focus = m->match.accepts_focus;
   m->argb = m->match.argb;
   m->fullscreen = m->match.fullscreen;
   m->modal = m->match.modal;

   row = 0;
   tab2 = e_widget_table_add(e_win_evas_win_get(evas), 0);
   lb = e_widget_label_add(evas, _("Unused"));
   e_widget_table_object_append(tab2, lb, 1, row, 1, 1, 0, 0, 0, 0);
   lb = e_widget_label_add(evas, _("On"));
   e_widget_table_object_append(tab2, lb, 2, row, 1, 1, 0, 0, 0, 0);
   lb = e_widget_label_add(evas, _("Off"));
   e_widget_table_object_append(tab2, lb, 3, row, 1, 1, 0, 0, 0, 0);
   row++;

   if (cfdata->edit_il == cfdata->borders_il)
     {
        lb = e_widget_label_add(evas, _("Borderless"));
        e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
        rg = e_widget_radio_group_new(&m->borderless);
        o = e_widget_radio_add(evas, NULL, 0, rg);
        e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, 1, rg);
        e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, -1, rg);
        e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
        row++;
     }
   if (cfdata->edit_il == cfdata->borders_il)
     {
        lb = e_widget_label_add(evas, _("Dialog"));
        e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
        rg = e_widget_radio_group_new(&m->dialog);
        o = e_widget_radio_add(evas, NULL, 0, rg);
        e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, 1, rg);
        e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, -1, rg);
        e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
        row++;
     }
   if (cfdata->edit_il == cfdata->borders_il)
     {
        lb = e_widget_label_add(evas, _("Accepts Focus"));
        e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
        rg = e_widget_radio_group_new(&m->accepts_focus);
        o = e_widget_radio_add(evas, NULL, 0, rg);
        e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, 1, rg);
        e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, -1, rg);
        e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
        row++;
     }
   lb = e_widget_label_add(evas, _("ARGB"));
   e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
   rg = e_widget_radio_group_new(&m->argb);
   o = e_widget_radio_add(evas, NULL, 0, rg);
   e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
   o = e_widget_radio_add(evas, NULL, 1, rg);
   e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
   o = e_widget_radio_add(evas, NULL, -1, rg);
   e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
   row++;
   if (cfdata->edit_il == cfdata->borders_il)
     {
        lb = e_widget_label_add(evas, _("Fullscreen"));
        e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
        rg = e_widget_radio_group_new(&m->fullscreen);
        o = e_widget_radio_add(evas, NULL, 0, rg);
        e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, 1, rg);
        e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, -1, rg);
        e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
        row++;
     }
   if (cfdata->edit_il == cfdata->borders_il)
     {
        lb = e_widget_label_add(evas, _("Modal"));
        e_widget_table_object_append(tab2, lb, 0, row, 1, 1, 1, 0, 1, 0);
        rg = e_widget_radio_group_new(&m->modal);
        o = e_widget_radio_add(evas, NULL, 0, rg);
        e_widget_table_object_append(tab2, o, 1, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, 1, rg);
        e_widget_table_object_append(tab2, o, 2, row, 1, 1, 0, 0, 0, 0);
        o = e_widget_radio_add(evas, NULL, -1, rg);
        e_widget_table_object_append(tab2, o, 3, row, 1, 1, 0, 0, 0, 0);
        row++;
     }
   e_widget_toolbook_page_append(tb, NULL, _("Flags"), tab2,
                                 1, 1, 1, 1, 0.5, 0.0);

   oi = e_comp_style_selector_create(evas, &(m->match.shadow_style));
   e_widget_toolbook_page_append(tb, NULL, _("Style"), oi,
                                 1, 1, 1, 1, 0.5, 0.0);

   oi = e_widget_list_add(evas, 1, 0);
   EINA_LIST_FOREACH(cfdata->comp_effects->next, l, s)
     {
        m->effect_type++;
        if (s == m->match.visibility_effect)
          break;
     }
   if (!s) m->effect_type = 0;
   rg = e_widget_radio_group_new(&(m->effect_type));
   EINA_LIST_FOREACH(cfdata->comp_effects, l, s)
     {
        char *p;
        const char *pp;
        char buf[PATH_MAX];

        pp = strchr(s, '/');
        pp = pp ? pp + 1 : s;
        p = memcpy(buf, pp, strlen(pp) + 1);
        p[0] = toupper(p[0]);
        o = e_widget_radio_add(evas, _(p), mode, rg);
        e_widget_list_object_append(oi, o, 1, 0, 0.5);
        mode++;
     }
   e_widget_toolbook_page_append(tb, NULL, _("Effect"), oi,
                                 1, 1, 1, 1, 0.5, 0.0);

   e_widget_frametable_object_append(of, tb, 0, 0, 1, 1, 1, 1, 1, 1);
   e_widget_toolbook_page_show(tb, 0);

   bt = e_widget_button_add(evas, _("OK"), NULL, _edit_ok, m, of);
   e_widget_frametable_object_append(of, bt, 0, 1, 1, 1, 0, 0, 0, 0);

   e_widget_size_min_get(of, &mw, &mh);
   {
      int ww, wh;

      evas_object_geometry_get(cfd->dia->win, NULL, NULL, &ww, &wh);
      evas_object_resize(cfd->dia->win, MAX(mw, ww), MAX(mh, wh));
   }
}

static void
_but_up(void *d1, void *d2)
{
   E_Config_Dialog *cfd = d1;
   Evas_Object *il = d2;
   Match_Config *m;
   int n;

   e_widget_ilist_freeze(il);
   n = e_widget_ilist_selected_get(il);
   if (n < 1) return;
   m = e_widget_ilist_nth_data_get(il, n);
   if (!m)
     {
        e_widget_ilist_thaw(il);
        return;
     }
   e_widget_ilist_remove_num(il, n);
   n--;
   _match_ilist_append(il, m, n, 1);
   e_widget_ilist_nth_show(il, n, 0);
   e_widget_ilist_selected_set(il, n);
   e_widget_ilist_thaw(il);
   e_widget_ilist_go(il);
   _match_list_up(&(cfd->cfdata->popups), m);
   _match_list_up(&(cfd->cfdata->borders), m);
   _match_list_up(&(cfd->cfdata->overrides), m);
   _match_list_up(&(cfd->cfdata->menus), m);
   _match_list_up(&(cfd->cfdata->objects), m);
   cfd->cfdata->changed = 1;
   e_config_dialog_changed_set(cfd, 1);
}

static void
_but_down(void *d1, void *d2)
{
   E_Config_Dialog *cfd = d1;
   Evas_Object *il = d2;
   Match_Config *m;
   int n;

   e_widget_ilist_freeze(il);
   n = e_widget_ilist_selected_get(il);
   if (n >= (e_widget_ilist_count(il) - 1)) return;
   m = e_widget_ilist_nth_data_get(il, n);
   if (!m)
     {
        e_widget_ilist_thaw(il);
        return;
     }
   e_widget_ilist_remove_num(il, n);
   _match_ilist_append(il, m, n, 0);
   e_widget_ilist_nth_show(il, n + 1, 0);
   e_widget_ilist_selected_set(il, n + 1);
   e_widget_ilist_thaw(il);
   e_widget_ilist_go(il);
   _match_list_down(&(cfd->cfdata->popups), m);
   _match_list_down(&(cfd->cfdata->borders), m);
   _match_list_down(&(cfd->cfdata->overrides), m);
   _match_list_down(&(cfd->cfdata->menus), m);
   _match_list_down(&(cfd->cfdata->objects), m);
   cfd->cfdata->changed = 1;
   e_config_dialog_changed_set(cfd, 1);
}

static void
_but_add(void *d1, void *d2)
{
   E_Config_Dialog *cfd = d1;
   Evas_Object *il = d2;
   Match_Config *m;

   m = E_NEW(Match_Config, 1);
   m->cfd = cfd;
   m->match.shadow_style = eina_stringshare_add("default");

   cfd->cfdata->edit_il = il;
   _create_edit_frame(cfd, evas_object_evas_get(il), cfd->cfdata, m);
}

static void
_but_del(void *d1, void *d2)
{
   E_Config_Dialog *cfd = d1;
   Evas_Object *il = d2;
   Match_Config *m;
   int n;

   e_widget_ilist_freeze(il);
   n = e_widget_ilist_selected_get(il);
   m = e_widget_ilist_nth_data_get(il, n);
   if (!m)
     {
        e_widget_ilist_thaw(il);
        return;
     }
   e_widget_ilist_remove_num(il, n);
   e_widget_ilist_thaw(il);
   e_widget_ilist_go(il);
   if (!_match_list_del(&(cfd->cfdata->popups), m))
     if (!_match_list_del(&(cfd->cfdata->borders), m))
       if (!_match_list_del(&(cfd->cfdata->overrides), m))
         if (!_match_list_del(&(cfd->cfdata->menus), m))
           _match_list_del(&(cfd->cfdata->objects), m);
   cfd->cfdata->changed = 1;
   e_config_dialog_changed_set(cfd, 1);
}

static void
_but_edit(void *d1, void *d2)
{
   E_Config_Dialog *cfd = d1;
   Evas_Object *il = d2;
   int n;
   Match_Config *m;

   n = e_widget_ilist_selected_get(il);
   m = e_widget_ilist_nth_data_get(il, n);
   if (!m) return;

   cfd->cfdata->edit_il = il;
   _create_edit_frame(cfd, evas_object_evas_get(il), cfd->cfdata, m);
   cfd->cfdata->changed = 1;
   e_config_dialog_changed_set(cfd, 1);
}

static Evas_Object *
_create_match_editor(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata EINA_UNUSED, Eina_List **matches, Evas_Object **il_ret)
{
   Evas_Object *tab, *il, *bt;
   Match_Config *m;
   Eina_List *l;

   tab = e_widget_table_add(e_win_evas_win_get(evas), 0);

   il = e_widget_ilist_add(evas, 16, 16, NULL);
   e_widget_size_min_set(il, 160, 100);

   EINA_LIST_FOREACH(*matches, l, m)
     {
        _match_ilist_append(il, m, -1, 0);
     }

   e_widget_ilist_go(il);
   e_widget_table_object_append(tab, il, 0, 0, 1, 5, 1, 1, 1, 1);

   bt = e_widget_button_add(evas, _("Up"), NULL, _but_up, cfd, il);
   e_widget_table_object_append(tab, bt, 1, 0, 1, 1, 1, 1, 0, 0);
   bt = e_widget_button_add(evas, _("Down"), NULL, _but_down, cfd, il);
   e_widget_table_object_append(tab, bt, 1, 1, 1, 1, 1, 1, 0, 0);
   bt = e_widget_button_add(evas, _("Add"), NULL, _but_add, cfd, il);
   e_widget_table_object_append(tab, bt, 1, 2, 1, 1, 1, 1, 0, 0);
   bt = e_widget_button_add(evas, _("Del"), NULL, _but_del, cfd, il);
   e_widget_table_object_append(tab, bt, 1, 3, 1, 1, 1, 1, 0, 0);
   bt = e_widget_button_add(evas, _("Edit"), NULL, _but_edit, cfd, il);
   e_widget_table_object_append(tab, bt, 1, 4, 1, 1, 1, 1, 0, 0);

   *il_ret = il;

   return tab;
}

static Evas_Object *
_create_styles_toolbook(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *tb, *oi, *il;

   tb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);

   oi = _create_match_editor(cfd, evas, cfdata, &(cfdata->borders), &il);
   cfdata->borders_il = il;
   e_widget_toolbook_page_append(tb, NULL, _("Apps"), oi, 1, 1, 1, 1, 0.5, 0.0);

   oi = _create_match_editor(cfd, evas, cfdata, &(cfdata->popups), &il);
   cfdata->popups_il = il;
   e_widget_toolbook_page_append(tb, NULL, _("Popups"), oi, 1, 1, 1, 1, 0.5, 0.0);

   oi = _create_match_editor(cfd, evas, cfdata, &(cfdata->overrides), &il);
   cfdata->overrides_il = il;
   e_widget_toolbook_page_append(tb, NULL, _("Overrides"), oi, 1, 1, 1, 1, 0.5, 0.0);

   oi = _create_match_editor(cfd, evas, cfdata, &(cfdata->menus), &il);
   cfdata->menus_il = il;
   e_widget_toolbook_page_append(tb, NULL, _("Menus"), oi, 1, 1, 1, 1, 0.5, 0.0);

   oi = _create_match_editor(cfd, evas, cfdata, &(cfdata->objects), &il);
   cfdata->objects_il = il;
   e_widget_toolbook_page_append(tb, NULL, _("Objects"), oi, 1, 1, 1, 1, 0.5, 0.0);

   e_widget_toolbook_page_show(tb, 0);

   return tb;
}

static int
_basic_apply_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   Eina_List *l;
   E_Comp_Match *m;
   Match_Config *m2;
   E_Comp_Config *conf = e_comp_config_get();

   if (!cfdata->changed) return 0;

   E_FREE_LIST(conf->match.popups, e_comp_cfdata_match_free);
   E_FREE_LIST(conf->match.borders, e_comp_cfdata_match_free);
   E_FREE_LIST(conf->match.overrides, e_comp_cfdata_match_free);
   E_FREE_LIST(conf->match.menus, e_comp_cfdata_match_free);
   E_FREE_LIST(conf->match.objects, e_comp_cfdata_match_free);

   EINA_LIST_FOREACH(cfdata->popups, l, m2)
     {
        m = E_NEW(E_Comp_Match, 1);
        _match_dup2(m2, m);
        conf->match.popups = eina_list_append(conf->match.popups, m);
     }
   EINA_LIST_FOREACH(cfdata->borders, l, m2)
     {
        m = E_NEW(E_Comp_Match, 1);
        _match_dup2(m2, m);
        conf->match.borders = eina_list_append(conf->match.borders, m);
     }
   EINA_LIST_FOREACH(cfdata->overrides, l, m2)
     {
        m = E_NEW(E_Comp_Match, 1);
        _match_dup2(m2, m);
        conf->match.overrides = eina_list_append(conf->match.overrides, m);
     }
   EINA_LIST_FOREACH(cfdata->menus, l, m2)
     {
        m = E_NEW(E_Comp_Match, 1);
        _match_dup2(m2, m);
        conf->match.menus = eina_list_append(conf->match.menus, m);
     }
   EINA_LIST_FOREACH(cfdata->objects, l, m2)
     {
        m = E_NEW(E_Comp_Match, 1);
        _match_dup2(m2, m);
        conf->match.objects = eina_list_append(conf->match.objects, m);
     }
   cfdata->changed = 0;
   e_comp_shadows_reset();
   e_config_save_queue();
   return 1;
}

static Evas_Object *
_basic_create_widgets(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *orec0;

   orec0 = evas_object_rectangle_add(evas);
   evas_object_name_set(orec0, "style_shadows");
   elm_win_center(cfd->dia->win, 1, 1);

   return _create_styles_toolbook(cfd, evas, cfdata);
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   E_FREE_LIST(cfdata->popups, _match_free);
   E_FREE_LIST(cfdata->borders, _match_free);
   E_FREE_LIST(cfdata->menus, _match_free);
   E_FREE_LIST(cfdata->objects, _match_free);
   E_FREE_LIST(cfdata->overrides, _match_free);
   free(cfdata);
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;
   Eina_List *l;
   Eina_Stringshare *grp;
   E_Comp_Match *m;
   Match_Config *m2;
   E_Comp_Config *conf = e_comp_config_get();

   cfdata = E_NEW(E_Config_Dialog_Data, 1);

   EINA_LIST_FOREACH(conf->match.popups, l, m)
     {
        m2 = E_NEW(Match_Config, 1);
        _match_dup(m, m2);
        m2->cfd = cfd;
        cfdata->popups = eina_list_append(cfdata->popups, m2);
     }

   EINA_LIST_FOREACH(conf->match.borders, l, m)
     {
        m2 = E_NEW(Match_Config, 1);
        _match_dup(m, m2);
        m2->cfd = cfd;
        cfdata->borders = eina_list_append(cfdata->borders, m2);
     }

   EINA_LIST_FOREACH(conf->match.overrides, l, m)
     {
        m2 = E_NEW(Match_Config, 1);
        _match_dup(m, m2);
        m2->cfd = cfd;
        cfdata->overrides = eina_list_append(cfdata->overrides, m2);
     }

   EINA_LIST_FOREACH(conf->match.menus, l, m)
     {
        m2 = E_NEW(Match_Config, 1);
        _match_dup(m, m2);
        m2->cfd = cfd;
        cfdata->menus = eina_list_append(cfdata->menus, m2);
     }

   EINA_LIST_FOREACH(conf->match.objects, l, m)
     {
        m2 = E_NEW(Match_Config, 1);
        _match_dup(m, m2);
        m2->cfd = cfd;
        cfdata->objects = eina_list_append(cfdata->objects, m2);
     }
   cfdata->comp_effects = e_theme_collection_items_find("base/theme/borders", "e/comp/effects/visibility");
   /* comp effects must be prefixed with "visibility" or they won't work and things will break! */
   EINA_LIST_FOREACH(cfdata->comp_effects, l, grp)
     {
        Eina_Stringshare *g;
        char buf[1024];

        snprintf(buf, sizeof(buf), "visibility/%s", grp);
        g = eina_stringshare_add(buf);
        eina_stringshare_del(grp);
        eina_list_data_set(l, g);
     }
   cfdata->comp_effects = eina_list_prepend(cfdata->comp_effects, eina_stringshare_add("none"));
   return cfdata;
}

E_API E_Config_Dialog *
e_int_config_comp_match(Evas_Object *parent, const char *params EINA_UNUSED)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   if (e_config_dialog_find("E", "internal/comp_matches")) return NULL;
   v = E_NEW(E_Config_Dialog_View, 1);

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply_data;
   v->basic.create_widgets = _basic_create_widgets;

   cfd = e_config_dialog_new(parent, _("Composite Match Settings"),
                             "E", "_comp_matches", "preferences-composite", 0, v, NULL);
   e_dialog_resizable_set(cfd->dia, 1);
   return cfd;
}
#include "e.h"
#include <assert.h>

typedef struct _CFModule CFModule;
typedef struct _CFType   CFType;
typedef struct _CFTypes  CFTypes;

struct _CFModule
{
   const char  *short_name, *name, *comment;
   const char  *icon, *orig_path;
   E_Module    *module;
   Evas_Object *end;
   int          idx;
   Eina_Bool    enabled E_BITFIELD;
};

struct _CFType
{
   const char *key, *name, *icon;
   Eina_Hash  *modules_hash; /* just used before constructing list */
   Eina_List  *modules; /* sorted and ready to be used */
};

struct _E_Config_Dialog_Data
{
   Evas                *evas;
   Evas_Object         *l_modules;
   Evas_Object         *o_toolbar;
   Evas_Object         *b_load, *b_unload;
   Evas_Object         *o_desc;
   Eina_List           *types;
   Ecore_Event_Handler *module_update;
   struct
   {
      Eina_List *loaded, *unloaded;
   } selected;
};

struct _CFTypes
{
   size_t      key_len;
   const char *key, *name, *icon;
};

/* pre defined types (used to specify icon and i18n name) */
static const CFTypes _types[] =
{
#define _CFT(k, n, i) \
  {sizeof(k) - 1, k, n, i}
   _CFT("utils", N_("Utilities"), "modules-utils"),
   _CFT("system", N_("System"), "modules-system"),
   _CFT("look", N_("Look"), "modules-look"),
   _CFT("files", N_("Files"), "modules-files"),
   _CFT("launcher", N_("Launcher"), "modules-launcher"),
   _CFT("core", N_("Core"), "modules-core"),
   _CFT("mobile", N_("Mobile"), "modules-mobile"),
   _CFT("settings", N_("Settings"), "modules-settings"),
#undef _CFT
   {0, NULL, NULL, NULL}
};

/* local function protos */
static void         _cftype_free(CFType *cft);

static void         _widget_list_selection_changed(void *data, Evas_Object *obj EINA_UNUSED);

static void        *_create_data(E_Config_Dialog *cfd);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static void         _fill_cat_list(E_Config_Dialog_Data *cfdata);
static void         _module_end_state_apply(CFModule *cfm);

static void         _toolbar_select_cb(void *data, void *data2);

static CFType      *_cftype_find(E_Config_Dialog_Data *cfdata, const char *key, const char *name, const char *icon);
static CFType      *_cftype_new(const char *key, const char *name, const char *icon);
static void         _load_module(E_Module_Desktop *md, Eina_Hash *types_hash);
static Eina_Bool    _types_list_create_foreach_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata);
static int          _types_list_sort(const void *data1, const void *data2);

static void         _btn_cb_unload(void *data, void *data2);
static void         _btn_cb_load(void *data, void *data2);

E_API E_Config_Dialog *
e_int_config_modules(Evas_Object *parent EINA_UNUSED, const char *params EINA_UNUSED)
{
   E_Config_Dialog *cfd = NULL;
   E_Config_Dialog_View *v = NULL;

   if (e_config_dialog_find("E", "extensions/modules")) return NULL;

   v = E_NEW(E_Config_Dialog_View, 1);
   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.create_widgets = _basic_create;

   cfd = e_config_dialog_new(NULL, _("Module Settings"),
                             "E", "extensions/modules",
                             "preferences-plugin", 0, v, NULL);
   return cfd;
}

static void *
_create_data(E_Config_Dialog *cfd EINA_UNUSED)
{
   Eina_Hash *types_hash;
   Eina_List *mods;
   E_Module_Desktop *md;
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);

   types_hash = eina_hash_string_superfast_new(NULL);
   mods = e_module_desktop_list();
   if (!mods)
     {
        eina_hash_free(types_hash);
        return cfdata;
     }

   EINA_LIST_FREE(mods, md)
     {
        _load_module(md, types_hash);
        e_module_desktop_free(md);
     }

   eina_hash_foreach(types_hash, _types_list_create_foreach_cb, cfdata);
   eina_hash_free(types_hash);
   cfdata->types = eina_list_sort(cfdata->types, -1, _types_list_sort);

   return cfdata;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   CFType *cft;

   EINA_LIST_FREE(cfdata->types, cft)
     _cftype_free(cft);

   eina_list_free(cfdata->selected.loaded);
   eina_list_free(cfdata->selected.unloaded);
   if (cfdata->module_update) ecore_event_handler_del(cfdata->module_update);
   free(cfdata);
}

static Eina_Bool
_module_update(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Module_Update *ev)
{
   CFType *cft;
   Eina_List *l, *ll;
   CFModule *cfm;

   EINA_LIST_FOREACH(cfdata->types, l, cft)
     {
        EINA_LIST_FOREACH(cft->modules, ll, cfm)
          if (cfm->short_name == ev->name)
            {
               cfm->enabled = ev->enabled;
               _module_end_state_apply(cfm);
               return ECORE_CALLBACK_RENEW;
            }
     }

   return ECORE_CALLBACK_RENEW;
}

static Evas_Object *
_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Coord mw, mh;
   Evas_Object *of, *ol, *ob, *or;

   e_dialog_resizable_set(cfd->dia, 1);

   cfdata->evas = evas_object_evas_get(cfd->dia->win);

   of = e_widget_table_add(e_win_evas_win_get(evas), 0);

   cfdata->o_toolbar = e_widget_toolbar_add(evas, 40 * e_scale, 40 * e_scale);
   e_widget_toolbar_scrollable_set(cfdata->o_toolbar, 1);
   _fill_cat_list(cfdata);
   e_widget_table_object_append(of, cfdata->o_toolbar, 0, 0, 2, 1, 1, 1, 1, 0);

   cfdata->l_modules = e_widget_ilist_add(evas, 40 * e_scale, 40 * e_scale, NULL);
   e_widget_ilist_multi_select_set(cfdata->l_modules, EINA_TRUE);
   e_widget_ilist_go(cfdata->l_modules);
   e_widget_size_min_get(cfdata->l_modules, &mw, &mh);
   if (mw < (200 * e_scale)) mw = 200 * e_scale;
   if (mh < (100 * e_scale)) mh = 100 * e_scale;
   e_widget_size_min_set(cfdata->l_modules, mw, mh);
   e_widget_on_change_hook_set(cfdata->l_modules,
                               _widget_list_selection_changed, cfdata);
   e_widget_table_object_append(of, cfdata->l_modules, 0, 1, 2, 1, 1, 1, 1, 1);

   ol = e_widget_button_add(evas, _("Load"), NULL, _btn_cb_load, cfdata, NULL);
   cfdata->b_load = ol;
   e_widget_disabled_set(ol, 1);
   e_widget_table_object_append(of, ol, 0, 2, 1, 1, 1, 1, 1, 0);

   ol = e_widget_button_add(evas, _("Unload"), NULL, _btn_cb_unload, cfdata, NULL);
   cfdata->b_unload = ol;
   e_widget_disabled_set(ol, 1);
   e_widget_table_object_append(of, ol, 1, 2, 1, 1, 1, 1, 1, 0);

   /* Module description. */
   ob = elm_box_add(cfdata->l_modules);
   E_FILL(ob); E_EXPAND(ob);
   elm_box_horizontal_set(ob, 1);

   or = evas_object_rectangle_add(evas);
   evas_object_size_hint_min_set(or, 0, 40 * e_scale);

   ol = e_widget_textblock_add(evas);
   cfdata->o_desc = ol;
   E_FILL(ol); E_EXPAND(ol);

   elm_box_pack_end(ob, ol);
   elm_box_pack_end(ob, or);
   e_widget_table_object_append(of, ob, 0, 3, 2, 1, 1, 0, 1, 0);

   e_dialog_resizable_set(cfd->dia, 1);
   e_util_win_auto_resize_fill(cfd->dia->win);
   elm_win_center(cfd->dia->win, 1, 1);

   e_widget_focus_set(cfdata->o_toolbar, 1);
   e_widget_toolbar_item_select(cfdata->o_toolbar, 0);

   cfdata->module_update = ecore_event_handler_add(E_EVENT_MODULE_UPDATE, (Ecore_Event_Handler_Cb)_module_update, cfdata);

   return of;
}

static void
_fill_cat_list(E_Config_Dialog_Data *cfdata)
{
   Evas_Coord w, h;
   Evas_Object *icon;
   CFType *cft;
   const CFTypes *itr;

   evas_event_freeze(cfdata->evas);
   edje_freeze();

   for (itr = _types; itr->key_len > 0; itr++)
     {
        cft = _cftype_find(cfdata, itr->key, itr->name, itr->icon);
        if (!cft)
          {
             WRN("CFT MISSING!!! key(%s) name(%s) icon(%s)", itr->key, itr->name, itr->icon);
             continue;
          }
        icon = e_icon_add(cfdata->evas);
        if (icon)
          {
             if (!e_util_icon_theme_set(icon, cft->icon))
               {
                  evas_object_del(icon);
                  icon = NULL;
               }
          }

        e_widget_toolbar_item_append(cfdata->o_toolbar, icon, _(cft->name),
                                     _toolbar_select_cb, cfdata, cft);
     }

   e_widget_size_min_get(cfdata->o_toolbar, &w, &h);
   e_widget_size_min_set(cfdata->o_toolbar, w, h);

   edje_thaw();
   evas_event_thaw(cfdata->evas);
}

static void
_list_item_append(E_Config_Dialog_Data *cfdata, CFModule *cfm)
{
   Evas_Object *icon, *end;

   if (!cfm->icon)
     icon = NULL;
   else
     {
        icon = e_icon_add(cfdata->evas);
        if (icon)
          {
             if (!e_util_icon_theme_set(icon, cfm->icon))
               {
                  if (cfm->orig_path)
                    {
                       char *dir = ecore_file_dir_get(cfm->orig_path);
                       char buf[PATH_MAX];

                       snprintf(buf, sizeof(buf), "%s/%s.edj", dir, cfm->icon);
                       free(dir);

                       e_icon_file_edje_set(icon, buf, "icon");
                    }
                  else
                    {
                       evas_object_del(icon);
                       icon = NULL;
                    }
               }
          }
     }

   end = edje_object_add(cfdata->evas);
   if (end)
     {
        if (e_theme_edje_object_set(end, "base/theme/widgets",
                                    "e/widgets/ilist/toggle_end"))
          {
             cfm->end = end;
             _module_end_state_apply(cfm);
          }
        else
          {
             EINA_LOG_ERR("your theme is missing 'e/widgets/ilist/toggle_end'!");
             evas_object_del(end);
             end = NULL;
          }
     }

   e_widget_ilist_append_full(cfdata->l_modules, icon, end,
                              cfm->name, NULL, cfm, NULL);
}

static void
_toolbar_select_cb(void *data, void *data2)
{
   CFType *cft, *cft_cat;
   E_Config_Dialog_Data *cfdata;
   Eina_List *l_type;
   Evas_Coord w, h;

   cfdata = data;
   cft_cat = data2;
   if (!cfdata || !cft_cat) return;

   eina_list_free(cfdata->selected.loaded);
   eina_list_free(cfdata->selected.unloaded);
   cfdata->selected.loaded = NULL;
   cfdata->selected.unloaded = NULL;
   e_widget_disabled_set(cfdata->b_load, EINA_TRUE);
   e_widget_disabled_set(cfdata->b_unload, EINA_TRUE);
   e_widget_textblock_markup_set(cfdata->o_desc, _("No modules selected."));

   evas_event_freeze(evas_object_evas_get(cfdata->l_modules));
   edje_freeze();
   e_widget_ilist_freeze(cfdata->l_modules);
   e_widget_ilist_clear(cfdata->l_modules);

   EINA_LIST_FOREACH(cfdata->types, l_type, cft)
     {
        CFModule *cfm;
        Eina_List *l_module;

        if (strcmp(cft->key, cft_cat->key))
          continue;

        EINA_LIST_FOREACH(cft->modules, l_module, cfm)
          _list_item_append(cfdata, cfm);
     }

   e_widget_ilist_go(cfdata->l_modules);
   e_widget_size_min_get(cfdata->l_modules, &w, &h);
   e_widget_size_min_set(cfdata->l_modules, w, h);
   e_widget_ilist_thaw(cfdata->l_modules);
   edje_thaw();
   evas_event_thaw(evas_object_evas_get(cfdata->l_modules));
}

static CFModule *
_module_new(const char *short_name, const Efreet_Desktop *desk)
{
   CFModule *cfm = E_NEW(CFModule, 1);

   if (!cfm) return NULL;
   cfm->short_name = eina_stringshare_add(short_name);

   if (desk->name)
     cfm->name = eina_stringshare_add(desk->name);
   else
     cfm->name = eina_stringshare_ref(cfm->short_name);

   cfm->icon = eina_stringshare_add(desk->icon);
   cfm->comment = eina_stringshare_add(desk->comment);
   cfm->orig_path = eina_stringshare_add(desk->orig_path);
   return cfm;
}

static void
_module_free(CFModule *cfm)
{
   eina_stringshare_del(cfm->short_name);
   eina_stringshare_del(cfm->name);
   eina_stringshare_del(cfm->icon);
   eina_stringshare_del(cfm->comment);
   eina_stringshare_del(cfm->orig_path);
   E_FREE(cfm);
}

static void
_module_end_state_apply(CFModule *cfm)
{
   const char *sig;

   if (!cfm->end) return;
   sig = cfm->enabled ? "e,state,checked" : "e,state,unchecked";
   edje_object_signal_emit(cfm->end, sig, "e");
}

static CFType *
_cftype_find(E_Config_Dialog_Data *cfdata, const char *key, const char *name, const char *icon)
{
   CFType *cft;
   Eina_List *l;

   EINA_LIST_FOREACH(cfdata->types, l, cft)
     if ((!strcmp(cft->key, key)) && (!strcmp(cft->name, name)) && (!strcmp(cft->icon, icon))) return cft;
   return NULL;
}

static CFType *
_cftype_new(const char *key, const char *name, const char *icon)
{
   CFType *cft = E_NEW(CFType, 1);

   if (!cft) return NULL;
   cft->key = eina_stringshare_add(key);
   cft->name = eina_stringshare_add(name);
   cft->icon = eina_stringshare_add(icon);
   //INF("CFT NEW: key(%s) name(%s) icon(%s)", key, name, icon);
   return cft;
}

static void
_cftype_free(CFType *cft)
{
   CFModule *cfm;

   assert(cft->modules_hash == NULL); // must do it before calling this function
   EINA_LIST_FREE(cft->modules, cfm)
     _module_free(cfm);

   eina_stringshare_del(cft->key);
   eina_stringshare_del(cft->name);
   eina_stringshare_del(cft->icon);
   E_FREE(cft);
}

static CFType *
_cftype_new_from_key(const char *key)
{
   const CFTypes *itr;
   char name[1024], icon[1024];
   size_t key_len;

   if (!key) return NULL;

   key_len = strlen(key);
   for (itr = _types; itr->key_len > 0; itr++)
     {
        if (key_len != itr->key_len) continue;
        if (strcmp(itr->key, key) != 0) continue;
        return _cftype_new(itr->key, itr->name, itr->icon);
     }

   if ((key_len + 1) >= sizeof(name)) return NULL;
   if ((key_len + sizeof("enlightenment/")) >= sizeof(icon)) return NULL;

   memcpy(name, key, key_len + 1);
   name[0] = toupper(name[0]);

   snprintf(icon, sizeof(icon), "enlightenment/%s", key);
   return _cftype_new(key, name, icon);
}

static void
_load_module(E_Module_Desktop *md, Eina_Hash *types_hash)
{
   CFType *cft;
   CFModule *cfm;
   const char *type, *mod;
   Eina_Bool new_type = EINA_FALSE;

   mod = ecore_file_file_get(md->dir);
   if (md->desktop->x)
     type = eina_hash_find(md->desktop->x, "X-Enlightenment-ModuleType");
   else
     type = NULL;
   if (!type) type = "utils";  // todo: warn?

   cft = eina_hash_find(types_hash, type);
   if (cft)
     {
        new_type = EINA_FALSE;
        if ((cft->modules_hash) &&
            (eina_hash_find(cft->modules_hash, mod)))
          return;
     }
   else
     {
        cft = _cftype_new_from_key(type);
        if (cft) new_type = EINA_TRUE;
        else return;
     }

   cfm = _module_new(mod, md->desktop);
   if (!cfm)
     {
        if (new_type) _cftype_free(cft);
        return;
     }

   if (!cft->modules_hash)
     cft->modules_hash = eina_hash_string_superfast_new(NULL);
   eina_hash_direct_add(cft->modules_hash, cfm->short_name, cfm);
   cft->modules = eina_list_append(cft->modules, cfm);
   cfm->module = e_module_find(mod);
   if (cfm->module)
     cfm->enabled = e_module_enabled_get(cfm->module);
   else
     cfm->enabled = 0;

   if (new_type)
     eina_hash_direct_add(types_hash, cft->key, cft);
}

static int
_modules_list_sort(const void *data1, const void *data2)
{
   const CFModule *m1 = data1, *m2 = data2;
   return strcmp(m1->name, m2->name);
}

static Eina_Bool
_types_list_create_foreach_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata)
{
   E_Config_Dialog_Data *cfdata = fdata;
   CFType *cft = data;

   // otherwise it should not be here
   assert(cft->modules);
   assert(cft->modules_hash);

   eina_hash_free(cft->modules_hash);
   cft->modules_hash = NULL;

   cft->modules = eina_list_sort(cft->modules, -1, _modules_list_sort);
   cfdata->types = eina_list_append(cfdata->types, cft);
   // TODO be paranoid about list append failure, otherwise leaks memory
   return EINA_TRUE;
}

static int
_types_list_sort(const void *data1, const void *data2)
{
   const CFType *t1 = data1, *t2 = data2;
   return strcmp(t1->name, t2->name);
}

static void
_widget_list_selection_changed(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   const Eina_List *l;
   const E_Ilist_Item *it;
   CFModule *cfm = NULL;
   const char *description;

   cfdata->selected.loaded = eina_list_free(cfdata->selected.loaded);
   cfdata->selected.unloaded = eina_list_free(cfdata->selected.unloaded);

   EINA_LIST_FOREACH(e_widget_ilist_selected_items_get(cfdata->l_modules), l, it)
     {
        cfm = e_widget_ilist_item_data_get(it);

        if (cfm->enabled)
          {
             cfdata->selected.loaded =
               eina_list_append(cfdata->selected.loaded, cfm);
             e_widget_disabled_set(cfdata->b_unload, 0);
             e_widget_disabled_set(cfdata->b_load, 1);
          }
        else
          {
             cfdata->selected.unloaded =
               eina_list_append(cfdata->selected.unloaded, cfm);
             e_widget_disabled_set(cfdata->b_load, 0);
             e_widget_disabled_set(cfdata->b_unload, 1);
          }
     }

   if ((cfm) && (eina_list_count(cfdata->selected.loaded) + eina_list_count(cfdata->selected.unloaded) == 1))
     description = cfm->comment;
   else if (eina_list_count(cfdata->selected.loaded) + eina_list_count(cfdata->selected.unloaded) > 1)
     description = _("More than one module selected.");
   else
     description = _("No modules selected.");

   e_widget_textblock_markup_set(cfdata->o_desc, description);
}

static void
_btn_cb_unload(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   CFModule *cfm;

   EINA_LIST_FREE(cfdata->selected.loaded, cfm)
     {
        if (!cfm->module)
          cfm->module = e_module_find(cfm->short_name);

        if (cfm->module)
          {
             e_module_disable(cfm->module);
             cfm->enabled = e_module_enabled_get(cfm->module);
          }

        _module_end_state_apply(cfm);
        cfdata->selected.unloaded = eina_list_append(cfdata->selected.unloaded, cfm);
     }

   e_widget_disabled_set(cfdata->b_unload, 1);
   e_widget_disabled_set(cfdata->b_load, 0);
}

static void
_btn_cb_load(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   CFModule *cfm;

   EINA_LIST_FREE(cfdata->selected.unloaded, cfm)
     {
        if (!cfm->module)
          cfm->module = e_module_find(cfm->short_name);
        if (!cfm->module)
          cfm->module = e_module_new(cfm->short_name);

        if (cfm->module)
          {
             e_module_enable(cfm->module);
             cfm->enabled = e_module_enabled_get(cfm->module);
          }

        _module_end_state_apply(cfm);
        cfdata->selected.loaded = eina_list_append(cfdata->selected.loaded, cfm);
     }

   e_widget_disabled_set(cfdata->b_load, 1);
   e_widget_disabled_set(cfdata->b_unload, 0);
}

#include "e.h"

typedef struct _CFGadget CFGadget;
struct _CFGadget
{
   const char *name, *id;
};

struct _E_Config_Dialog_Data
{
   E_Gadcon            *gc;
   E_Gadcon_Site        site;

   const char          *sel;

   struct
   {
      Evas_Object *o_list;
   } basic, advanced;
   Evas_Object         *class_list;
   Evas_Object         *o_add, *o_del;
   Ecore_Event_Handler *gcc_add;
   Ecore_Event_Handler *gcc_del;
   Ecore_Event_Handler *cc_del;
   Ecore_Event_Handler *cc_add;
   Ecore_Timer         *load_timer;
   Eina_Hash           *gadget_hash;
};

/* local function prototypes */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _fill_data(E_Config_Dialog_Data *cfdata);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Eina_Bool    _free_gadgets(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED);
static Evas_Object *_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Evas_Object *_advanced_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);
static Eina_Bool    _cb_load_timer(void *data);
static void         _fill_list(E_Config_Dialog_Data *cfdata);
static void         _fill_list_advanced(E_Config_Dialog_Data *cfdata, Eina_Bool, Eina_Bool);
static void         _cb_list_selected(void *data);
static void         _cb_add(void *data, void *data2 EINA_UNUSED);
static void         _cb_del(void *data, void *data2 EINA_UNUSED);
static CFGadget    *_search_hash(E_Config_Dialog_Data *cfdata, const char *name);
static Eina_Bool    _cb_cc_add(E_Config_Dialog_Data *cfdata, int type, E_Event_Gadcon_Client_Class_Add *ev);
static Eina_Bool    _cb_cc_del(E_Config_Dialog_Data *cfdata, int type, E_Event_Gadcon_Client_Class_Add *ev);
static Eina_Bool    _cb_gcc_add(E_Config_Dialog_Data *cfdata, int type, E_Event_Gadcon_Client_Add *ev);
static Eina_Bool    _cb_gcc_del(E_Config_Dialog_Data *cfdata, int type, E_Event_Gadcon_Client_Del *ev);

/* local function prototypes */
static void
_create_dialog(E_Gadcon *gc, const char *title, E_Gadcon_Site site)
{
   E_Config_Dialog_View *v;

   if (gc->config_dialog)
     {
        e_client_activate(e_win_client_get(gc->config_dialog->dia->win), 1);
        return;
     }
   if (!(v = E_NEW(E_Config_Dialog_View, 1))) return;

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.create_widgets = _basic_create;
   v->advanced.create_widgets = _advanced_create;

   gc->config_dialog =
     e_config_dialog_new(NULL, title, "E", "_gadcon_config_dialog",
                         "preferences-desktop-shelf", 0, v, gc);
   if (site) gc->config_dialog->cfdata->site = site;
   elm_win_center(gc->config_dialog->dia->win, 1, 1);
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->gadget_hash = eina_hash_string_superfast_new(NULL);
   cfdata->gc = cfd->data;
   if (cfdata->gc->shelf)
     cfdata->site = E_GADCON_SITE_SHELF;
   else if (cfdata->gc->toolbar)
     cfdata->site = E_GADCON_SITE_EFM_TOOLBAR;
   else
     cfdata->site = E_GADCON_SITE_UNKNOWN;
   _fill_data(cfdata);

   cfdata->gcc_add = ecore_event_handler_add(E_EVENT_GADCON_CLIENT_ADD,
                                             (Ecore_Event_Handler_Cb)_cb_gcc_add, cfdata);
   cfdata->gcc_del = ecore_event_handler_add(E_EVENT_GADCON_CLIENT_DEL,
                                             (Ecore_Event_Handler_Cb)_cb_gcc_del, cfdata);

   cfdata->cc_add = ecore_event_handler_add(E_EVENT_GADCON_CLIENT_CLASS_ADD,
                                            (Ecore_Event_Handler_Cb)_cb_cc_add, cfdata);
   cfdata->cc_del = ecore_event_handler_add(E_EVENT_GADCON_CLIENT_CLASS_DEL,
                                            (Ecore_Event_Handler_Cb)_cb_cc_del, cfdata);
   return cfdata;
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   Eina_List *l;
   E_Config_Gadcon_Client *cf_gcc;

   EINA_LIST_FOREACH(cfdata->gc->cf->clients, l, cf_gcc)
     {
        CFGadget *gad;

        if (!cf_gcc->id) continue;
        gad = E_NEW(CFGadget, 1);
        gad->name = eina_stringshare_add(cf_gcc->name);
        gad->id = eina_stringshare_add(cf_gcc->id);
        eina_hash_direct_add(cfdata->gadget_hash, gad->id, gad);
     }
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   if (cfdata->gcc_add) ecore_event_handler_del(cfdata->gcc_add);
   if (cfdata->gcc_del) ecore_event_handler_del(cfdata->gcc_del);
   if (cfdata->cc_add) ecore_event_handler_del(cfdata->cc_add);
   if (cfdata->cc_del) ecore_event_handler_del(cfdata->cc_del);

   if (cfdata->load_timer) ecore_timer_del(cfdata->load_timer);

   if (cfdata->gadget_hash)
     {
        eina_hash_foreach(cfdata->gadget_hash, _free_gadgets, NULL);
        eina_hash_free(cfdata->gadget_hash);
     }

   cfdata->gc->config_dialog = NULL;
   free(cfdata);
}

static Eina_Bool
_free_gadgets(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   CFGadget *gadget;

   if (!(gadget = data)) return EINA_FALSE;
   if (gadget->name) eina_stringshare_del(gadget->name);
   if (gadget->id) eina_stringshare_del(gadget->id);
   E_FREE(gadget);

   return EINA_TRUE;
}

static void
_cb_list_selected(void *data)
{
   E_Config_Dialog_Data *cfdata;
   const E_Ilist_Item *it;
   const Eina_List *l;
   unsigned int loaded = 0;

   if (!(cfdata = data)) return;
   EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->basic.o_list), l, it)
     {
        const char *name;

        if ((!it->selected) || (it->header)) continue;
        if (!(name = e_widget_ilist_item_value_get(it))) continue;
        if (_search_hash(cfdata, name)) loaded++;
     }
   e_widget_disabled_set(cfdata->o_add, EINA_FALSE);
   e_widget_disabled_set(cfdata->o_del, !loaded);
}

static void
_list_item_del_advanced(E_Config_Dialog_Data *cfdata, E_Gadcon_Client *gcc)
{
   E_Ilist_Item *ili;
   const Eina_List *l;
   int x = 0;

   if (!gcc->cf)
     {
        eina_hash_foreach(cfdata->gadget_hash, _free_gadgets, NULL);
        eina_hash_free(cfdata->gadget_hash);
        cfdata->gadget_hash = eina_hash_string_superfast_new(NULL);
        _fill_data(cfdata);
        _fill_list_advanced(cfdata, EINA_TRUE, EINA_FALSE);
        return;
     }
   EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->advanced.o_list), l, ili)
     {
        if (e_widget_ilist_item_value_get(ili) == gcc->cf->id)
          {
             e_widget_ilist_remove_num(cfdata->advanced.o_list, x);
             break;
          }
        x++;
     }
}

static void
_list_item_del(E_Config_Dialog_Data *cfdata EINA_UNUSED, Evas_Object *list, const E_Gadcon_Client_Class *cc)
{
   E_Ilist_Item *ili;
   const Eina_List *l;
   int x = 0;

   EINA_LIST_FOREACH(e_widget_ilist_items_get(list), l, ili)
     {
        const char *name;

        name = e_widget_ilist_item_value_get(ili);
        if (!strcmp(name, cc->name))
          {
             e_widget_ilist_remove_num(list, x);
             break;
          }
        x++;
     }
}

static void
_list_item_add_advanced(E_Config_Dialog_Data *cfdata, E_Gadcon_Client *gcc, E_Config_Gadcon_Client *cf_gcc)
{
   Evas_Object *icon = NULL, *end;
   Evas *evas;

   if (gcc && (gcc->gadcon != cfdata->gc)) return;
   if (!gcc)
     {
        Eina_List *l;
        EINA_LIST_FOREACH(cfdata->gc->clients, l, gcc)
          if (gcc->cf == cf_gcc) break;
        if (gcc && (gcc->cf != cf_gcc)) gcc = NULL;
     }
   else
     {
        const Eina_List *l;
        E_Ilist_Item *ili;

        EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->advanced.o_list), l, ili)
          {
             if (e_widget_ilist_item_label_get(ili) == cf_gcc->id)
               {
                  end = e_widget_ilist_item_end_get(ili);
                  edje_object_signal_emit(end, "e,state,checked", "e");
                  return;
               }
          }
     }
   evas = evas_object_evas_get(cfdata->advanced.o_list);
   if (gcc && gcc->client_class->func.icon) icon = gcc->client_class->func.icon(gcc->client_class, evas);

   end = edje_object_add(evas);
   if (!e_theme_edje_object_set(end, "base/theme/widgets",
                                "e/widgets/ilist/toggle_end"))
     {
        evas_object_del(end);
        end = NULL;
     }

   if (gcc)
     edje_object_signal_emit(end, "e,state,checked", "e");
   else
     edje_object_signal_emit(end, "e,state,unchecked", "e");

   e_widget_ilist_append_full(cfdata->advanced.o_list, icon, end, cf_gcc->id,
                              NULL, cfdata, cf_gcc->id);
   if (!e_widget_ilist_count(cfdata->advanced.o_list)) return;
   e_widget_disabled_set(cfdata->o_del, 0);
   e_widget_ilist_selected_set(cfdata->advanced.o_list, 0);
}

static void
_list_item_class_add(E_Config_Dialog_Data *cfdata, const E_Gadcon_Client_Class *cc)
{
   Evas_Object *icon = NULL;
   const char *lbl = NULL;
   Evas *evas;

   evas = evas_object_evas_get(cfdata->class_list);
   if ((cc->func.is_site) && (!cc->func.is_site(cfdata->site)))
     return;
   if (cc->func.label) lbl = cc->func.label(cc);
   if (!lbl) lbl = cc->name;
   if (cc->func.icon) icon = cc->func.icon(cc, evas);

   e_widget_ilist_append_full(cfdata->class_list, icon, NULL, lbl,
                              NULL, cfdata, cc->name);
}

static void
_list_item_add(E_Config_Dialog_Data *cfdata, const E_Gadcon_Client_Class *cc)
{
   Evas_Object *icon = NULL, *end;
   const char *lbl = NULL;
   Evas *evas;

   evas = evas_object_evas_get(cfdata->basic.o_list);
   if ((cc->func.is_site) && (!cc->func.is_site(cfdata->site)))
     return;
   if (cc->func.label) lbl = cc->func.label(cc);
   if (!lbl) lbl = cc->name;
   if (cc->func.icon) icon = cc->func.icon(cc, evas);

   end = edje_object_add(evas);
   if (!e_theme_edje_object_set(end, "base/theme/widgets",
                                "e/widgets/ilist/toggle_end"))
     {
        evas_object_del(end);
        end = NULL;
     }

   if (_search_hash(cfdata, cc->name))
     {
        if (end) edje_object_signal_emit(end, "e,state,checked", "e");
     }
   else
     {
        if (end) edje_object_signal_emit(end, "e,state,unchecked", "e");
     }

   e_widget_ilist_append_full(cfdata->basic.o_list, icon, end, lbl,
                              _cb_list_selected, cfdata, cc->name);
}

static void
_fill_list_advanced(E_Config_Dialog_Data *cfdata, Eina_Bool clients, Eina_Bool classes)
{
   Eina_List *l;
   E_Config_Gadcon_Client *cf_gcc;
   const E_Gadcon_Client_Class *cc;
   Evas *evas;
   int mw;

   while (clients)
     {
        evas = evas_object_evas_get(cfdata->advanced.o_list);
        evas_event_freeze(evas);
        edje_freeze();
        e_widget_ilist_freeze(cfdata->advanced.o_list);
        e_widget_ilist_clear(cfdata->advanced.o_list);

        EINA_LIST_FOREACH(cfdata->gc->cf->clients, l, cf_gcc)
          _list_item_add_advanced(cfdata, NULL, cf_gcc);

        e_widget_ilist_go(cfdata->advanced.o_list);
        e_widget_size_min_get(cfdata->advanced.o_list, &mw, NULL);
        if (mw < (200 * e_scale)) mw = (200 * e_scale);
        e_widget_size_min_set(cfdata->advanced.o_list, mw, (160 * e_scale));
        e_widget_ilist_thaw(cfdata->advanced.o_list);
        edje_thaw();
        evas_event_thaw(evas);
        if (e_widget_ilist_count(cfdata->advanced.o_list))
          e_widget_ilist_selected_set(cfdata->advanced.o_list, 0);
        else
          e_widget_disabled_set(cfdata->o_del, 1);
        break;
     }
///////////////
   while (classes)
     {
        evas = evas_object_evas_get(cfdata->class_list);
        evas_event_freeze(evas);
        edje_freeze();
        e_widget_ilist_freeze(cfdata->class_list);
        e_widget_ilist_clear(cfdata->class_list);

        EINA_LIST_FOREACH(e_gadcon_provider_list(), l, cc)
          _list_item_class_add(cfdata, cc);

        e_widget_ilist_go(cfdata->class_list);
        e_widget_size_min_get(cfdata->class_list, &mw, NULL);
        if (mw < (200 * e_scale)) mw = (200 * e_scale);
        e_widget_size_min_set(cfdata->class_list, mw, (160 * e_scale));
        e_widget_ilist_thaw(cfdata->class_list);
        edje_thaw();
        evas_event_thaw(evas);
        e_widget_ilist_selected_set(cfdata->class_list, 0);
        e_widget_disabled_set(cfdata->o_add, 0);
        break;
     }
}

static void
_fill_list(E_Config_Dialog_Data *cfdata)
{
   Eina_List *l;
   E_Gadcon_Client_Class *cc;
   Evas *evas;
   int mw;

   evas = evas_object_evas_get(cfdata->basic.o_list);
   evas_event_freeze(evas);
   edje_freeze();
   e_widget_ilist_freeze(cfdata->basic.o_list);
   e_widget_ilist_clear(cfdata->basic.o_list);

   EINA_LIST_FOREACH(e_gadcon_provider_list(), l, cc)
     _list_item_add(cfdata, cc);

   e_widget_ilist_go(cfdata->basic.o_list);
   e_widget_size_min_get(cfdata->basic.o_list, &mw, NULL);
   if (mw < (200 * e_scale)) mw = (200 * e_scale);
   e_widget_size_min_set(cfdata->basic.o_list, mw, (160 * e_scale));
   e_widget_ilist_thaw(cfdata->basic.o_list);
   edje_thaw();
   evas_event_thaw(evas);
}

static void
_cb_add_advanced(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;
   E_Config_Gadcon_Client *cf_gcc;
   CFGadget *gad;

   if (!(cfdata = data)) return;
   cf_gcc = e_gadcon_client_config_new(cfdata->gc, e_widget_ilist_selected_value_get(cfdata->class_list));

   gad = E_NEW(CFGadget, 1);
   gad->name = eina_stringshare_add(cf_gcc->name);
   gad->id = eina_stringshare_add(cf_gcc->id);
   eina_hash_direct_add(cfdata->gadget_hash, gad->id, gad);

   e_gadcon_unpopulate(cfdata->gc);
   if (cfdata->gc->custom)
     e_gadcon_custom_populate_request(cfdata->gc);
   else
     e_gadcon_populate(cfdata->gc);
   e_config_save_queue();
   e_widget_ilist_selected_set(cfdata->class_list, 0);
}

static void
_cb_add(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;
   const E_Ilist_Item *it;
   const Eina_List *l;
   int update = 0;

   if (!(cfdata = data)) return;
   EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->basic.o_list), l, it)
     {
        E_Config_Gadcon_Client *cf_gcc;
        CFGadget *gad;
        Evas_Object *end;
        const char *name;

        if ((!it->selected) || (it->header)) continue;
        if (!(name = e_widget_ilist_item_value_get(it))) continue;
        if (!(cf_gcc = e_gadcon_client_config_new(cfdata->gc, name))) continue;
        if ((end = e_widget_ilist_item_end_get(it)))
          edje_object_signal_emit(end, "e,state,checked", "e");

        gad = E_NEW(CFGadget, 1);
        gad->name = eina_stringshare_add(cf_gcc->name);
        gad->id = eina_stringshare_add(cf_gcc->id);
        eina_hash_direct_add(cfdata->gadget_hash, gad->id, gad);

        update = 1;
     }
   if (update)
     {
        if (cfdata->gc->custom)
          e_gadcon_custom_populate_request(cfdata->gc);
        else
          {
             e_gadcon_unpopulate(cfdata->gc);
             e_gadcon_populate(cfdata->gc);
          }
        e_config_save_queue();
     }
   e_widget_ilist_unselect(cfdata->basic.o_list);
   e_widget_disabled_set(cfdata->o_add, EINA_TRUE);
   e_widget_disabled_set(cfdata->o_del, EINA_TRUE);
}

static void
_cb_del_advanced(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata = data;
   E_Config_Gadcon_Client *cf_gcc;
   const Eina_List *l, *sel, *ll, *lll, *llll;
   E_Ilist_Item *ili;
   int x = 0, refresh = 0;

   sel = e_widget_ilist_items_get(cfdata->advanced.o_list);
   EINA_LIST_FOREACH_SAFE(sel, ll, lll, ili)
     {
        if (!ili) continue;
        if (!ili->selected) continue;
        x = 0;
        EINA_LIST_FOREACH_SAFE(cfdata->gc->cf->clients, l, llll, cf_gcc)
          {
             if (cf_gcc->id == e_widget_ilist_item_value_get(ili))
               {
                  CFGadget *gad;
                  E_Gadcon_Client *gcc;

                  if ((gad = eina_hash_find(cfdata->gadget_hash, cf_gcc->id)))
                    {
                       eina_hash_del(cfdata->gadget_hash, gad->id, gad);
                       if (gad->name) eina_stringshare_del(gad->name);
                       if (gad->id) eina_stringshare_del(gad->id);
                       E_FREE(gad);
                    }
                  EINA_LIST_FOREACH(cfdata->gc->clients, l, gcc)
                    {
                       if (gcc->cf != cf_gcc) continue;
                       gcc->cf = NULL;
                       if (gcc->gadcon->custom && gcc->gadcon->editing)
                         e_object_unref(E_OBJECT(gcc));
                       e_object_del(E_OBJECT(gcc));
                       break;
                    }
                  refresh = 1;
                  e_gadcon_client_config_del(cfdata->gc->cf, cf_gcc);
                  e_config_save_queue();
                  e_widget_ilist_remove_num(cfdata->advanced.o_list, x);
                  x++;
                  break;
               }
          }
     }
   if (refresh && (!cfdata->gc->custom))
     {
        e_gadcon_unpopulate(cfdata->gc);
        e_gadcon_populate(cfdata->gc);
     }
   e_widget_ilist_selected_set(cfdata->advanced.o_list, 0);
}

static void
_cb_del(void *data, void *data2 EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;
   const E_Ilist_Item *it;
   const Eina_List *l;
   int update = 0;

   if (!(cfdata = data)) return;
   EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->basic.o_list), l, it)
     {
        E_Config_Gadcon_Client *cf_gcc;
        E_Gadcon_Client *gcc;
        Eina_List *cl;
        Evas_Object *end;
        const char *name;

        if ((!it->selected) || (it->header)) continue;
        name = e_widget_ilist_item_value_get(it);

        /* remove from actual gadget container */
        EINA_LIST_FOREACH(cfdata->gc->cf->clients, cl, cf_gcc)
          {
             CFGadget *gad;

             if (name != cf_gcc->name) continue;

             /* remove from gadget hash if exists */
             if ((gad = eina_hash_find(cfdata->gadget_hash, cf_gcc->id)))
               {
                  eina_hash_del(cfdata->gadget_hash, gad->id, gad);
                  if (gad->name) eina_stringshare_del(gad->name);
                  if (gad->id) eina_stringshare_del(gad->id);
                  E_FREE(gad);
               }

             /* set ilist end toggle if we don't have any more in the hash */
             if (!_search_hash(cfdata, name))
               {
                  if ((end = e_widget_ilist_item_end_get(it)))
                    edje_object_signal_emit(end, "e,state,unchecked", "e");
               }
             EINA_LIST_FOREACH(cfdata->gc->clients, cl, gcc)
               {
                  if (gcc->cf != cf_gcc) continue;
                  gcc->cf = NULL;
                  if (gcc->gadcon->custom && gcc->gadcon->editing)
                    e_object_unref(E_OBJECT(gcc));
                  e_object_del(E_OBJECT(gcc));
                  break;
               }

             /* remove from gadget container */
             e_gadcon_client_config_del(cfdata->gc->cf, cf_gcc);

             update = 1;
             break;
          }
     }
   if (update)
     {
        if (!cfdata->gc->custom)
          {
             if (cfdata->gc->shelf)
               e_shelf_hide(cfdata->gc->shelf);
             e_gadcon_unpopulate(cfdata->gc);
             e_gadcon_populate(cfdata->gc);
             if (cfdata->gc->shelf)
               e_shelf_show(cfdata->gc->shelf);
          }
        e_config_save_queue();
     }
   e_widget_ilist_unselect(cfdata->basic.o_list);
   e_widget_disabled_set(cfdata->o_add, EINA_TRUE);
   e_widget_disabled_set(cfdata->o_del, EINA_TRUE);
}

static CFGadget *
_search_hash(E_Config_Dialog_Data *cfdata, const char *name)
{
   Eina_Iterator *it;
   CFGadget *gad, *ret = NULL;

   if (!name) return NULL;
   if (!(it = eina_hash_iterator_data_new(cfdata->gadget_hash)))
     return NULL;
   EINA_ITERATOR_FOREACH(it, gad)
     {
        if ((gad->name) && (!strcmp(gad->name, name)))
          {
             ret = gad;
             break;
          }
     }
   eina_iterator_free(it);
   return ret;
}

static Eina_Bool
_cb_gcc_del(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Gadcon_Client_Del *ev)
{
   if (cfdata->advanced.o_list)
     _list_item_del_advanced(cfdata, ev->gcc);
   else
     {
        eina_hash_foreach(cfdata->gadget_hash, _free_gadgets, NULL);
        eina_hash_free(cfdata->gadget_hash);
        cfdata->gadget_hash = eina_hash_string_superfast_new(NULL);
        _fill_data(cfdata);
        _fill_list(cfdata);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_cb_gcc_add(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Gadcon_Client_Add *ev)
{
   if (cfdata->advanced.o_list)
     _list_item_add_advanced(cfdata, ev->gcc, ev->gcc->cf);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_cb_cc_del(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Gadcon_Client_Class_Add *ev)
{
   if (cfdata->basic.o_list)
     _list_item_del(cfdata, cfdata->basic.o_list, ev->cc);
   else
     _list_item_del(cfdata, cfdata->class_list, ev->cc);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_cb_cc_add(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Gadcon_Client_Class_Add *ev)
{
   if (cfdata->basic.o_list)
     _list_item_add(cfdata, ev->cc);
   else
     _list_item_class_add(cfdata, ev->cc);
   return ECORE_CALLBACK_PASS_ON;
}

static Evas_Object *
_advanced_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *ot, *otb;
   int mw;

   cfdata->basic.o_list = NULL;
   otb = e_widget_toolbook_add(evas, 48 * e_scale, 48 * e_scale);
   ////////////////////////////////////////////////////////////
   ot = e_widget_table_add(e_win_evas_win_get(evas), EINA_FALSE);

   cfdata->advanced.o_list =
     e_widget_ilist_add(evas, (24 * e_scale), (24 * e_scale), &cfdata->sel);
   e_widget_ilist_multi_select_set(cfdata->advanced.o_list, EINA_TRUE);
   e_widget_size_min_get(cfdata->advanced.o_list, &mw, NULL);
   if (mw < (200 * e_scale)) mw = (200 * e_scale);
   e_widget_size_min_set(cfdata->advanced.o_list, mw, (160 * e_scale));
   e_widget_table_object_append(ot, cfdata->advanced.o_list,
                                0, 0, 1, 1, 1, 1, 1, 1);

   cfdata->o_del =
     e_widget_button_add(evas, _("Remove"), "list-remove", _cb_del_advanced, cfdata, NULL);
   e_widget_disabled_set(cfdata->o_del, EINA_TRUE);
   e_widget_table_object_append(ot, cfdata->o_del, 0, 1, 1, 1, 1, 1, 1, 0);
   /* e_widget_list_object_append(ot, cfdata->o_del, 1, 0, 0.5); */

   e_widget_toolbook_page_append(otb, NULL, _("Loaded Gadgets"), ot, 1, 1, 1, 1, 0.5, 0.0);
   ////////////////////////////////////////////////////////////
//   ot = e_widget_list_add(evas, 0, 0);
   ot = e_widget_table_add(e_win_evas_win_get(evas), EINA_FALSE);

   cfdata->class_list =
     e_widget_ilist_add(evas, (24 * e_scale), (24 * e_scale), NULL);
   e_widget_size_min_get(cfdata->class_list, &mw, NULL);
   if (mw < (200 * e_scale)) mw = (200 * e_scale);
   e_widget_size_min_set(cfdata->class_list, mw, (160 * e_scale));
   e_widget_table_object_append(ot, cfdata->class_list,
                                0, 0, 1, 1, 1, 1, 1, 1);
//   e_widget_list_object_append(ot, cfdata->class_list, 1, 1, 0.5);

   cfdata->o_add =
     e_widget_button_add(evas, _("Add"), "list-add", _cb_add_advanced, cfdata, NULL);
   e_widget_disabled_set(cfdata->o_add, EINA_TRUE);
   e_widget_table_object_append(ot, cfdata->o_add, 0, 1, 1, 1, 1, 1, 1, 0);
//   e_widget_list_object_append(ot, cfdata->o_add, 1, 0, 0.5);

   e_widget_toolbook_page_append(otb, NULL, _("Available Gadgets"), ot, 1, 1, 1, 1, 0.5, 0.0);
   ////////////////////////////////////////////////////////////
   e_widget_toolbook_page_show(otb, 0);
   if (cfdata->load_timer) ecore_timer_del(cfdata->load_timer);
   cfdata->load_timer = ecore_timer_loop_add(0.01, _cb_load_timer, cfdata);

   elm_win_center(cfd->dia->win, 1, 1);

   return otb;
}

static Evas_Object *
_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *ot;
   int mw;

   cfdata->advanced.o_list = cfdata->class_list = NULL;
   ot = e_widget_table_add(e_win_evas_win_get(evas), 0);

   cfdata->basic.o_list = e_widget_ilist_add(evas, 24, 24, NULL);
   e_widget_ilist_multi_select_set(cfdata->basic.o_list, EINA_TRUE);
   e_widget_size_min_get(cfdata->basic.o_list, &mw, NULL);
   if (mw < (200 * e_scale)) mw = (200 * e_scale);
   e_widget_size_min_set(cfdata->basic.o_list, mw, (160 * e_scale));
   e_widget_table_object_append(ot, cfdata->basic.o_list, 0, 0, 2, 1, 1, 1, 1, 1);

   cfdata->o_add =
     e_widget_button_add(evas, _("Add"), "list-add", _cb_add, cfdata, NULL);
   e_widget_disabled_set(cfdata->o_add, EINA_TRUE);
   e_widget_table_object_append(ot, cfdata->o_add, 0, 1, 1, 1, 1, 1, 1, 0);

   cfdata->o_del =
     e_widget_button_add(evas, _("Remove"), "list-remove", _cb_del, cfdata, NULL);
   e_widget_disabled_set(cfdata->o_del, EINA_TRUE);
   e_widget_table_object_append(ot, cfdata->o_del, 1, 1, 1, 1, 1, 1, 1, 0);

   if (cfdata->load_timer) ecore_timer_del(cfdata->load_timer);
   cfdata->load_timer = ecore_timer_loop_add(0.01, _cb_load_timer, cfdata);

   elm_win_center(cfd->dia->win, 1, 1);

   return ot;
}

static Eina_Bool
_cb_load_timer(void *data)
{
   E_Config_Dialog_Data *cfdata;

   if (!(cfdata = data)) return ECORE_CALLBACK_RENEW;
   if (cfdata->basic.o_list)
     _fill_list(cfdata);
   else
     _fill_list_advanced(cfdata, EINA_TRUE, EINA_TRUE);
   cfdata->load_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

E_API void
e_int_gadcon_config_shelf(E_Gadcon *gc)
{
   _create_dialog(gc, _("Shelf Contents"), 0);
}

E_API void
e_int_gadcon_config_toolbar(E_Gadcon *gc)
{
   _create_dialog(gc, _("Toolbar Contents"), 0);
}

E_API void
e_int_gadcon_config_hook(E_Gadcon *gc, const char *name, E_Gadcon_Site site)
{
   _create_dialog(gc, name, site);
}

#include "e.h"

static Ecore_Exe *_e_intl_input_method_exec = NULL;
static Ecore_Event_Handler *_e_intl_exit_handler = NULL;

static char *_e_intl_orig_lang = NULL;
static char *_e_intl_orig_language = NULL;
static char *_e_intl_language = NULL;

static char *_e_intl_language_alias = NULL;

static char *_e_intl_orig_xmodifiers = NULL;
static char *_e_intl_orig_qt_im_module = NULL;
static char *_e_intl_orig_gtk_im_module = NULL;
static char *_e_intl_orig_ecore_imf_module = NULL;

static const char *_e_intl_imc_personal_path = NULL;
static const char *_e_intl_imc_system_path = NULL;

#define E_EXE_STOP(EXE)     if (EXE) { ecore_exe_terminate(EXE); ecore_exe_free(EXE); EXE = NULL; }
#define E_EXE_IS_VALID(EXE) (!((!EXE) || (EXE[0] == 0)))

/* All locale parts */
#define E_INTL_LOC_ALL         E_INTL_LOC_LANG | \
  E_INTL_LOC_REGION |                            \
  E_INTL_LOC_CODESET |                           \
  E_INTL_LOC_MODIFIER

/* Locale parts which are significant when Validating */
#define E_INTL_LOC_SIGNIFICANT E_INTL_LOC_LANG | \
  E_INTL_LOC_REGION |                            \
  E_INTL_LOC_CODESET

/* Language Setting and Listing */
static char      *_e_intl_language_path_find(char *language);
static Eina_List *_e_intl_language_dir_scan(const char *dir);
static int        _e_intl_language_list_find(Eina_List *language_list, char *language);

/* Locale Validation and Discovery */
static Eina_Hash *_e_intl_locale_alias_hash_get(void);
static char      *_e_intl_locale_alias_get(const char *language);
static Eina_List *_e_intl_locale_system_locales_get(void);
static Eina_List *_e_intl_locale_search_order_get(const char *locale);
static int        _e_intl_locale_validate(const char *locale);
static void       _e_intl_locale_hash_free(Eina_Hash *language_hash);
static Eina_Bool  _e_intl_locale_hash_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);

/* Input Method Configuration and Management */
static Eina_Bool  _e_intl_cb_exit(void *data, int type, void *event);
static Eina_List *_e_intl_imc_dir_scan(const char *dir);

EINTERN int
e_intl_init(void)
{
   char *s;

   e_intl_data_init();

   if ((s = getenv("LANG"))) _e_intl_orig_lang = strdup(s);
   if ((s = getenv("LANGUAGE"))) _e_intl_orig_language = strdup(s);

   if ((s = getenv("GTK_IM_MODULE"))) _e_intl_orig_gtk_im_module = strdup(s);
   if ((s = getenv("QT_IM_MODULE"))) _e_intl_orig_qt_im_module = strdup(s);
   if ((s = getenv("XMODIFIERS"))) _e_intl_orig_xmodifiers = strdup(s);
   if ((s = getenv("ECORE_IMF_MODULE"))) _e_intl_orig_ecore_imf_module = strdup(s);

   return 1;
}

EINTERN int
e_intl_shutdown(void)
{
   E_FREE(_e_intl_language);
   E_FREE(_e_intl_orig_lang);
   E_FREE(_e_intl_orig_language);

   E_FREE(_e_intl_orig_gtk_im_module);
   E_FREE(_e_intl_orig_qt_im_module);
   E_FREE(_e_intl_orig_xmodifiers);
   E_FREE(_e_intl_orig_ecore_imf_module);

   if (_e_intl_imc_personal_path)
     eina_stringshare_del(_e_intl_imc_personal_path);
   if (_e_intl_imc_system_path)
     eina_stringshare_del(_e_intl_imc_system_path);

   e_intl_data_shutdown();

   return 1;
}

/* Setup configuration settings and start services */
EINTERN int
e_intl_post_init(void)
{
   if ((e_config->language) && (e_config->language[0] != 0))
     e_intl_language_set(e_config->language);
   else
     e_intl_language_set(NULL);

   if ((e_config->input_method) && (e_config->input_method[0] != 0))
     e_intl_input_method_set(e_config->input_method);

   _e_intl_exit_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                                                  _e_intl_cb_exit, NULL);
   return 1;
}

EINTERN int
e_intl_post_shutdown(void)
{
   if (_e_intl_exit_handler)
     {
        ecore_event_handler_del(_e_intl_exit_handler);
        _e_intl_exit_handler = NULL;
     }

   e_intl_input_method_set(NULL);

   e_intl_language_set(NULL);
   E_FREE(_e_intl_language_alias);

   E_EXE_STOP(_e_intl_input_method_exec);
   return 1;
}

/*
 * TODO
 * - Add error dialogs explaining any errors while setting the locale
 *      * Locale aliases need to be configured
 *      * Locale is invalid
 *      * Message files are not found for this locale, then we have (en_US, POSIX, C)
 * - Add support of compound locales i.e. (en_US;zh_CN;C) ==Defer==
 * - Add Configuration for to-be-set environment variables
 */
E_API void
e_intl_language_set(const char *lang)
{
   int set_envars;
   int ok;

   set_envars = 1;
   /* NULL lang means set everything back to the original environment
    * defaults
    */
   if (!lang)
     {
        e_util_env_set("LANG", _e_intl_orig_lang);

        if (!lang) lang = getenv("LC_ALL");
        if (!lang) lang = getenv("LANG");

        set_envars = 0;
     }

   E_FREE(_e_intl_language_alias);
   _e_intl_language_alias = _e_intl_locale_alias_get(lang);
   E_FREE(_e_intl_language);

   if (lang)
     _e_intl_language = strdup(lang);
   else
     _e_intl_language = NULL;

   ok = 1;
   if (strcmp(_e_intl_language_alias, "C"))
     {
        ok = _e_intl_locale_validate(_e_intl_language_alias);
        if (!ok)
          {
             char *p, *new_lang;

             new_lang = _e_intl_language_alias;
             p = strchr(new_lang, '.');
             if (p) *p = 0;
             _e_intl_language_alias = strdup(new_lang);
             E_FREE(new_lang);
             ok = _e_intl_locale_validate(_e_intl_language_alias);
          }
     }
   if (!ok)
     {
        fprintf(stderr, "The locale '%s' cannot be found on your "
                        "system. Please install this locale or try "
                        "something else.", _e_intl_language_alias);
        return;
     }
   /* Only set env vars is a non NULL locale was passed */
   if (set_envars)
     {
        e_util_env_set("LANG", lang);
        /* Unset LANGUAGE, apparently causes issues if set */
        e_util_env_set("LANGUAGE", NULL);
        efreet_lang_reset();
        setlocale(LC_ALL, lang);
     }
   else
     {
        setlocale(LC_ALL, "");
     }

   if (_e_intl_language)
     {
        char *locale_path;

        locale_path = _e_intl_language_path_find(_e_intl_language_alias);
        if (!locale_path)
          {
             E_Locale_Parts *locale_parts;

             locale_parts = e_intl_locale_parts_get(_e_intl_language_alias);

             /* If locale is C or some form of en don't report an error */
             if ((!locale_parts) &&
                 (strcmp(_e_intl_language_alias, "C")))
               {
                  fprintf(stderr,
                          "An error occurred setting your locale. \n\n"

                          "The locale you have chosen '%s' appears to \n"
                          "be an alias, however, it can not be resolved.\n"
                          "Please make sure you have a 'locale.alias' \n"
                          "file in your 'messages' path which can resolve\n"
                          "this alias.\n\n"

                          "Enlightenment will not be translated.\n",
                          _e_intl_language_alias);
               }
             else if ((locale_parts) && (locale_parts->lang) &&
                      (strcmp(locale_parts->lang, "en")))
               {
                  fprintf(stderr,
                          "An error occurred setting your locale. \n\n"

                          "The translation files for the locale you \n"
                          "have chosen (%s) cannot be found in your \n"
                          "'messages' path. \n\n"

                          "Enlightenment will not be translated.\n",
                          _e_intl_language_alias);
               }
             e_intl_locale_parts_free(locale_parts);
          }
        else
          {
#ifdef HAVE_GETTEXT
             bindtextdomain(PACKAGE, locale_path);
             textdomain(PACKAGE);
             bind_textdomain_codeset(PACKAGE, "UTF-8");
#endif
             free(locale_path);
          }
     }
}

E_API const char *
e_intl_language_get(void)
{
   return _e_intl_language;
}

E_API const char *
e_intl_language_alias_get(void)
{
   return _e_intl_language_alias;
}

E_API Eina_List *
e_intl_language_list(void)
{
   Eina_List *next;
   Eina_List *dir_list;
   Eina_List *all_languages;
   E_Path_Dir *epd;

   all_languages = NULL;
   dir_list = e_path_dir_list_get(path_messages);
   EINA_LIST_FOREACH(dir_list, next, epd)
     {
        Eina_List *dir_languages;
        char *language;

        dir_languages = _e_intl_language_dir_scan(epd->dir);

        EINA_LIST_FREE(dir_languages, language)
          if ((_e_intl_language_list_find(all_languages, language)) ||
              ((strlen(language) > 2) && (!_e_intl_locale_validate(language))))
            {
               free(language);
            }
          else
            all_languages = eina_list_append(all_languages, language);
     }

   e_path_dir_list_free(dir_list);

   return all_languages;
}

static int
_e_intl_language_list_find(Eina_List *language_list, char *language)
{
   Eina_List *l;
   char *lang;

   if ((!language_list) || (!language)) return 0;

   EINA_LIST_FOREACH(language_list, l, lang)
     if (!strcmp(lang, language)) return 1;

   return 0;
}

E_API void
e_intl_input_method_set(const char *imc_path)
{
   if (!imc_path)
     {
        E_EXE_STOP(_e_intl_input_method_exec);
        e_util_env_set("GTK_IM_MODULE", _e_intl_orig_gtk_im_module);
        e_util_env_set("QT_IM_MODULE", _e_intl_orig_qt_im_module);
        e_util_env_set("XMODIFIERS", _e_intl_orig_xmodifiers);
        e_util_env_set("ECORE_IMF_MODULE", _e_intl_orig_ecore_imf_module);
     }

   if (imc_path)
     {
        Eet_File *imc_ef;
        E_Input_Method_Config *imc;

        imc_ef = eet_open(imc_path, EET_FILE_MODE_READ);
        if (imc_ef)
          {
             imc = e_intl_input_method_config_read(imc_ef);
             eet_close(imc_ef);

             if (imc)
               {
                  e_util_env_set("GTK_IM_MODULE", imc->gtk_im_module);
                  e_util_env_set("QT_IM_MODULE", imc->qt_im_module);
                  e_util_env_set("XMODIFIERS", imc->xmodifiers);
                  e_util_env_set("ECORE_IMF_MODULE", imc->ecore_imf_module);

                  E_EXE_STOP(_e_intl_input_method_exec);

                  if (E_EXE_IS_VALID(imc->e_im_exec))
                    {
                       // if you see valgrind complain about memory
                       // definitely lost here... it's wrong.
                       _e_intl_input_method_exec = e_util_exe_safe_run
                         (imc->e_im_exec, NULL);
                       ecore_exe_tag_set(_e_intl_input_method_exec, "E/im_exec");

                       if ((!_e_intl_input_method_exec) ||
                           (!ecore_exe_pid_get(_e_intl_input_method_exec)))
                         e_util_dialog_show(_("Input Method Error"),
                                            _("Error starting the input method executable<ps/><ps/>"
                                              "please make sure that your input<ps/>"
                                              "method configuration is correct and<ps/>"
                                              "that your configuration's<ps/>"
                                              "executable is in your PATH<ps/>"));
                    }
                  e_intl_input_method_config_free(imc);
               }
          }
     }
}

E_API Eina_List *
e_intl_input_method_list(void)
{
   Eina_List *input_methods;
   Eina_List *im_list;

   im_list = NULL;

   /* Personal Path */
   im_list = _e_intl_imc_dir_scan(e_intl_imc_personal_path_get());

   /* System Path */
   input_methods = _e_intl_imc_dir_scan(e_intl_imc_system_path_get());
   im_list = eina_list_merge(im_list, input_methods);

   return im_list;
}

const char *
e_intl_imc_personal_path_get(void)
{
   if (!_e_intl_imc_personal_path)
     {
        char buf[4096];

        e_user_dir_concat_static(buf, "input_methods");
        _e_intl_imc_personal_path = eina_stringshare_add(buf);
     }
   return _e_intl_imc_personal_path;
}

const char *
e_intl_imc_system_path_get(void)
{
   if (!_e_intl_imc_system_path)
     {
        char buf[4096];

        e_prefix_data_concat_static(buf, "data/input_methods");
        _e_intl_imc_system_path = eina_stringshare_add(buf);
     }
   return _e_intl_imc_system_path;
}

static Eina_Bool
_e_intl_cb_exit(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Del *ev;

   ev = event;
   if (!ev->exe) return ECORE_CALLBACK_PASS_ON;
   if (ev->exe == _e_intl_input_method_exec)
     _e_intl_input_method_exec = NULL;
   return ECORE_CALLBACK_PASS_ON;
}

static void
_e_intl_locale_hash_free(Eina_Hash *locale_hash)
{
   if (!locale_hash) return;
   eina_hash_foreach(locale_hash, _e_intl_locale_hash_free_cb, NULL);
   eina_hash_free(locale_hash);
}

static Eina_Bool
_e_intl_locale_hash_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   free(data);
   return 1;
}

/*
 * get the directory associated with the language. Language Must be valid alias
 * i.e. Already validated and already de-aliased.
 *
 * NULL means:
 *  1) The user does not have an enlightenment translation for this locale
 *  2) The user does not have their locale.aliases configured correctly
 *
 * @return NULL if not found.
 */
static char *
_e_intl_language_path_find(char *language)
{
   char *directory;
   char *data;
   Eina_List *dir_list;
   Eina_List *search_list;
   Eina_List *next_dir;
   Eina_List *next_search;
   E_Path_Dir *epd;
   char *search_locale;
   int found;

   search_list = _e_intl_locale_search_order_get(language);

   if (!search_list) return NULL;

   directory = NULL;
   found = 0;
   dir_list = e_path_dir_list_get(path_messages);

   /* For each directory in the path */
   EINA_LIST_FOREACH(dir_list, next_dir, epd)
     {
        if (found) break;

        /* Match canonicalized locale against each possible search */
        EINA_LIST_FOREACH(search_list, next_search, search_locale)
          {
             char message_path[PATH_MAX];

             snprintf(message_path, sizeof(message_path), "%s/%s/LC_MESSAGES/%s.mo", epd->dir, search_locale, PACKAGE);

             if ((ecore_file_exists(message_path)) && (!ecore_file_is_dir(message_path)))
               {
                  directory = strdup(epd->dir);
                  found = 1;
                  break;
               }
          }
     }

   e_path_dir_list_free(dir_list);

   EINA_LIST_FREE(search_list, data)
     free(data);

   return directory;
}

static Eina_List *
_e_intl_language_dir_scan(const char *dir)
{
   Eina_List *languages;
   Eina_List *files;
   char *file;

   languages = NULL;

   files = ecore_file_ls(dir);
   if (!files) return NULL;

   if (files)
     {
        EINA_LIST_FREE(files, file)
          {
             char file_path[PATH_MAX];

             snprintf(file_path, sizeof(file_path), "%s/%s/LC_MESSAGES/%s.mo",
                      dir, file, PACKAGE);
             if (ecore_file_exists(file_path) && !ecore_file_is_dir(file_path))
               languages = eina_list_append(languages, file);
             else
               free(file);
          }
     }
   return languages;
}

/* get the alias for a locale
 *
 * return pointer to allocated alias string. never returns NULL
 * String will be the same if its a valid locale already or there
 * is no alias.
 */
static char *
_e_intl_locale_alias_get(const char *language)
{
   Eina_Hash *alias_hash;
   char *alias;
   char lbuf[256];
   char *lower_language = lbuf;

   if ((!language) || (!strncmp(language, "POSIX", strlen("POSIX"))))
     return strdup("C");

   alias_hash = _e_intl_locale_alias_hash_get();
   if (!alias_hash) /* No alias file available */
     return strdup(language);

   strncpy(lbuf, language, sizeof(lbuf) - 1);
   lbuf[sizeof(lbuf) - 1] = '\0';
   eina_str_tolower(&lower_language);
   alias = eina_hash_find(alias_hash, lower_language);

   if (alias)
     alias = strdup(alias);
   else
     alias = strdup(language);

   _e_intl_locale_hash_free(alias_hash);

   return alias;
}

static Eina_Hash *
_e_intl_locale_alias_hash_get(void)
{
   Eina_List *next;
   Eina_List *dir_list;
   E_Path_Dir *epd;
   Eina_Hash *alias_hash;

   dir_list = e_path_dir_list_get(path_messages);
   alias_hash = NULL;

   EINA_LIST_FOREACH(dir_list, next, epd)
     {
        char buf[4096];
        FILE *f;

        snprintf(buf, sizeof(buf), "%s/locale.alias", epd->dir);
        f = fopen(buf, "r");
        if (f)
          {
             char alias[4096], locale[4096];

             /* read locale alias lines */
             while (fscanf(f, "%4095s %4095[^\n]\n", alias, locale) == 2)
               {
                  /* skip comments */
                  if ((alias[0] == '!') || (alias[0] == '#'))
                    continue;

                  /* skip dupes */
                  if (eina_hash_find(alias_hash, alias))
                    continue;

                  if (!alias_hash) alias_hash = eina_hash_string_superfast_new(NULL);
                  eina_hash_add(alias_hash, alias, strdup(locale));
               }
             fclose(f);
          }
     }
   e_path_dir_list_free(dir_list);

   return alias_hash;
}

/* return parts of the locale that are requested in the mask
 * return null if the locale looks to be invalid (Does not have
 * ll_DD)
 *
 * the returned string needs to be freed
 */
/*
 * Not canonic, just gets the parts
 */
E_API E_Locale_Parts *
e_intl_locale_parts_get(const char *locale)
{
   /* Parse Results */
   E_Locale_Parts *locale_parts;
   char language[4] = {0};
   char territory[4] = {0};
   char codeset[32] = {0};
   char modifier[32] = {0};

   /* Parse State */
   int state = 0;   /* start out looking for the language */
   unsigned int locale_idx;
   int tmp_idx = 0;

   /* Parse Loop - Separators are _ . @ */
   for (locale_idx = 0; locale_idx < strlen(locale); locale_idx++)
     {
        char locale_char;
        locale_char = locale[locale_idx];

        /* we have finished scanning the locale string */
        if (!locale_char)
          break;

        /* scan this character based on the current start */
        switch (state)
          {
           case 0: /* Gathering Language */
             if (tmp_idx == 2 && locale_char == '_')
               {
                  state++;
                  language[tmp_idx] = 0;
                  tmp_idx = 0;
               }
             else if ((tmp_idx < 2) && (islower(locale_char)))
               language[tmp_idx++] = locale_char;
             else if (locale_char == '.') // no territory
               {
                  state = 2;
                  language[tmp_idx] = 0;
                  tmp_idx = 0;
               }
             else
               return NULL;
             break;

           case 1: /* Gathering Territory */
             if (tmp_idx == 2 && locale_char == '.')
               {
                  state++;
                  territory[tmp_idx] = 0;
                  tmp_idx = 0;
               }
             else if ((tmp_idx == 2) && (locale_char == '@'))
               {
                  state += 2;
                  territory[tmp_idx] = 0;
                  codeset[0] = 0;
                  tmp_idx = 0;
               }
             else if ((tmp_idx < 2) && isupper(locale_char))
               territory[tmp_idx++] = locale_char;
             else
               return NULL;
             break;

           case 2: /* Gathering Codeset */
             if (locale_char == '@')
               {
                  state++;
                  codeset[tmp_idx] = 0;
                  tmp_idx = 0;
               }
             else if (tmp_idx < 31)
               codeset[tmp_idx++] = locale_char;
             else
               return NULL;
             break;

           case 3: /* Gathering modifier */
             if (tmp_idx < 31)
               modifier[tmp_idx++] = locale_char;
             else
               return NULL;
             break;

           default:
             break;
          }
     }

   /* set end-of-string \0 */
   /* There are no breaks here on purpose */
   switch (state)
     {
      case 0:
        language[tmp_idx] = 0;
        tmp_idx = 0;
        EINA_FALLTHROUGH;
        /* no break */

      case 1:
        territory[tmp_idx] = 0;
        tmp_idx = 0;
        EINA_FALLTHROUGH;
        /* no break */

      case 2:
        codeset[tmp_idx] = 0;
        tmp_idx = 0;
        EINA_FALLTHROUGH;
        /* no break */

      case 3:
        modifier[tmp_idx] = 0;
        EINA_FALLTHROUGH;
        /* no break */

      default:
        break;
     }

   if ((!language[0]) && (!territory[0]) && (!codeset[0]) && (!modifier[0])) return NULL;
   locale_parts = E_NEW(E_Locale_Parts, 1);

   /* Put the parts of the string together */
   if (language[0] != 0)
     {
        locale_parts->mask |= E_INTL_LOC_LANG;
        locale_parts->lang = eina_stringshare_add(language);
     }
   if (territory[0] != 0)
     {
        locale_parts->mask |= E_INTL_LOC_REGION;
        locale_parts->region = eina_stringshare_add(territory);
     }
   if (codeset[0] != 0)
     {
        locale_parts->mask |= E_INTL_LOC_CODESET;
        locale_parts->codeset = eina_stringshare_add(codeset);
     }
   if (modifier[0] != 0)
     {
        locale_parts->mask |= E_INTL_LOC_MODIFIER;
        locale_parts->modifier = eina_stringshare_add(modifier);
     }

   return locale_parts;
}

E_API void
e_intl_locale_parts_free(E_Locale_Parts *locale_parts)
{
   if (locale_parts)
     {
        if (locale_parts->lang) eina_stringshare_del(locale_parts->lang);
        if (locale_parts->region) eina_stringshare_del(locale_parts->region);
        if (locale_parts->codeset) eina_stringshare_del(locale_parts->codeset);
        if (locale_parts->modifier) eina_stringshare_del(locale_parts->modifier);
        E_FREE(locale_parts);
     }
}

E_API char *
e_intl_locale_parts_combine(E_Locale_Parts *locale_parts, int mask)
{
   int locale_size;
   char *locale;

   if (!locale_parts) return NULL;

   if ((mask & locale_parts->mask) != mask) return NULL;

   /* Construct the clean locale string */
   locale_size = 0;

   /* determine the size */
   if (mask & E_INTL_LOC_LANG)
     locale_size = strlen(locale_parts->lang) + 1;

   if (mask & E_INTL_LOC_REGION)
     locale_size += strlen(locale_parts->region) + 1;

   if (mask & E_INTL_LOC_CODESET)
     locale_size += strlen(locale_parts->codeset) + 1;

   if (mask & E_INTL_LOC_MODIFIER)
     locale_size += strlen(locale_parts->modifier) + 1;

   if (!locale_size) return NULL;

   /* Allocate memory */
   locale = (char *)malloc(locale_size);
   locale[0] = 0;

   if (mask & E_INTL_LOC_LANG)
     strcat(locale, locale_parts->lang);

   if (mask & E_INTL_LOC_REGION)
     {
        if (locale[0] != 0) strcat(locale, "_");
        strcat(locale, locale_parts->region);
     }

   if (mask & E_INTL_LOC_CODESET)
     {
        if (locale[0] != 0) strcat(locale, ".");
        strcat(locale, locale_parts->codeset);
     }

   if (mask & E_INTL_LOC_MODIFIER)
     {
        if (locale[0] != 0) strcat(locale, "@");
        strcat(locale, locale_parts->modifier);
     }

   return locale;
}

E_API char *
e_intl_locale_charset_canonic_get(const char *charset)
{
   char charset_canonic[32];
   char c;
   int i, i_tmp;

   i = 0;
   i_tmp = 0;
   while ((c = charset[i++]) != 0)
     {
        if (isalnum(c))
          charset_canonic[i_tmp++] = tolower(c);
     }
   charset_canonic[i_tmp] = 0;

   if (!strcmp(charset, charset_canonic))
     return NULL;

   return strdup(charset_canonic);
}

static Eina_List *
_e_intl_locale_system_locales_get(void)
{
   Eina_List *locales;
   FILE *output;

   locales = NULL;
   /* FIXME: Maybe needed for other BSD OS, or even Solaris */
#ifdef __OpenBSD__
   output = popen("ls /usr/share/locale", "r");
#else
   output = popen("locale -a", "r");
#endif
   if (output)
     {
        char line[32];
        while (fscanf(output, "%31[^\n]\n", line) == 1)
          locales = eina_list_append(locales, strdup(line));

        pclose(output);
     }
   return locales;
}

/*
 * must be an un aliased locale;
 */
static int
_e_intl_locale_validate(const char *locale)
{
   Eina_List *all_locales;
   E_Locale_Parts *locale_parts;
   char *locale_next;
   char *locale_lr = NULL;
   char *locale_cs_canonic;
   int found;

   found = 0;

   locale_parts = e_intl_locale_parts_get(locale);

   /* Gather the search information */
   if (locale_parts)
     {
        if (locale_parts->mask & E_INTL_LOC_REGION)
          locale_lr = e_intl_locale_parts_combine(locale_parts, E_INTL_LOC_LANG | E_INTL_LOC_REGION);
        else if (locale_parts->lang)
          locale_lr = strdup(locale_parts->lang);
     }
   if (!locale_lr)
     {
        /* Not valid locale, maybe its an alias */
        locale_lr = strdup(locale);
        locale_cs_canonic = NULL;
     }
   else
     {
        if (locale_parts && locale_parts->codeset)
          locale_cs_canonic = e_intl_locale_charset_canonic_get(locale_parts->codeset);
        else
          locale_cs_canonic = NULL;
     }

   /* Get list of all available locales and aliases */
   all_locales = _e_intl_locale_system_locales_get();

   /* Match locale with one from the list */
   EINA_LIST_FREE(all_locales, locale_next)
     {
        if (found == 0)
          {
             E_Locale_Parts *locale_parts_next;
             char *locale_lr_next = NULL;

             locale_parts_next = e_intl_locale_parts_get(locale_next);
             if (locale_parts_next)
               {
                  if (locale_parts_next->mask & E_INTL_LOC_REGION)
                    locale_lr_next = e_intl_locale_parts_combine(locale_parts_next,
                                                                 E_INTL_LOC_LANG | E_INTL_LOC_REGION);
                  else if (locale_parts_next->lang)
                    locale_lr_next = strdup(locale_parts_next->lang);
               }
             if ((locale_parts) && (locale_lr_next) &&
                 (!strcmp(locale_lr, locale_lr_next)))
               {
                  /* Matched lang/region part, now if CS matches */
                  if ((!locale_parts->codeset) && (!locale_parts_next->codeset))
                    {
                       /* Lang/Region parts match and no charsets,
                        * we have a match
                        */
                       found = 1;
                    }
                  else if (locale_parts->codeset && locale_parts_next->codeset)
                    {
                       if (!strcmp(locale_parts->codeset, locale_parts_next->codeset))
                         {
                            /* Lang/Region and charsets match */
                            found = 1;
                         }
                       else if (locale_cs_canonic)
                         {
                            char *locale_cs_canonic_next;
                            /* try to match charsets in canonic form */

                            locale_cs_canonic_next =
                              e_intl_locale_charset_canonic_get(locale_parts_next->codeset);

                            if (locale_cs_canonic_next)
                              {
                                 if (!strcmp(locale_cs_canonic, locale_cs_canonic_next))
                                   {
                                      /* Lang/Resion and charsets in canonic
                                       * form match
                                       */
                                      found = 1;
                                   }
                                 free(locale_cs_canonic_next);
                              }
                            else
                              {
                                 if (!strcmp(locale_cs_canonic, locale_parts_next->codeset))
                                   {
                                      /* Lang/Resion and charsets in canonic
                                       * form match
                                       */
                                      found = 1;
                                   }
                              }
                         }
                    }
               }
             else
               {
                  /* Its an alias */
                  if (!strcmp(locale_lr, locale_next)) found = 1;
               }
             e_intl_locale_parts_free(locale_parts_next);
             E_FREE(locale_lr_next);
          }
        free(locale_next);
     }

   e_intl_locale_parts_free(locale_parts);
   free(locale_lr);
   E_FREE(locale_cs_canonic);
   return found;
}

/*
 *  arg local must be an already validated and unaliased locale
 *  returns the locale search order e.g.
 *  en_US.UTF-8 ->
 *   Mask (b) Locale (en_US.UTF-8)
 *   Mask (a) Locale (en_US)
 *   Mask (9) Locale (en.UTF-8)
 *   Mask (8) Locale (en)
 */
static Eina_List *
_e_intl_locale_search_order_get(const char *locale)
{
   Eina_List *search_list;
   E_Locale_Parts *locale_parts;
   char *masked_locale;
   int mask;

   locale_parts = e_intl_locale_parts_get(locale);
   if (!locale_parts) return NULL;

   search_list = NULL;
   for (mask = E_INTL_LOC_ALL; mask >= E_INTL_LOC_LANG; mask--)
     {
        if ((mask & locale_parts->mask) == mask)
          {
             /* Only append if the mask we need is available */
             masked_locale = e_intl_locale_parts_combine(locale_parts, mask);
             search_list = eina_list_append(search_list, masked_locale);
          }
     }
   e_intl_locale_parts_free(locale_parts);
   return search_list;
}

static Eina_List *
_e_intl_imc_dir_scan(const char *dir)
{
   Eina_List *imcs = NULL;
   Eina_List *files;
   char *file;

   files = ecore_file_ls(dir);

   EINA_LIST_FREE(files, file)
     {
        if (strstr(file, ".imc"))
          {
             char buf[PATH_MAX];

             snprintf(buf, sizeof(buf), "%s/%s", dir, file);
             imcs = eina_list_append(imcs, strdup(buf));
          }
        free(file);
     }
   return imcs;
}

#include <Eina.h>
#include <Eet.h>
#include "e_macros.h"
#define E_TYPEDEFS
#include "e_config_data.h"
#include "e_intl_data.h"
#undef E_TYPEDEFS
#include "e_config_data.h"
#include "e_intl_data.h"

/* This file is the counterpart for data storage of e_intl */
/* This only needs to be separate because the e_imc binary and other third parties
   many want to include the functionality to read IMC data from EET files
 */
static Eet_Data_Descriptor *_e_intl_input_method_config_edd = NULL;

EINTERN int
e_intl_data_init(void)
{
   _e_intl_input_method_config_edd = E_CONFIG_DD_NEW("input_method_config", E_Input_Method_Config);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, version, INT);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, e_im_name, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, gtk_im_module, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, qt_im_module, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, xmodifiers, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, ecore_imf_module, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, e_im_exec, STR);
   E_CONFIG_VAL(_e_intl_input_method_config_edd, E_Input_Method_Config, e_im_setup_exec, STR);

   return 1;
}

EINTERN int
e_intl_data_shutdown(void)
{
   E_CONFIG_DD_FREE(_e_intl_input_method_config_edd);

   return 1;
}

/* Get the input method configuration from the file */
E_API E_Input_Method_Config *
e_intl_input_method_config_read(Eet_File *imc_file)
{
   E_Input_Method_Config *imc;

   imc = NULL;
   if (imc_file)
     {
        imc = (E_Input_Method_Config *)eet_data_read(imc_file, _e_intl_input_method_config_edd, "imc");
     }
   return imc;
}

/* Write the input method configuration to the file */
E_API int
e_intl_input_method_config_write(Eet_File *imc_file, E_Input_Method_Config *imc)
{
   int ok = 0;

   if (imc_file)
     {
        ok = eet_data_write(imc_file, _e_intl_input_method_config_edd, "imc", imc, 0);
     }
   return ok;
}

E_API void
e_intl_input_method_config_free(E_Input_Method_Config *imc)
{
   if (imc)
     {
        if (imc->e_im_name) eina_stringshare_del(imc->e_im_name);
        if (imc->gtk_im_module) eina_stringshare_del(imc->gtk_im_module);
        if (imc->qt_im_module) eina_stringshare_del(imc->qt_im_module);
        if (imc->xmodifiers) eina_stringshare_del(imc->xmodifiers);
        if (imc->ecore_imf_module) eina_stringshare_del(imc->ecore_imf_module);
        if (imc->e_im_exec) eina_stringshare_del(imc->e_im_exec);
        if (imc->e_im_setup_exec) eina_stringshare_del(imc->e_im_setup_exec);
        free(imc);
     }
}

#include "e.h"

typedef struct _Main_Data Main_Data;

struct _Main_Data
{
   E_Menu *menu;
   E_Menu *apps;
   E_Menu *all_apps;
   E_Menu *desktops;
   E_Menu *clients;
   E_Menu *enlightenment;
   E_Menu *config;
   E_Menu *lost_clients;
};

/* local subsystem functions */
static void         _e_int_menus_main_del_hook(void *obj);
static void         _e_int_menus_main_about(void *data, E_Menu *m, E_Menu_Item *mi);
//static void _e_int_menus_fwin_favorites_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_apps_scan(E_Menu *m, Efreet_Menu *menu);
static void         _e_int_menus_apps_start(void *data, E_Menu *m);
static void         _e_int_menus_apps_free_hook2(void *obj);
static void         _e_int_menus_apps_run(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_apps_drag(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_config_pre_cb(void *data, E_Menu *m);
static void         _e_int_menus_config_free_hook(void *obj);
static void         _e_int_menus_clients_pre_cb(void *data, E_Menu *m);
static void         _e_int_menus_clients_item_create(E_Client *ec, E_Menu *m);
static void         _e_int_menus_clients_free_hook(void *obj);
static void         _e_int_menus_clients_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_clients_icon_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_clients_cleanup_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static int          _e_int_menus_clients_group_desk_cb(const void *d1, const void *d2);
static int          _e_int_menus_clients_group_class_cb(const void *d1, const void *d2);
static int          _e_int_menus_clients_sort_alpha_cb(const void *d1, const void *d2);
static int          _e_int_menus_clients_sort_z_order_cb(const void *d1, const void *d2);
static void         _e_int_menus_clients_add_by_class(Eina_List *clients, E_Menu *m);
static void         _e_int_menus_clients_add_by_desk(E_Desk *curr_desk, Eina_List *clients, E_Menu *m);
static void         _e_int_menus_clients_add_by_none(Eina_List *clients, E_Menu *m);
static void         _e_int_menus_clients_menu_add_iconified(Eina_List *clients, E_Menu *m);
static const char  *_e_int_menus_clients_title_abbrv(const char *title);
static void         _e_int_menus_virtuals_pre_cb(void *data, E_Menu *m);
static void         _e_int_menus_virtuals_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_virtuals_icon_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_themes_about(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_report_bug(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_lost_clients_pre_cb(void *data, E_Menu *m);
static void         _e_int_menus_lost_clients_free_hook(void *obj);
static void         _e_int_menus_lost_clients_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_augmentation_add(E_Menu *m, Eina_List *augmentation);
static void         _e_int_menus_augmentation_del(E_Menu *m, Eina_List *augmentation);
static void         _e_int_menus_shelves_pre_cb(void *data, E_Menu *m);
static void         _e_int_menus_shelves_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_shelves_add_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_shelves_del_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_config_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_main_showhide(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_main_restart(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_main_exit(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_desktops_free_hook(void *obj);
static void         _e_int_menus_desk_item_cb(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_int_menus_item_label_set(Efreet_Menu *entry, E_Menu_Item *mi);
static Efreet_Menu *_e_int_menus_apps_thread_new(E_Menu *m, const char *dir);
static Eina_Bool    _e_int_menus_efreet_desktop_cache_update(void *d, int type, void *e);
//static void _e_int_menus_apps_drag_finished(E_Drag *drag, int dropped EINA_UNUSED);

/* local subsystem globals */
static Eina_Hash *_e_int_menus_augmentation = NULL;
static Eina_List *_e_int_menus_augmentation_disabled = NULL;
static Eina_Hash *_e_int_menus_app_menus = NULL;
static Eina_Hash *_e_int_menus_app_menus_waiting = NULL;
static Efreet_Menu *_e_int_menus_app_menu_default = NULL;
static Ecore_Timer *_e_int_menus_app_cleaner = NULL;
static Eina_List *handlers = NULL;

static Eina_List *
_e_int_menus_augmentation_find(const char *key)
{
   Eina_List *l;
   char *data;

   if ((!_e_int_menus_augmentation) || (!key)) return NULL;

   EINA_LIST_FOREACH(_e_int_menus_augmentation_disabled, l, data)
     if (!strcmp(data, key)) return NULL;
   return eina_hash_find(_e_int_menus_augmentation, key);
}

static void
_e_int_menus_wallpaper_cb()
{
   e_configure_registry_call("appearance/wallpaper", NULL, NULL);
}

#ifdef ISCOMFITOR
static void
_TEST_ADD(void *data, E_Dialog *dia EINA_UNUSED)
{
   char buf[4096];

   snprintf(buf, sizeof(buf), "ITEM %d", e_widget_ilist_count(data) + 1);
   e_widget_ilist_append(data, NULL, buf, NULL, NULL, NULL);
}

static void
_TEST_DEL(void *data, E_Dialog *dia EINA_UNUSED)
{
   e_widget_ilist_remove_num(data, e_widget_ilist_selected_get(data));
}

static void
_TEST(void *d EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Dialog *dia;
   Evas_Object *o_list;
   Evas *e;

   dia = e_dialog_normal_win_new(e_comp, "E", "_widget_playground_dialog");
   e = evas_object_evas_get(dia->win);
   o_list = e_widget_ilist_add(e, 32, 32, NULL);
   e_dialog_button_add(dia, "Add", NULL, _TEST_ADD, o_list);
   e_dialog_button_add(dia, "Del", NULL, _TEST_DEL, o_list);
   e_dialog_content_set(dia, o_list, 100, 300);
   e_dialog_show(dia);
}

#endif

/* externally accessible functions */
E_API E_Menu *
e_int_menus_main_new(void)
{
   E_Menu *m, *subm;
   E_Menu_Item *mi;
   Main_Data *dat;
   Eina_List *l = NULL;
   int separator = 0;

   dat = calloc(1, sizeof(Main_Data));
   m = e_menu_new();
   e_menu_title_set(m, _("Main"));
   dat->menu = m;
   e_object_data_set(E_OBJECT(m), dat);
   e_object_del_attach_func_set(E_OBJECT(m), _e_int_menus_main_del_hook);

   e_menu_category_set(m, "main");

#ifdef ISCOMFITOR
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, "TEST");
   e_menu_item_callback_set(mi, _TEST, NULL);
#endif

   l = _e_int_menus_augmentation_find("main/0");
   if (l) _e_int_menus_augmentation_add(m, l);

   if (e_config->menu_favorites_show)
     {
        subm = e_int_menus_favorite_apps_new();
        if (subm)
          {
             dat->apps = subm;
             mi = e_menu_item_new(m);
             e_menu_item_label_set(mi, _("Favorite Applications"));
             e_util_menu_item_theme_icon_set(mi, "user-bookmarks");
             e_menu_item_submenu_set(mi, subm);
          }
     }

   if (e_config->menu_apps_show)
     {
        subm = e_int_menus_all_apps_new();
        dat->all_apps = subm;
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Applications"));
        e_util_menu_item_theme_icon_set(mi, "preferences-applications");
        e_menu_item_submenu_set(mi, subm);
     }

   l = _e_int_menus_augmentation_find("main/1");
   if (l) _e_int_menus_augmentation_add(m, l);

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   l = _e_int_menus_augmentation_find("main/2");
   if (l) _e_int_menus_augmentation_add(m, l);

   subm = e_int_menus_desktops_new();
   dat->desktops = subm;
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Desktop"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop");
   e_menu_item_submenu_set(mi, subm);

   subm = e_int_menus_clients_new();
   dat->clients = subm;
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Windows"));
   e_util_menu_item_theme_icon_set(mi, "preferences-system-windows");
   e_menu_item_submenu_set(mi, subm);
   e_object_data_set(E_OBJECT(subm), dat);

#ifndef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        subm = e_int_menus_lost_clients_new();
        e_object_data_set(E_OBJECT(subm), dat);
        dat->lost_clients = subm;
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Lost Windows"));
        e_util_menu_item_theme_icon_set(mi, "preferences-windows-lost");
        e_menu_item_submenu_set(mi, subm);
     }
#endif

   l = _e_int_menus_augmentation_find("main/3");
   if (l) _e_int_menus_augmentation_add(m, l);

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   l = _e_int_menus_augmentation_find("main/4");
   if (l) _e_int_menus_augmentation_add(m, l);

   subm = e_menu_new();
   dat->enlightenment = subm;

   l = _e_int_menus_augmentation_find("enlightenment/0");
   if (l) _e_int_menus_augmentation_add(subm, l);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Enlightenment"));
   e_util_menu_item_theme_icon_set(mi, "enlightenment");
   e_menu_item_submenu_set(mi, subm);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("About"));
   e_util_menu_item_theme_icon_set(mi, "help-about");
   e_menu_item_callback_set(mi, _e_int_menus_main_about, NULL);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("About Theme"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-theme");
   e_menu_item_callback_set(mi, _e_int_menus_themes_about, NULL);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Report Bug"));
   e_util_menu_item_theme_icon_set(mi, "dialog-error");
   e_menu_item_callback_set(mi, _e_int_menus_report_bug, NULL);

   l = _e_int_menus_augmentation_find("enlightenment/1");
   if (l) _e_int_menus_augmentation_add(subm, l);

   mi = e_menu_item_new(subm);
   e_menu_item_separator_set(mi, 1);

   l = _e_int_menus_augmentation_find("enlightenment/2");
   if (l) _e_int_menus_augmentation_add(subm, l);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Restart"));
   e_util_menu_item_theme_icon_set(mi, "system-restart");
   e_menu_item_callback_set(mi, _e_int_menus_main_restart, NULL);

   mi = e_menu_item_new(subm);
   e_menu_item_label_set(mi, _("Exit"));
   e_util_menu_item_theme_icon_set(mi, "application-exit");
   e_menu_item_callback_set(mi, _e_int_menus_main_exit, NULL);

   l = _e_int_menus_augmentation_find("enlightenment/3");
   if (l) _e_int_menus_augmentation_add(subm, l);

   l = _e_int_menus_augmentation_find("main/5");
   if (l) _e_int_menus_augmentation_add(m, l);

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   l = _e_int_menus_augmentation_find("main/6");
   if (l) _e_int_menus_augmentation_add(m, l);

   subm = e_int_menus_config_new();
   dat->config = subm;
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Settings"));
   e_menu_item_callback_set(mi, _e_int_menus_config_cb, NULL);
   e_util_menu_item_theme_icon_set(mi, "preferences-system");
   e_menu_item_submenu_set(mi, subm);

   l = _e_int_menus_augmentation_find("main/7");
   if (l) _e_int_menus_augmentation_add(m, l);

   l = eina_hash_find(_e_int_menus_augmentation, "main/8");
   if (l)
     {
        separator = 1;
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);
        _e_int_menus_augmentation_add(m, l);
     }

   l = eina_hash_find(_e_int_menus_augmentation, "main/9");
   if (l)
     {
        if (!separator)
          {
             mi = e_menu_item_new(m);
             e_menu_item_separator_set(mi, 1);
          }
        _e_int_menus_augmentation_add(m, l);
     }

   return m;
}

E_API E_Menu *
e_int_menus_apps_new(const char *dir)
{
   E_Menu *m;

   m = e_menu_new();
   if (dir) e_object_data_set(E_OBJECT(m), (void *)eina_stringshare_add(dir));
   e_menu_pre_activate_callback_set(m, _e_int_menus_apps_start, NULL);
   return m;
}

E_API E_Menu *
e_int_menus_desktops_new(void)
{
   E_Menu *m, *subm;
   E_Menu_Item *mi;

   m = e_menu_new();

   subm = e_menu_new();
   e_menu_category_set(m, "desktop");
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Virtual"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop");
   e_menu_pre_activate_callback_set(subm, _e_int_menus_virtuals_pre_cb, NULL);
   e_menu_item_submenu_set(mi, subm);

   subm = e_menu_new();
   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Shelves"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-shelf");
   e_menu_pre_activate_callback_set(subm, _e_int_menus_shelves_pre_cb, NULL);
   e_menu_item_submenu_set(mi, subm);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Change Wallpaper"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-wallpaper");
   e_menu_item_callback_set(mi, _e_int_menus_wallpaper_cb, NULL);

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Show/Hide All Windows"));
   e_util_menu_item_theme_icon_set(mi, "preferences-system-windows");
   e_menu_item_callback_set(mi, _e_int_menus_main_showhide, NULL);

   e_object_free_attach_func_set(E_OBJECT(m), _e_int_menus_desktops_free_hook);
   return m;
}

E_API E_Menu *
e_int_menus_favorite_apps_new(void)
{
   E_Menu *m = NULL;
   char buf[PATH_MAX];

   e_user_dir_concat_static(buf, "applications/menu/favorite.menu");
   if (ecore_file_exists(buf)) m = e_int_menus_apps_new(buf);
   if (m) _e_int_menus_apps_start(NULL, m);
   return m;
}

E_API E_Menu *
e_int_menus_all_apps_new(void)
{
   E_Menu *m;

   m = e_int_menus_apps_new(NULL);
   return m;
}

E_API E_Menu *
e_int_menus_config_new(void)
{
   E_Menu *m;

   m = e_menu_new();
   e_menu_category_set(m, "config");
   e_menu_pre_activate_callback_set(m, _e_int_menus_config_pre_cb, NULL);
   return m;
}

E_API E_Menu *
e_int_menus_clients_new(void)
{
   E_Menu *m;

   m = e_menu_new();
   e_menu_pre_activate_callback_set(m, _e_int_menus_clients_pre_cb, NULL);
   return m;
}

E_API E_Menu *
e_int_menus_lost_clients_new(void)
{
   E_Menu *m;

   m = e_menu_new();
   e_menu_pre_activate_callback_set(m, _e_int_menus_lost_clients_pre_cb, NULL);
   return m;
}

E_API E_Int_Menu_Augmentation *
e_int_menus_menu_augmentation_add_sorted(const char *menu,
                                         const char *sort_key,
                                         void (*func_add)(void *data, E_Menu *m),
                                         void *data_add,
                                         void (*func_del)(void *data, E_Menu *m),
                                         void *data_del)
{
   E_Int_Menu_Augmentation *maug;
   Eina_List *l;
   Eina_Bool old = EINA_FALSE;

   maug = E_NEW(E_Int_Menu_Augmentation, 1);
   if (!maug) return NULL;

   maug->sort_key = eina_stringshare_add(sort_key);

   maug->add.func = func_add;
   maug->add.data = data_add;

   maug->del.func = func_del;
   maug->del.data = data_del;

   if (_e_int_menus_augmentation)
     {
        if ((l = eina_hash_find(_e_int_menus_augmentation, menu)))
          old = EINA_TRUE;
     }
   else
     {
        _e_int_menus_augmentation = eina_hash_string_superfast_new(NULL);
        l = NULL;
        old = EINA_FALSE;
     }

   if ((!l) || (!maug->sort_key))
     l = eina_list_append(l, maug);
   else
     {
        E_Int_Menu_Augmentation *m2;
        Eina_List *l2;

        /* keep list sorted, those missing sort_key at the end. */
        EINA_LIST_FOREACH(l, l2, m2)
          {
             if (!m2->sort_key)
               break;
             if (strcasecmp(maug->sort_key, m2->sort_key) < 0)
               break;
          }

        if (l2)
          l = eina_list_prepend_relative_list(l, maug, l2);
        else
          l = eina_list_append(l, maug);
     }

   if (old)
     eina_hash_modify(_e_int_menus_augmentation, menu, l);
   else
     eina_hash_add(_e_int_menus_augmentation, menu, l);

   return maug;
}

E_API E_Int_Menu_Augmentation *
e_int_menus_menu_augmentation_add(const char *menu,
                                  void (*func_add)(void *data, E_Menu *m),
                                  void *data_add,
                                  void (*func_del)(void *data, E_Menu *m),
                                  void *data_del)
{
   return e_int_menus_menu_augmentation_add_sorted
            (menu, NULL, func_add, data_add, func_del, data_del);
}

E_API void
e_int_menus_menu_augmentation_del(const char *menu, E_Int_Menu_Augmentation *maug)
{
   Eina_List *l;

   if (!_e_int_menus_augmentation)
     {
        eina_stringshare_del(maug->sort_key);
        free(maug);
        return;
     }

   l = eina_hash_find(_e_int_menus_augmentation, menu);
   if (l)
     {
        l = eina_list_remove(l, maug);

        if (l)
          eina_hash_modify(_e_int_menus_augmentation, menu, l);
        else
          eina_hash_del_by_key(_e_int_menus_augmentation, menu);
     }
   eina_stringshare_del(maug->sort_key);
   free(maug);
}

E_API void
e_int_menus_cache_clear(void)
{
   if (_e_int_menus_app_menus)
     eina_hash_free_buckets(_e_int_menus_app_menus);
}

E_API void
e_int_menus_menu_augmentation_point_disabled_set(const char *menu, Eina_Bool disabled)
{
   if (!menu) return;
   if (disabled)
     {
        _e_int_menus_augmentation_disabled =
          eina_list_append(_e_int_menus_augmentation_disabled, menu);
     }
   else
     {
        _e_int_menus_augmentation_disabled =
          eina_list_remove(_e_int_menus_augmentation_disabled, menu);
     }
}

EINTERN void
e_int_menus_init(void)
{
   if (!e_config->menu_apps_show)
     {
        char buf[PATH_MAX];

        e_user_dir_concat_static(buf, "applications/menu/favorite.menu");
        _e_int_menus_apps_thread_new(NULL, eina_stringshare_add(buf));
     }
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_UPDATE, _e_int_menus_efreet_desktop_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_BUILD, _e_int_menus_efreet_desktop_cache_update, NULL);
}

EINTERN void
e_int_menus_shutdown(void)
{
   if (_e_int_menus_app_cleaner) ecore_timer_del(_e_int_menus_app_cleaner);
   _e_int_menus_app_cleaner = NULL;
   eina_hash_free(_e_int_menus_app_menus_waiting);
   _e_int_menus_app_menus_waiting = NULL;
   efreet_menu_free(_e_int_menus_app_menu_default);
   _e_int_menus_app_menu_default = NULL;
   E_FREE_LIST(handlers, ecore_event_handler_del);
}

/* local subsystem functions */
static Eina_Bool
_e_int_menus_efreet_desktop_cache_update(void *d EINA_UNUSED, int type EINA_UNUSED, void *e EINA_UNUSED)
{
   e_int_menus_shutdown();
   e_int_menus_init();
   return ECORE_CALLBACK_RENEW;
}

static void
_e_int_menus_main_del_hook(void *obj)
{
   Main_Data *dat;
   E_Menu *m;

   m = obj;
   dat = e_object_data_get(E_OBJECT(obj));
   if (dat->apps) e_object_del(E_OBJECT(dat->apps));
   if (dat->all_apps) e_object_del(E_OBJECT(dat->all_apps));
   if (dat->desktops) e_object_del(E_OBJECT(dat->desktops));
   if (dat->clients) e_object_del(E_OBJECT(dat->clients));
   if (dat->lost_clients) e_object_del(E_OBJECT(dat->lost_clients));
   if (dat->enlightenment) e_object_del(E_OBJECT(dat->enlightenment));
   if (dat->config) e_object_del(E_OBJECT(dat->config));
   free(dat);

   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/0"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/1"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/2"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/3"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/4"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/5"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/6"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/7"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/8"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/9"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/10"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("main/11"));
}

static void
_e_int_menus_main_about(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_About *about;

   about = e_about_new();
   if (about) e_about_show(about);
}

static void
_e_int_menus_themes_about(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Theme_About *about;

   about = e_theme_about_new();
   if (about) e_theme_about_show(about);
}

static void
_e_int_menus_report_bug(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_util_open("https://www.enlightenment.org/contrib/report-bug.md", NULL);
}

/*
   static void
   _e_int_menus_fwin_favorites_item_cb(void *data, E_Menu *m, E_Menu_Item *mi)
   {
   e_fwin_new("favorites", "/");
   }
 */

static void
_e_int_menus_config_cb(void *data EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *act;

   act = e_action_find("configuration");
   if (act) act->func.go(E_OBJECT(m->zone), NULL);
}

static void
_e_int_menus_main_showhide(void *data EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *act;

   act = e_action_find("desk_deskshow_toggle");
   if (act) act->func.go(E_OBJECT(m->zone), NULL);
}

static void
_e_int_menus_main_restart(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *a;

   a = e_action_find("restart");
   if ((a) && (a->func.go)) a->func.go(NULL, NULL);
}

static void
_e_int_menus_main_exit(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *a;

   a = e_action_find("exit");
   if ((a) && (a->func.go)) a->func.go(NULL, NULL);
}

static char *
_e_int_menus_app_exe_get(const char *exec)
{
   char *real, *d;
   const char *s;
   Eina_Bool in_quote_dbl = EINA_FALSE;
   Eina_Bool in_quote = EINA_FALSE;

   real = malloc(strlen(exec) + 1);
   if (!real) return NULL;
   for (d = real, s = exec; *s; s++)
     {
        if (in_quote_dbl)
          {
             switch (*s)
               {
                case '\"':
                  in_quote_dbl = EINA_FALSE;
                  break;
                case '\\':
                  s++;
                  EINA_FALLTHROUGH
                default:
                  *d = *s;
                  d++;
                  break;
               }
          }
        else if (in_quote)
          {
             switch (*s)
               {
                case '\'':
                  in_quote = EINA_FALSE;
                  break;
                case '\\':
                  s++;
                  EINA_FALLTHROUGH
                default:
                  *d = *s;
                  d++;
                  break;
               }
          }
        else
          {
             switch (*s)
               {
                case ' ':
                case '\t':
                case '\r':
                case '\n':
                  goto done;
                  break;
                case '\"':
                  in_quote_dbl = EINA_TRUE;
                  break;
                case '\'':
                  in_quote = EINA_TRUE;
                  break;
                case '\\':
                  s++;
                  EINA_FALLTHROUGH
                default:
                  *d = *s;
                  d++;
                  break;
               }
          }
     }
done:
   *d = 0;
   return real;
}
/*
 * This function searches $PATH for try_exec or exec
 * return true if try_exec or exec is found!
 */
static Eina_Bool
_e_int_menus_app_finder(const char *exec)
{
   const char *env = getenv("PATH");
   char **split, buf[PATH_MAX];
   Eina_Bool exec_found = EINA_FALSE;
   int i = 0;
   char *real = NULL;

   if (!exec) return EINA_FALSE;
   real = _e_int_menus_app_exe_get(exec);
   if (!real) return EINA_FALSE;
   if (strchr(real, '/'))
     {
        if (ecore_file_exists(real) && ecore_file_can_exec(real))
          {
             free(real);
             return EINA_TRUE;
          }
     }

   if (!env)
     {
        WRN("Unable to $PATH, Returning TRUE for every .desktop");
        free(real);
        return EINA_TRUE;
     }

   split = eina_str_split(env, ":", 0);
   for (i = 0; split[i] != NULL; i++)
     {
        snprintf(buf, sizeof(buf), "%s/%s", split[i], real);

        if (ecore_file_exists(buf) && ecore_file_can_exec(buf))
          {
             exec_found = EINA_TRUE;
             break;
          }
     }
   free(split[0]);
   free(split);

   if (!exec_found)
     WRN("Unable to find: [%s] I searched $PATH=%s", exec, env);

   free(real);
   return exec_found;
}

/*
 * This function initialises E_Int_Menu_Applications and adds
 * our data.
 */
static E_Int_Menu_Applications*
_e_int_menus_app_config_set(Efreet_Desktop *desktop)
{
   E_Int_Menu_Applications *ma;

   ma = E_NEW(E_Int_Menu_Applications, 1);

   ma->orig_path = eina_stringshare_add(desktop->orig_path);
   ma->try_exec = eina_stringshare_add(desktop->try_exec);
   ma->exec = eina_stringshare_add(desktop->exec);
   ma->load_time = desktop->load_time;
   ma->exec_valid = 1; //ALL .desktop files are VALID unless proven otherwise :)
   return ma;
}

/*
 * This function adds/updates our E_Int_Menu_Applications config,
 * returns true if the .desktop file is valid.
 */
static Eina_Bool
_e_int_menus_app_config_append(Efreet_Desktop *desktop)
{
   E_Int_Menu_Applications *ma, *cma;
   Eina_List *l, *l_next;

   if (!desktop) return EINA_TRUE;

   cma = _e_int_menus_app_config_set(desktop);
   EINA_LIST_FOREACH_SAFE(e_config->menu_applications, l, l_next, ma)
     {
        if ((!strcmp(ma->orig_path, cma->orig_path)) && (ma->load_time == cma->load_time))
          {
             eina_stringshare_del(cma->orig_path);
             eina_stringshare_del(cma->try_exec);
             eina_stringshare_del(cma->exec);
             free(cma);
             return ma->exec_valid;
          }

        if ((!strcmp(ma->orig_path, cma->orig_path)) && (ma->load_time != cma->load_time))
          {
             WRN("Modified: [%s]", cma->orig_path);
             e_config->menu_applications = eina_list_remove(e_config->menu_applications, ma);
          }
     }

   if (cma->try_exec)
     {
        WRN("Try_Exec: [%s]", cma->try_exec);
        cma->exec_valid = _e_int_menus_app_finder(cma->try_exec);
     }
   else if (cma->exec)
     {
        cma->exec_valid = _e_int_menus_app_finder(cma->exec);
     }

   e_config->menu_applications = eina_list_append(e_config->menu_applications, cma);
   return cma->exec_valid;
}

static void
_e_int_menus_apps_scan(E_Menu *m, Efreet_Menu *menu)
{
   E_Menu_Item *mi;
   Eina_List *l;

   if (menu->entries)
     {
        Efreet_Menu *entry;

        EINA_LIST_FOREACH(menu->entries, l, entry)
          {
             if ((entry->type == EFREET_MENU_ENTRY_DESKTOP) &&
                 (!_e_int_menus_app_config_append(entry->desktop)))
               continue;

             mi = e_menu_item_new(m);
             _e_int_menus_item_label_set(entry, mi);

             if (entry->icon)
               {
                  if (entry->icon[0] == '/')
                    e_menu_item_icon_file_set(mi, entry->icon);
                  else
                    e_util_menu_item_theme_icon_set(mi, entry->icon);
               }
             if (entry->type == EFREET_MENU_ENTRY_SEPARATOR)
               e_menu_item_separator_set(mi, 1);
             else if (entry->type == EFREET_MENU_ENTRY_DESKTOP)
               {
                  e_menu_item_callback_set(mi, _e_int_menus_apps_run,
                                           entry->desktop);
                  e_menu_item_drag_callback_set(mi, _e_int_menus_apps_drag,
                                                entry->desktop);
               }
             else if (entry->type == EFREET_MENU_ENTRY_MENU)
               {
                  E_Menu *subm;

                  subm = e_menu_new();
                  efreet_menu_ref(entry);
                  e_menu_pre_activate_callback_set(subm,
                                                   _e_int_menus_apps_start,
                                                   entry);
                  e_menu_item_submenu_set(mi, subm);
                  e_object_free_attach_func_set(E_OBJECT(subm),
                                                _e_int_menus_apps_free_hook2);
               }
             /* TODO: Highlight header
                else if (entry->type == EFREET_MENU_ENTRY_HEADER)
              */
          }
     }
   else
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("No applications"));
        e_menu_item_disabled_set(mi, 1);
     }
}

static Eina_Bool
_e_int_menus_app_cleaner_cb(void *d EINA_UNUSED)
{
   eina_hash_free_buckets(_e_int_menus_app_menus);
   return EINA_TRUE;
}

static void
_e_int_menus_apps_menu_del(void *data)
{
   const char *dir;

   dir = e_object_data_get(data);
   eina_hash_del_by_key(_e_int_menus_app_menus_waiting, dir);
}

static Efreet_Menu *
_e_int_menus_apps_thread_new(E_Menu *m, const char *dir)
{
   Efreet_Menu *menu = NULL;
   E_Menu *mn = NULL;
   char buf[PATH_MAX];

   if (dir)
     {
        if (_e_int_menus_app_menus)
          menu = eina_hash_find(_e_int_menus_app_menus, dir);
        else
          _e_int_menus_app_menus = eina_hash_string_superfast_new((void *)efreet_menu_free);
     }
   else
     {
        menu = _e_int_menus_app_menu_default;
        if (!menu)
          menu = _e_int_menus_app_menu_default = efreet_menu_get();
     }

   if (menu) return menu;
   if (dir)
     {
        if (_e_int_menus_app_menus_waiting)
          mn = eina_hash_find(_e_int_menus_app_menus_waiting, dir);
        else
          _e_int_menus_app_menus_waiting = eina_hash_string_superfast_new(NULL);
     }
   else return NULL;

   if (mn) return NULL;
   if (dir && m)
     eina_hash_add(_e_int_menus_app_menus_waiting, dir, m);

   menu = efreet_menu_parse(dir);

   eina_hash_add(_e_int_menus_app_menus, dir, menu);
   mn = eina_hash_set(_e_int_menus_app_menus_waiting, dir, NULL);
   if (!mn) goto on_end;
   e_object_del_attach_func_set(E_OBJECT(mn), NULL);

   if (_e_int_menus_app_cleaner)
     ecore_timer_loop_reset(_e_int_menus_app_cleaner);
   else
     _e_int_menus_app_cleaner = ecore_timer_loop_add(300, _e_int_menus_app_cleaner_cb, NULL);
   eina_stringshare_del(dir);
   if (m)
     {
        _e_int_menus_apps_scan(m, menu);
        e_menu_pre_activate_callback_set(m, NULL, NULL);
        e_object_data_set(E_OBJECT(m), menu);
        e_object_free_attach_func_set(E_OBJECT(m),
                                      _e_int_menus_apps_free_hook2);
     }

   if (!e_config->menu_apps_show) goto on_end;

   e_user_dir_concat_static(buf, "applications/menu/favorite.menu");
   dir = eina_stringshare_add(buf);

   if (eina_hash_find(_e_int_menus_app_menus, dir))
     eina_stringshare_del(dir);
   else
     _e_int_menus_apps_thread_new(NULL, dir);

 on_end:
   if (m) e_object_del_attach_func_set(E_OBJECT(m), _e_int_menus_apps_menu_del);
   return NULL;
}

static void
_e_int_menus_apps_start(void *data, E_Menu *m)
{
   Efreet_Menu *menu;
   const char *dir = NULL;

   if (m->items) return;

   menu = data;
   if (!menu)
     {
        dir = e_object_data_get(E_OBJECT(m));
        menu = _e_int_menus_apps_thread_new(m, dir);
     }
   if (!menu) return;
   if (_e_int_menus_app_cleaner)
     ecore_timer_loop_reset(_e_int_menus_app_cleaner);
   eina_stringshare_del(dir);
   _e_int_menus_apps_scan(m, menu);
   if (m->pre_activate_cb.func == _e_int_menus_apps_start)
     {
        efreet_menu_unref(m->pre_activate_cb.data);
        m->pre_activate_cb.func = NULL;
        m->pre_activate_cb.data = NULL;
     }
   e_menu_pre_activate_callback_set(m, NULL, NULL);
   e_object_data_set(E_OBJECT(m), menu);
   e_object_free_attach_func_set(E_OBJECT(m),
                                 _e_int_menus_apps_free_hook2);
}

static void
_e_int_menus_apps_free_hook2(void *obj)
{
   E_Menu *m;
   Eina_List *l, *l_next;
   E_Menu_Item *mi;

   m = obj;
   // unref the e menu we had pointed to in the pre activate cb */
   if (m->pre_activate_cb.func == _e_int_menus_apps_start)
     {
        efreet_menu_unref(m->pre_activate_cb.data);
        m->pre_activate_cb.func = NULL;
        m->pre_activate_cb.data = NULL;
     }
   // XXX TODO: this should be automatic in e_menu, just get references right!
   // XXX TODO: fix references and remove me!!!
   EINA_LIST_FOREACH_SAFE(m->items, l, l_next, mi)
     {
        if (mi->submenu)
          e_object_del(E_OBJECT(mi->submenu));
     }
}

static void
_e_int_menus_apps_run(void *data, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   Efreet_Desktop *desktop;

   desktop = data;
   e_exec(m->zone, desktop, NULL, NULL, "menu/apps");
}

/*
   static void
   _e_int_menus_apps_drag_finished(E_Drag *drag, int dropped EINA_UNUSED)
   {
   Efreet_Desktop *desktop;

   desktop = drag->data;
   efreet_desktop_free(desktop);
   }
 */

static void
_e_int_menus_apps_drag(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   Efreet_Desktop *desktop;

   desktop = data;

   /* start drag! */
   if (mi->icon_object)
     {
        E_Drag *drag;
        Evas_Object *o = NULL;
        Evas_Coord x, y, w, h;
        unsigned int size;
        const char *drag_types[] = { "enlightenment/desktop" };

        evas_object_geometry_get(mi->icon_object, &x, &y, &w, &h);
        efreet_desktop_ref(desktop);
        drag = e_drag_new(x, y, drag_types, 1, desktop, -1,
                          NULL, NULL);
        drag->button_mask = evas_pointer_button_down_mask_get(e_comp->evas);

        size = MIN(w, h);
        o = e_util_desktop_icon_add(desktop, size, e_drag_evas_get(drag));
        e_drag_object_set(drag, o);
        e_drag_resize(drag, w, h);
        e_drag_start(drag, mi->drag.x + w, mi->drag.y + h);
     }
}

static void
_e_int_menus_virtuals_pre_cb(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu_Item *mi;
   E_Menu *root;

   e_menu_pre_activate_callback_set(m, NULL, NULL);

   root = e_menu_root_get(m);
   if ((root) && (root->zone))
     {
        E_Zone *zone;
        int i;

        zone = root->zone;
        for (i = 0; i < (zone->desk_x_count * zone->desk_y_count); i++)
          {
             E_Desk *desk;

             desk = zone->desks[i];
             mi = e_menu_item_new(m);
             e_menu_item_radio_group_set(mi, 1);
             e_menu_item_radio_set(mi, 1);
             e_menu_item_label_set(mi, desk->name);
             if (desk == e_desk_current_get(zone))
               e_menu_item_toggle_set(mi, 1);
             e_menu_item_callback_set(mi, _e_int_menus_virtuals_item_cb, desk);
             e_menu_item_realize_callback_set(mi, _e_int_menus_virtuals_icon_cb, desk);
          }
     }

   if (e_configure_registry_exists("screen/virtual_desktops"))
     {
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);

        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Set Virtual Desktops"));
        e_util_menu_item_theme_icon_set(mi, "preferences-desktop");
        e_menu_item_callback_set(mi, _e_int_menus_desk_item_cb, NULL);
     }
}

static void
_e_int_menus_desktops_free_hook(void *obj)
{
   E_Menu *m;
   Eina_List *l, *l_next;
   E_Menu_Item *mi;

   m = obj;
   // XXX TODO: this should be automatic in e_menu, just get references right!
   // XXX TODO: fix references and remove me!!!
   EINA_LIST_FOREACH_SAFE(m->items, l, l_next, mi)
     {
        if (mi->submenu)
          e_object_del(E_OBJECT(mi->submenu));
     }
}

static void
_e_int_menus_desk_item_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_configure_registry_call("screen/virtual_desktops", NULL, NULL);
}

static void
_e_int_menus_virtuals_item_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Desk *desk;

   if (!(desk = data)) return;
   e_desk_show(desk);
}

static void
_e_int_menus_virtuals_icon_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Desk *desk;
   Evas_Object *o;
   const char *bgfile;
   int tw, th;

   desk = data;
   E_OBJECT_CHECK(desk);

   tw = 50;
   th = (tw * desk->zone->h) / desk->zone->w;

   bgfile = e_bg_file_get(desk->zone->num, desk->x, desk->y);
   o = e_thumb_icon_add(m->evas);
   e_thumb_icon_file_set(o, bgfile, "e/desktop/background");
   eina_stringshare_del(bgfile);
   e_thumb_icon_size_set(o, tw, th);
   e_thumb_icon_begin(o);
   mi->icon_object = o;
}

static void
_e_e_int_menus_conf_comp_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_int_config_comp(NULL, NULL);
}

static void
_e_int_menus_config_pre_cb(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu_Item *mi;
   Eina_List *l = NULL;

   e_menu_pre_activate_callback_set(m, NULL, NULL);

   l = _e_int_menus_augmentation_find("config/0");
   if (l)
     {
        _e_int_menus_augmentation_add(m, l);
        if (_e_int_menus_augmentation_find("config/1"))
          {
             mi = e_menu_item_new(m);
             e_menu_item_separator_set(mi, 1);
          }
     }

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Composite"));
   e_util_menu_item_theme_icon_set(mi, "preferences-composite");
   e_menu_item_callback_set(mi, _e_e_int_menus_conf_comp_cb, NULL);

   l = _e_int_menus_augmentation_find("config/1");
   if (l) _e_int_menus_augmentation_add(m, l);

   l = _e_int_menus_augmentation_find("config/2");
   if (l)
     {
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);
        _e_int_menus_augmentation_add(m, l);
     }

   e_object_free_attach_func_set(E_OBJECT(m), _e_int_menus_config_free_hook);
}

static void
_e_int_menus_config_free_hook(void *obj)
{
   E_Menu *m;

   m = obj;
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("config/0"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("config/1"));
   _e_int_menus_augmentation_del(m, _e_int_menus_augmentation_find("config/2"));
}

static int
_e_int_menus_clients_group_desk_cb(const void *d1, const void *d2)
{
   const E_Client *ec1;
   const E_Client *ec2;
   int j, k;

   if (!d1) return 1;
   if (!d2) return -1;

   ec1 = d1;
   ec2 = d2;

   j = ec1->desk->y * 12 + ec1->desk->x;
   k = ec2->desk->y * 12 + ec2->desk->x;

   if (j > k) return 1;
   if (j < k) return -1;
   return -1;   /* Returning '-1' on equal is intentional */
}

static int
_e_int_menus_clients_group_class_cb(const void *d1, const void *d2)
{
   const E_Client *ec1, *ec2;

   if (!d1) return 1;
   if (!d2) return -1;

   ec1 = d1;
   ec2 = d2;

   if (!ec1->icccm.class)
     return -1;
   if (!ec2->icccm.class)
     return 1;

   return strcmp(ec1->icccm.class, ec2->icccm.class) > 0 ? 1 : -1;
}

static int
_e_int_menus_clients_sort_alpha_cb(const void *d1, const void *d2)
{
   const E_Client *ec1, *ec2;
   const char *name1, *name2;

   if (!d1) return 1;
   if (!d2) return -1;

   ec1 = d1;
   ec2 = d2;
   name1 = e_client_util_name_get(ec1);
   name2 = e_client_util_name_get(ec2);

   if (strcasecmp(name1, name2) > 0) return 1;
   if (strcasecmp(name1, name2) < 0) return -1;
   return 0;
}

static int
_e_int_menus_clients_sort_z_order_cb(const void *d1, const void *d2)
{
   const E_Client *ec1, *ec2;

   if (!d1) return 1;
   if (!d2) return -1;

   ec1 = d1;
   ec2 = d2;

   if (ec1->layer < ec2->layer) return 1;
   if (ec1->layer > ec2->layer) return -1;
   return 0;
}

static void
_e_int_menus_clients_menu_add_iconified(Eina_List *clients, E_Menu *m)
{
   Eina_List *l = NULL;
   E_Client *ec = NULL;
   E_Menu_Item *mi = NULL;

   if (!eina_list_count(clients)) return;
   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   EINA_LIST_FOREACH(clients, l, ec)
     _e_int_menus_clients_item_create(ec, m);
}

static void
_e_int_menus_clients_add_by_class(Eina_List *clients, E_Menu *m)
{
   Eina_List *l = NULL, *ico = NULL;
   E_Client *ec;
   E_Menu *subm = NULL;
   E_Menu_Item *mi = NULL;
   char *class = NULL;

   EINA_LIST_FOREACH(clients, l, ec)
     {
        if ((ec->iconic) &&
            (e_config->clientlist_separate_iconified_apps == E_CLIENTLIST_GROUPICONS_SEP))
          {
             ico = eina_list_append(ico, ec);
             continue;
          }

        if (((e_util_strcmp(class, ec->icccm.class)) &&
             e_config->clientlist_separate_with != E_CLIENTLIST_GROUP_SEP_NONE))
          {
             if (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU)
               {
                  if ((subm) && (mi)) e_menu_item_submenu_set(mi, subm);
                  mi = e_menu_item_new(m);
                  e_menu_item_label_set(mi, ec->icccm.class);
                  e_util_menu_item_theme_icon_set(mi, "preferences-system-windows");
                  subm = e_menu_new();
               }
             else
               {
                  mi = e_menu_item_new(m);
                  e_menu_item_separator_set(mi, 1);
               }
             free(class);
             class = strdup(ec->icccm.class);
          }
        if (subm && (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU))
          _e_int_menus_clients_item_create(ec, subm);
        else
          _e_int_menus_clients_item_create(ec, m);
     }

   if ((e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU)
       && (subm) && (mi))
     e_menu_item_submenu_set(mi, subm);

   _e_int_menus_clients_menu_add_iconified(ico, m);
   free(class);
}

static void
_e_int_menus_clients_add_by_desk(E_Desk *curr_desk, Eina_List *clients, E_Menu *m)
{
   E_Desk *desk = NULL;
   Eina_List *l = NULL, *alt = NULL, *ico = NULL;
   E_Client *ec;
   E_Menu *subm;
   E_Menu_Item *mi = NULL;

   /* Deal with present desk first */
   EINA_LIST_FOREACH(clients, l, ec)
     {
        if (ec->iconic && (e_config->clientlist_separate_iconified_apps == E_CLIENTLIST_GROUPICONS_SEP))
          {
             ico = eina_list_append(ico, ec);
             continue;
          }

        if (ec->desk != curr_desk)
          {
             if ((!ec->iconic) ||
                 (ec->iconic && e_config->clientlist_separate_iconified_apps ==
                  E_CLIENTLIST_GROUPICONS_OWNER))
               {
                  alt = eina_list_append(alt, ec);
                  continue;
               }
          }
        else
          _e_int_menus_clients_item_create(ec, m);
     }

   desk = NULL;
   subm = NULL;
   if (eina_list_count(alt) > 0)
     {
        if (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU)
          {
             mi = e_menu_item_new(m);
             e_menu_item_separator_set(mi, 1);
          }

        EINA_LIST_FOREACH(alt, l, ec)
          {
             if ((ec->desk != desk) &&
                 (e_config->clientlist_separate_with != E_CLIENTLIST_GROUP_SEP_NONE))
               {
                  if (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU)
                    {
                       if (subm && mi) e_menu_item_submenu_set(mi, subm);
                       mi = e_menu_item_new(m);
                       e_menu_item_label_set(mi, ec->desk->name);
                       e_util_menu_item_theme_icon_set(mi, "preferences-desktop");
                       subm = e_menu_new();
                    }
                  else
                    {
                       mi = e_menu_item_new(m);
                       e_menu_item_separator_set(mi, 1);
                    }
                  desk = ec->desk;
               }
             if (subm && (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU))
               _e_int_menus_clients_item_create(ec, subm);
             else
               _e_int_menus_clients_item_create(ec, m);
          }
        if (e_config->clientlist_separate_with == E_CLIENTLIST_GROUP_SEP_MENU
            && (subm) && (mi))
          e_menu_item_submenu_set(mi, subm);
     }

   _e_int_menus_clients_menu_add_iconified(ico, m);
}

static void
_e_int_menus_clients_add_by_none(Eina_List *clients, E_Menu *m)
{
   Eina_List *l = NULL, *ico = NULL;
   E_Client *ec;

   EINA_LIST_FOREACH(clients, l, ec)
     {
        if ((ec->iconic) && (e_config->clientlist_separate_iconified_apps == E_CLIENTLIST_GROUPICONS_SEP))
          {
             ico = eina_list_append(ico, ec);
             continue;
          }
        _e_int_menus_clients_item_create(ec, m);
     }
   _e_int_menus_clients_menu_add_iconified(ico, m);
}

static void
_e_int_menus_clients_pre_cb(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu *subm;
   E_Menu_Item *mi = NULL;
   Eina_List *l = NULL, *clients = NULL;
   E_Client *ec;
   E_Zone *zone = NULL;
   E_Desk *desk = NULL;
   Main_Data *dat;

   e_menu_pre_activate_callback_set(m, NULL, NULL);
   /* get the current clients */
   zone = e_zone_current_get();
   desk = e_desk_current_get(zone);

   if (e_config->clientlist_sort_by == E_CLIENTLIST_SORT_MOST_RECENT)
     l = e_client_focus_stack_get();
   else
     l = e_comp->clients;
   EINA_LIST_FOREACH(l, l, ec)
     {
        if (ec->user_skip_winlist || e_client_util_ignored_get(ec)) continue;
        if ((ec->zone == zone) || (ec->iconic) ||
            (ec->zone != zone && e_config->clientlist_include_all_zones))
          {
             if (ec->stack.next == NULL)
               clients = eina_list_append(clients, ec);
          }
     }

   dat = (Main_Data *)e_object_data_get(E_OBJECT(m));
   if (!dat) e_menu_title_set(m, _("Windows"));

   if (!clients)
     {
        /* FIXME here we want nothing, but that crashes!!! */
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("No windows"));
        e_menu_item_disabled_set(mi, 1);
     }

   if (clients)
     {
        /* Sort the clients */
        if (e_config->clientlist_sort_by == E_CLIENTLIST_SORT_ALPHA)
          clients = eina_list_sort(clients, eina_list_count(clients),
                                   _e_int_menus_clients_sort_alpha_cb);

        if (e_config->clientlist_sort_by == E_CLIENTLIST_SORT_ZORDER)
          clients = eina_list_sort(clients, eina_list_count(clients),
                                   _e_int_menus_clients_sort_z_order_cb);

        /* Group the clients */
        if (e_config->clientlist_group_by == E_CLIENTLIST_GROUP_DESK)
          {
             clients = eina_list_sort(clients, eina_list_count(clients),
                                      _e_int_menus_clients_group_desk_cb);
             _e_int_menus_clients_add_by_desk(desk, clients, m);
          }
        if (e_config->clientlist_group_by == E_CLIENTLIST_GROUP_CLASS)
          {
             clients = eina_list_sort(clients, eina_list_count(clients),
                                      _e_int_menus_clients_group_class_cb);
             _e_int_menus_clients_add_by_class(clients, m);
          }
        if (e_config->clientlist_group_by == E_CLIENTLIST_GROUP_NONE)
          _e_int_menus_clients_add_by_none(clients, m);
     }

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Cleanup Windows"));
   e_util_menu_item_theme_icon_set(mi, "preferences-system-windows");
   e_menu_item_callback_set(mi, _e_int_menus_clients_cleanup_cb, zone);

   if ((dat) && (e_config->screen_limits == E_CLIENT_OFFSCREEN_LIMIT_ALLOW_FULL))
     {
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);

        subm = e_int_menus_lost_clients_new();
        e_object_data_set(E_OBJECT(subm), e_object_data_get(E_OBJECT(m)));
        dat->lost_clients = subm;
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Lost Windows"));
        e_util_menu_item_theme_icon_set(mi, "preferences-windows-lost");
        e_menu_item_submenu_set(mi, subm);
     }

   e_object_free_attach_func_set(E_OBJECT(m), _e_int_menus_clients_free_hook);
   e_object_data_set(E_OBJECT(m), clients);
}

static const char *
_e_int_menus_clients_title_abbrv(const char *title)
{
   static char abbv[E_CLIENTLIST_MAX_CAPTION_LEN + 4];
   char *abbvptr = abbv;
   int str_len, len, len2, max_len;

   if (!e_config->clientlist_limit_caption_len) return title;

   max_len = e_config->clientlist_max_caption_len;
   if (eina_unicode_utf8_get_len(title) <= max_len) return title;

   memset(&abbv, 0, sizeof(abbv));
   /* Advance to the end of the first half of the string. */
   len = 0;
   for (len2 = (max_len / 2); len2; len2--)
     {
        Eina_Unicode u = eina_unicode_utf8_next_get(title, &len);
        if ((!u) || ((u >= 0xdc80) && (u <= 0xdcff))) break;
     }

   strncat(abbvptr, title, len);
   abbvptr += len;

   /* Append the ellipsis char. */
   strcpy(abbvptr, "\xE2\x80\xA6");
   abbvptr += 3;

   /* Advance to the start of the second half of the string */
   len = str_len = strlen(title);
   for (len2 = (max_len / 2); len2; len2--)
     eina_unicode_utf8_get_prev(title, &len);

   strncpy(abbvptr, title + len, str_len);
   abbvptr[str_len] = '\0';

   return abbv;
}

static void
_e_int_menus_clients_item_create(E_Client *ec, E_Menu *m)
{
   E_Menu_Item *mi;
   const char *title;

   title = _e_int_menus_clients_title_abbrv(e_client_util_name_get(ec));
   mi = e_menu_item_new(m);
   e_menu_item_check_set(mi, 1);
   if ((title) && (title[0]))
     e_menu_item_label_set(mi, title);
   else
     e_menu_item_label_set(mi, _("Untitled window"));
   /* ref the client as we implicitly unref it in the callback */
   e_object_ref(E_OBJECT(ec));
/*   e_object_breadcrumb_add(E_OBJECT(ec), "clients_menu");*/
   e_menu_item_callback_set(mi, _e_int_menus_clients_item_cb, ec);
   e_menu_item_realize_callback_set(mi, _e_int_menus_clients_icon_cb, ec);
   if (!ec->iconic) e_menu_item_toggle_set(mi, 1);
}

static void
_e_int_menus_clients_free_hook(void *obj)
{
   Eina_List *clients;
   E_Client *ec;
   E_Menu *m;

   m = obj;
   clients = e_object_data_get(E_OBJECT(m));
   EINA_LIST_FREE(clients, ec)
     e_object_unref(E_OBJECT(ec));
}

static void
_e_int_menus_clients_item_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec;

   ec = data;
   E_OBJECT_CHECK(ec);

   if (ec->iconic)
     {
        if (e_config->clientlist_warp_to_iconified_desktop == 1)
          e_desk_show(ec->desk);
        if (!ec->lock_user_iconify)
          e_client_uniconify(ec);
     }

   if (!ec->iconic) e_desk_show(ec->desk);
   if (!ec->lock_user_stacking) evas_object_raise(ec->frame);
   e_client_focus_set_with_pointer(ec);
}

static void
_e_int_menus_clients_icon_cb(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Client *ec;

   ec = data;
   E_OBJECT_CHECK(ec);
   mi->icon_object = e_client_icon_add(ec, m->evas);
}

static void
_e_int_menus_clients_cleanup_cb(void *data EINA_UNUSED, E_Menu *m, E_Menu_Item *mi EINA_UNUSED)
{
   E_Action *act;

   act = e_action_find("cleanup_windows");
   if (act) act->func.go(E_OBJECT(m->zone), NULL);
}

static void
_e_int_menus_lost_clients_pre_cb(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu_Item *mi;
   Eina_List *l, *clients = NULL;
   E_Client *ec;
   E_Menu *root;
   E_Zone *zone = NULL;

   e_menu_pre_activate_callback_set(m, NULL, NULL);
   root = e_menu_root_get(m);
   /* get the current clients */
   if (root) zone = root->zone;
   clients = e_client_lost_windows_get(zone);

   if (!clients)
     {
        /* FIXME here we want nothing, but that crashes!!! */
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("No windows"));
        e_menu_item_disabled_set(mi, 1);
        return;
     }
   EINA_LIST_FOREACH(clients, l, ec)
     {
        const char *title = "";

        title = e_client_util_name_get(ec);
        mi = e_menu_item_new(m);
        if ((title) && (title[0]))
          e_menu_item_label_set(mi, title);
        else
          e_menu_item_label_set(mi, _("Untitled window"));
        /* ref the client as we implicitly unref it in the callback */
        e_object_ref(E_OBJECT(ec));
        e_menu_item_callback_set(mi, _e_int_menus_lost_clients_item_cb, ec);
        if (ec->desktop)
          e_util_desktop_menu_item_icon_add(ec->desktop, 24, mi);
     }
   e_object_free_attach_func_set(E_OBJECT(m),
                                 _e_int_menus_lost_clients_free_hook);
   e_object_data_set(E_OBJECT(m), clients);
}

static void
_e_int_menus_lost_clients_free_hook(void *obj)
{
   Eina_List *clients;
   E_Client *ec;
   E_Menu *m;

   m = obj;
   clients = e_object_data_get(E_OBJECT(m));
   EINA_LIST_FREE(clients, ec)
     e_object_unref(E_OBJECT(ec));
}

static void
_e_int_menus_lost_clients_item_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Client *ec = data;

   E_OBJECT_CHECK(ec);
   if (ec->iconic) e_client_uniconify(ec);
   if (ec->desk) e_desk_show(ec->desk);
   e_comp_object_util_center(ec->frame);
   evas_object_raise(ec->frame);
   if (!ec->lock_focus_out)
     evas_object_focus_set(ec->frame, 1);
}

static void
_e_int_menus_augmentation_add(E_Menu *m, Eina_List *augmentation)
{
   E_Int_Menu_Augmentation *aug;
   Eina_List *l;
   char *data;

   if ((!augmentation) || (!m)) return;
   EINA_LIST_FOREACH(_e_int_menus_augmentation_disabled, l, data)
     if (eina_hash_find(_e_int_menus_augmentation, data) == augmentation)
       return;

   EINA_LIST_FOREACH(augmentation, l, aug)
     if (aug->add.func) aug->add.func(aug->add.data, m);
}

static void
_e_int_menus_augmentation_del(E_Menu *m, Eina_List *augmentation)
{
   E_Int_Menu_Augmentation *aug;
   Eina_List *l;
   char *data;

   if ((!augmentation) || (!m)) return;
   EINA_LIST_FOREACH(_e_int_menus_augmentation_disabled, l, data)
     if (eina_hash_find(_e_int_menus_augmentation, data) == augmentation)
       return;

   EINA_LIST_FOREACH(augmentation, l, aug)
     if (aug->del.func) aug->del.func(aug->del.data, m);
}

static void
_e_int_menus_shelves_pre_cb(void *data EINA_UNUSED, E_Menu *m)
{
   E_Menu_Item *mi;
   Eina_List *l, *shelves = NULL;
   E_Shelf *es;
   E_Zone *zone;

   e_menu_pre_activate_callback_set(m, NULL, NULL);
   zone = e_zone_current_get();

   /* get the current clients */
   shelves = e_shelf_list();
   EINA_LIST_FOREACH(shelves, l, es)
     {
        char buf[1024];
        const char *name;

        if (!es) continue;
        if (es->zone->num != zone->num) continue;

        if (es->name)
          name = es->name;
        else
          {
             name = buf;
             snprintf(buf, sizeof(buf), _("Shelf %s"), e_shelf_orient_string_get(es));
          }

        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, name);
        e_menu_item_callback_set(mi, _e_int_menus_shelves_item_cb, es);
        switch (es->cfg->orient)
          {
           case E_GADCON_ORIENT_LEFT:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-left");
             break;

           case E_GADCON_ORIENT_RIGHT:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-right");
             break;

           case E_GADCON_ORIENT_TOP:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-top");
             break;

           case E_GADCON_ORIENT_BOTTOM:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom");
             break;

           case E_GADCON_ORIENT_CORNER_TL:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-top-left");
             break;

           case E_GADCON_ORIENT_CORNER_TR:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-top-right");
             break;

           case E_GADCON_ORIENT_CORNER_BL:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom-left");
             break;

           case E_GADCON_ORIENT_CORNER_BR:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom-right");
             break;

           case E_GADCON_ORIENT_CORNER_LT:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-left-top");
             break;

           case E_GADCON_ORIENT_CORNER_RT:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-right-top");
             break;

           case E_GADCON_ORIENT_CORNER_LB:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-left-bottom");
             break;

           case E_GADCON_ORIENT_CORNER_RB:
             e_util_menu_item_theme_icon_set(mi, "preferences-position-right-bottom");
             break;

           default:
             e_util_menu_item_theme_icon_set(mi, "preferences-desktop-shelf");
             break;
          }
     }
   if (shelves)
     {
        mi = e_menu_item_new(m);
        e_menu_item_separator_set(mi, 1);
     }

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Add a Shelf"));
   e_util_menu_item_theme_icon_set(mi, "list-add");
   e_menu_item_callback_set(mi, _e_int_menus_shelves_add_cb, NULL);

   if (shelves)
     {
        mi = e_menu_item_new(m);
        e_menu_item_label_set(mi, _("Delete a Shelf"));
        e_util_menu_item_theme_icon_set(mi, "list-remove");
        e_menu_item_callback_set(mi, _e_int_menus_shelves_del_cb, NULL);
     }
}

static void
_e_int_menus_shelves_item_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *s = data;

   E_OBJECT_CHECK(s);
   e_int_shelf_config(s);
}

static void
_e_int_menus_shelves_add_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Zone *zone;

   zone = e_zone_current_get();
   e_shelf_new_dialog(NULL, zone);
}

static void
_e_int_menus_shelves_del_cb(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   e_configure_registry_call("extensions/shelves", NULL, NULL);
}

static void
_e_int_menus_item_label_set(Efreet_Menu *entry, E_Menu_Item *mi)
{
   Efreet_Desktop *desktop;
   char label[4096];
   int opt = 0;

   if ((!entry) || (!mi)) return;

   desktop = entry->desktop;
   if ((e_config->menu_eap_name_show) && (entry->name)) opt |= 0x4;
   if (desktop)
     {
        if ((e_config->menu_eap_generic_show) && (desktop->generic_name) &&
            (desktop->generic_name[0] != 0))
          opt |= 0x2;
        if ((e_config->menu_eap_comment_show) && (desktop->comment) &&
            (desktop->comment[0] != 0))
          opt |= 0x1;
     }

   if (opt == 0x7)
     snprintf(label, sizeof(label), "%s (%s) [%s]", entry->name,
              desktop->generic_name, desktop->comment);
   else if (opt == 0x6)
     snprintf(label, sizeof(label), "%s (%s)", entry->name,
              desktop->generic_name);
   else if (opt == 0x5)
     snprintf(label, sizeof(label), "%s [%s]", entry->name, desktop->comment);
   else if (opt == 0x4)
     snprintf(label, sizeof(label), "%s", entry->name);
   else if (opt == 0x3)
     snprintf(label, sizeof(label), "%s [%s]", desktop->generic_name,
              desktop->comment);
   else if (opt == 0x2)
     snprintf(label, sizeof(label), "%s", desktop->generic_name);
   else if (opt == 0x1)
     snprintf(label, sizeof(label), "%s", desktop->comment);
   else
     snprintf(label, sizeof(label), "%s", entry->name);

   e_menu_item_label_set(mi, label);
}

#include "e.h"

struct _E_Config_Dialog_Data
{
   E_Shelf        *es;
   E_Config_Shelf *escfg;

   Evas_Object    *o_autohide, *o_desk_list, *o_overlap;
   Eina_List      *autohide_list, *desk_list;

   int             layer, overlap;
   int             orient, fit_along;
   int             size;
   const char     *style;
   int             autohide, autohide_show_action;
   double          hide_timeout, hide_duration;
   int             desk_show_mode;
   Eina_List      *handlers;
};

/* local function prototypes */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _fill_data(E_Config_Dialog_Data *cfdata);
static int          _basic_check_changed(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata);
static void         _free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata);
static int          _basic_apply(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static void         _fill_styles(E_Config_Dialog_Data *cfdata, Evas_Object *obj);
static void         _cb_autohide_change(void *data, Evas_Object *obj EINA_UNUSED);
static void         _fill_desks(E_Config_Dialog_Data *cfdata);

E_API void
e_int_shelf_config(E_Shelf *es)
{
   E_Config_Dialog_View *v;

   if (!(v = E_NEW(E_Config_Dialog_View, 1))) return;

   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.create_widgets = _basic_create;
   v->basic.apply_cfdata = _basic_apply;
   v->basic.check_changed = _basic_check_changed;

   es->config_dialog =
     e_config_dialog_new(NULL, _("Shelf Settings"),
                         "E", "_shelf_config_dialog",
                         "preferences-desktop-shelf", 0, v, es);
   elm_win_center(es->config_dialog->dia->win, 1, 1);
}

/* local functions */
static Eina_Bool
_shelf_event_add(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Shelf *ev)
{
   if (ev->shelf->cfg == cfdata->escfg)
     cfdata->es = ev->shelf;
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_shelf_event_del(E_Config_Dialog_Data *cfdata, int type EINA_UNUSED, E_Event_Shelf *ev)
{
   if (ev->shelf == cfdata->es)
     cfdata->es = NULL;
   return ECORE_CALLBACK_RENEW;
}

static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->es = cfd->data;
   cfdata->escfg = cfdata->es->cfg;
   E_LIST_HANDLER_APPEND(cfdata->handlers, E_EVENT_SHELF_ADD, _shelf_event_add, cfdata);
   E_LIST_HANDLER_APPEND(cfdata->handlers, E_EVENT_SHELF_DEL, _shelf_event_del, cfdata);
   _fill_data(cfdata);
   return cfdata;
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   /* stacking */
   cfdata->layer = cfdata->escfg->layer;
   cfdata->overlap = cfdata->escfg->overlap;

   /* position */
   cfdata->orient = cfdata->escfg->orient;
   cfdata->fit_along = cfdata->escfg->fit_along;

   /* size */
   cfdata->size = MAX(cfdata->escfg->size, 20);

   /* style */
   if (cfdata->escfg->style)
     cfdata->style = eina_stringshare_ref(cfdata->escfg->style);
   else
     cfdata->style = eina_stringshare_add("");

   /* autohide */
   cfdata->autohide = cfdata->escfg->autohide;
   cfdata->autohide_show_action = cfdata->escfg->autohide_show_action;
   cfdata->hide_timeout = cfdata->escfg->hide_timeout;
   cfdata->hide_duration = cfdata->escfg->hide_duration;

   /* desktop */
   cfdata->desk_show_mode = cfdata->escfg->desk_show_mode;
   cfdata->desk_list = cfdata->escfg->desk_list;
}

static int
_basic_check_changed(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
#define CHECK(X) if (cfdata->X != cfdata->escfg->X) return 1
   CHECK(layer);
   CHECK(overlap);
   CHECK(orient);
   CHECK(fit_along);
   CHECK(size);
   CHECK(style);
   CHECK(autohide);
   CHECK(autohide_show_action);
   CHECK(desk_show_mode);
   CHECK(desk_list);
   if (fabs(cfdata->hide_timeout - cfdata->escfg->hide_timeout) > 0.19) return 1;
   if (fabs(cfdata->hide_duration - cfdata->escfg->hide_duration) > 0.04) return 1;
   return 0;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   eina_list_free(cfdata->autohide_list);

   eina_stringshare_del(cfdata->style);
   cfdata->style = NULL;

   E_FREE_LIST(cfdata->handlers, ecore_event_handler_del);
   cfdata->es->config_dialog = NULL;
   E_FREE(cfdata);
}

static Evas_Object *
_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *otb, *ol, *ow;
   E_Radio_Group *rg;

   e_dialog_resizable_set(cfd->dia, 1);

   otb = e_widget_toolbook_add(evas, 24, 24);

   /* Stacking */
   ol = e_widget_list_add(evas, 0, 0);
   rg = e_widget_radio_group_new(&(cfdata->layer));
   ow = e_widget_radio_add(evas, _("Above Everything"), E_LAYER_CLIENT_ABOVE, rg);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_radio_add(evas, _("Below Windows"), E_LAYER_CLIENT_DESKTOP, rg);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_radio_add(evas, _("Below Everything"), E_LAYER_DESKTOP, rg);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
  e_widget_toolbook_page_append(otb, NULL, _("Stacking"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   /* position */
   ol = e_widget_table_add(e_win_evas_win_get(evas), 1);
   rg = e_widget_radio_group_new(&(cfdata->orient));
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-left",
                                24, 24, E_GADCON_ORIENT_LEFT, rg);
   e_widget_table_object_append(ol, ow, 0, 2, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-right",
                                24, 24, E_GADCON_ORIENT_RIGHT, rg);
   e_widget_table_object_append(ol, ow, 2, 2, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-top",
                                24, 24, E_GADCON_ORIENT_TOP, rg);
   e_widget_table_object_append(ol, ow, 1, 0, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-bottom",
                                24, 24, E_GADCON_ORIENT_BOTTOM, rg);
   e_widget_table_object_append(ol, ow, 1, 4, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-top-left",
                                24, 24, E_GADCON_ORIENT_CORNER_TL, rg);
   e_widget_table_object_append(ol, ow, 0, 0, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-top-right",
                                24, 24, E_GADCON_ORIENT_CORNER_TR, rg);
   e_widget_table_object_append(ol, ow, 2, 0, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-bottom-left",
                                24, 24, E_GADCON_ORIENT_CORNER_BL, rg);
   e_widget_table_object_append(ol, ow, 0, 4, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-bottom-right",
                                24, 24, E_GADCON_ORIENT_CORNER_BR, rg);
   e_widget_table_object_append(ol, ow, 2, 4, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-left-top",
                                24, 24, E_GADCON_ORIENT_CORNER_LT, rg);
   e_widget_table_object_append(ol, ow, 0, 1, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-right-top",
                                24, 24, E_GADCON_ORIENT_CORNER_RT, rg);
   e_widget_table_object_append(ol, ow, 2, 1, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-left-bottom",
                                24, 24, E_GADCON_ORIENT_CORNER_LB, rg);
   e_widget_table_object_append(ol, ow, 0, 3, 1, 1, 1, 1, 1, 1);
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-right-bottom",
                                24, 24, E_GADCON_ORIENT_CORNER_RB, rg);
   e_widget_table_object_append(ol, ow, 2, 3, 1, 1, 1, 1, 1, 1);
   e_widget_toolbook_page_append(otb, NULL, _("Position"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   /* size */
   ol = e_widget_list_add(evas, 0, 0);
   ow = e_widget_slider_add(evas, 1, 0, _("%1.0f pixels"), 20, 256, 4, 0,
                            NULL, &(cfdata->size), 100);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_check_add(evas, _("Shrink to Content Width"),
                           &(cfdata->fit_along));
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Size"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   /* style */
   ol = e_widget_list_add(evas, 0, 0);
   ow = e_widget_ilist_add(evas, 60, 20, &(cfdata->style));
   _fill_styles(cfdata, ow);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Style"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   /* autohide */
   ol = e_widget_list_add(evas, 0, 0);
   cfdata->o_autohide =
     e_widget_check_add(evas, _("Auto-hide the shelf"), &(cfdata->autohide));
   e_widget_on_change_hook_set(cfdata->o_autohide, _cb_autohide_change, cfdata);
   e_widget_list_object_append(ol, cfdata->o_autohide, 1, 1, 0.5);

   rg = e_widget_radio_group_new(&(cfdata->autohide_show_action));
   ow = e_widget_radio_add(evas, _("Show on mouse in"), 0, rg);
   e_widget_disabled_set(ow, !cfdata->autohide);
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_radio_add(evas, _("Show on mouse click"), 1, rg);
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_disabled_set(ow, !cfdata->autohide);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);

   ow = e_widget_label_add(evas, _("Hide timeout"));
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_disabled_set(ow, !cfdata->autohide);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_slider_add(evas, 1, 0, _("%.1f seconds"), 0.2, 6.0, 0.2, 0,
                            &(cfdata->hide_timeout), NULL, 100);
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_disabled_set(ow, !cfdata->autohide);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);

   ow = e_widget_label_add(evas, _("Hide duration"));
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_disabled_set(ow, !cfdata->autohide);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_slider_add(evas, 1, 0, _("%.2f seconds"), 0.05, 6.0, 0.05, 0,
                            &(cfdata->hide_duration), NULL, 100);
   cfdata->autohide_list = eina_list_append(cfdata->autohide_list, ow);
   e_widget_disabled_set(ow, !cfdata->autohide);
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   ow = e_widget_check_add(evas, _("Don't adjust windows when overlapping the shelf"),
                           &(cfdata->overlap));
   e_widget_disabled_set(ow, ((!cfdata->autohide) || (!e_config->border_fix_on_shelf_toggle)));
   cfdata->o_overlap = ow;
   e_widget_list_object_append(ol, ow, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Auto Hide"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   /* Desktop */
   ol = e_widget_list_add(evas, 0, 0);
   rg = e_widget_radio_group_new(&(cfdata->desk_show_mode));
   ow = e_widget_radio_add(evas, _("Show on all Desktops"), 0, rg);
   e_widget_list_object_append(ol, ow, 1, 0, 0.5);
   ow = e_widget_radio_add(evas, _("Show on specified Desktops"), 1, rg);
   e_widget_list_object_append(ol, ow, 1, 0, 0.5);

   cfdata->o_desk_list = e_widget_ilist_add(evas, 24, 24, NULL);
   e_widget_ilist_multi_select_set(cfdata->o_desk_list, EINA_TRUE);
   _fill_desks(cfdata);
   e_widget_list_object_append(ol, cfdata->o_desk_list, 1, 1, 0.5);
   e_widget_toolbook_page_append(otb, NULL, _("Desktop"), ol,
                                 1, 1, 1, 0, 0.5, 0.0);

   e_widget_toolbook_page_show(otb, 0);
   return otb;
}

static int
_basic_apply(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata)
{
   E_Config_Shelf_Desk *sd;
   int recreate = 0;

   if (cfdata->escfg->orient != cfdata->orient)
     {
        cfdata->escfg->orient = cfdata->orient;
        recreate = 1;
     }

   if (cfdata->escfg->fit_along != cfdata->fit_along)
     {
        cfdata->escfg->fit_along = cfdata->fit_along;
        cfdata->es->fit_along = cfdata->fit_along;
        recreate = 1;
     }

   if (cfdata->escfg->size != cfdata->size)
     {
        cfdata->escfg->size = cfdata->size;
        cfdata->es->size = cfdata->size;
        recreate = 1;
     }

   cfdata->escfg->layer = cfdata->layer;
   evas_object_layer_set(cfdata->es->comp_object, cfdata->escfg->layer);
   cfdata->escfg->overlap = cfdata->overlap;
   e_shelf_autohide_set(cfdata->es, cfdata->autohide + (cfdata->autohide * cfdata->autohide_show_action));
   cfdata->escfg->autohide_show_action = cfdata->autohide_show_action;
   cfdata->escfg->hide_timeout = cfdata->hide_timeout;
   cfdata->escfg->hide_duration = cfdata->hide_duration;
   cfdata->escfg->desk_show_mode = cfdata->desk_show_mode;

   EINA_LIST_FREE(cfdata->escfg->desk_list, sd)
     E_FREE(sd);
   cfdata->escfg->desk_list = NULL;

   if (cfdata->desk_show_mode)
     {
        const Eina_List *l;
        const E_Ilist_Item *it;
        int idx = -1;

        EINA_LIST_FOREACH(e_widget_ilist_items_get(cfdata->o_desk_list), l, it)
          {
             E_Desk *desk;

             idx++;
             if ((!it) || (!it->selected)) continue;
             if (!(desk = e_desk_at_pos_get(cfdata->es->zone, idx))) continue;
             sd = E_NEW(E_Config_Shelf_Desk, 1);
             sd->x = desk->x;
             sd->y = desk->y;
             cfdata->escfg->desk_list =
               eina_list_append(cfdata->escfg->desk_list, sd);
          }
     }

   if (!recreate)
     e_gadcon_unpopulate(cfdata->es->gadcon);
   if (!cfdata->escfg->style)
     {
        cfdata->escfg->style = eina_stringshare_ref(cfdata->style);
        e_shelf_style_set(cfdata->es, cfdata->style);
     }
   else if ((cfdata->escfg->style) &&
            (cfdata->escfg->style != cfdata->style))
     {
        eina_stringshare_replace(&cfdata->escfg->style, cfdata->style);
        e_shelf_style_set(cfdata->es, cfdata->style);
     }
   if (recreate)
     {
        E_Zone *zone;
        E_Config_Shelf *cf_es;

        zone = cfdata->es->zone;
        cf_es = cfdata->es->cfg;
        cfdata->es->config_dialog = NULL;
        e_shelf_hide(cfdata->es);
        e_object_del(E_OBJECT(cfdata->es));

        cfdata->es = e_shelf_config_new(zone, cf_es);
        cfdata->es->config_dialog = cfd;
     }
   else
     e_gadcon_populate(cfdata->es->gadcon);

   if (cfdata->escfg->desk_show_mode)
     {
        E_Desk *desk;
        Eina_List *l;
        int show = 0;

        desk = e_desk_current_get(cfdata->es->zone);
        EINA_LIST_FOREACH(cfdata->escfg->desk_list, l, sd)
          {
             if ((desk->x == sd->x) && (desk->y == sd->y))
               {
                  show = 1;
                  break;
               }
          }
        if (show) e_shelf_show(cfdata->es);
        else e_shelf_hide(cfdata->es);
     }
   else
     e_shelf_show(cfdata->es);

   if ((cfdata->escfg->autohide) && (!cfdata->es->hidden))
     e_shelf_toggle(cfdata->es, 0);
   else if ((!cfdata->escfg->autohide) && (cfdata->es->hidden))
     e_shelf_toggle(cfdata->es, 1);

   e_shelf_obstacles_update(cfdata->es);
   e_config_save_queue();
   cfdata->es->config_dialog = cfd;
   return 1;
}

static void
_fill_styles(E_Config_Dialog_Data *cfdata, Evas_Object *obj)
{
   Evas *evas;
   Eina_List *l, *styles;
   char *style;
   const char *str;
   int mw, n = 0;

   evas = evas_object_evas_get(obj);
   evas_event_freeze(evas);
   edje_freeze();
   e_widget_ilist_freeze(obj);
   e_widget_ilist_clear(obj);

   styles = e_theme_shelf_list();
   EINA_LIST_FOREACH(styles, l, style)
     {
        Evas_Object *thumb, *ow;
        char buff[4096];

        thumb = e_livethumb_add(evas);
        e_livethumb_vsize_set(thumb, 120, 40);
        ow = edje_object_add(e_livethumb_evas_get(thumb));
        snprintf(buff, sizeof(buff), "e/shelf/%s/base", style);
        e_theme_edje_object_set(ow, "base/theme/shelf", buff);
        e_livethumb_thumb_set(thumb, ow);
        e_widget_ilist_append(obj, thumb, style, NULL, NULL, style);
        if (!strcmp(cfdata->style, style))
          e_widget_ilist_selected_set(obj, n);
        n++;
     }

   e_widget_size_min_get(obj, &mw, NULL);
   e_widget_size_min_set(obj, mw, 180);

   e_widget_ilist_go(obj);
   e_widget_ilist_thaw(obj);
   edje_thaw();
   evas_event_thaw(evas);

   EINA_LIST_FREE(styles, str)
     eina_stringshare_del(str);
}

static void
_cb_autohide_change(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Config_Dialog_Data *cfdata;
   Eina_List *l;
   Evas_Object *ow;

   if (!(cfdata = data)) return;
   EINA_LIST_FOREACH(cfdata->autohide_list, l, ow)
     e_widget_disabled_set(ow, !cfdata->autohide);

   e_widget_disabled_set(cfdata->o_overlap, ((!cfdata->autohide) || (!e_config->border_fix_on_shelf_toggle)));
}

static void
_fill_desks(E_Config_Dialog_Data *cfdata)
{
   Evas *evas;
   int mw, x, y;
   int i = 0;

   evas = evas_object_evas_get(cfdata->o_desk_list);
   evas_event_freeze(evas);
   edje_freeze();
   e_widget_ilist_freeze(cfdata->o_desk_list);
   e_widget_ilist_clear(cfdata->o_desk_list);

   for (y = 0; y < e_config->zone_desks_y_count; y++)
     for (x = 0; x < e_config->zone_desks_x_count; x++)
       {
          E_Desk *desk;
          Eina_List *l;
          E_Config_Shelf_Desk *sd;

          desk = e_desk_at_xy_get(cfdata->es->zone, x, y);
          e_widget_ilist_append(cfdata->o_desk_list, NULL, desk->name,
                                NULL, NULL, NULL);
          i++;

          EINA_LIST_FOREACH(cfdata->desk_list, l, sd)
            {
               if ((sd->x != x) || (sd->y != y)) continue;
               e_widget_ilist_multi_select(cfdata->o_desk_list, i);
               break;
            }
       }

   e_widget_size_min_get(cfdata->o_desk_list, &mw, NULL);
   e_widget_size_min_set(cfdata->o_desk_list, mw, 180);

   e_widget_ilist_go(cfdata->o_desk_list);
   e_widget_ilist_thaw(cfdata->o_desk_list);
   edje_thaw();
   evas_event_thaw(evas);
}

#include "e.h"

/* local function protos */
static void        *_create_data(E_Config_Dialog *cfd);
static void         _fill_data(E_Config_Dialog_Data *cfdata);
static void         _free_data(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static int          _basic_apply(E_Config_Dialog *cfd, E_Config_Dialog_Data *cfdata);
static Evas_Object *_basic_create(E_Config_Dialog *cfd, Evas *evas, E_Config_Dialog_Data *cfdata);

struct _E_Config_Dialog_Data
{
   E_Toolbar *tbar;
   int        orient;
};

E_API void
e_int_toolbar_config(E_Toolbar *tbar)
{
   E_Config_Dialog *cfd;
   E_Config_Dialog_View *v;

   v = E_NEW(E_Config_Dialog_View, 1);
   if (!v) return;
   v->create_cfdata = _create_data;
   v->free_cfdata = _free_data;
   v->basic.apply_cfdata = _basic_apply;
   v->basic.create_widgets = _basic_create;
   cfd = e_config_dialog_new(NULL, _("Toolbar Settings"), "E",
                             "_toolbar_config_dialog", "preferences-desktop-shelf",
                             0, v, tbar);
   tbar->cfg_dlg = cfd;
}

/* local functions */
static void *
_create_data(E_Config_Dialog *cfd)
{
   E_Config_Dialog_Data *cfdata;

   cfdata = E_NEW(E_Config_Dialog_Data, 1);
   cfdata->tbar = cfd->data;
   _fill_data(cfdata);
   return cfdata;
}

static void
_fill_data(E_Config_Dialog_Data *cfdata)
{
   cfdata->orient = cfdata->tbar->gadcon->orient;
}

static void
_free_data(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   cfdata->tbar->cfg_dlg = NULL;
   E_FREE(cfdata);
}

static int
_basic_apply(E_Config_Dialog *cfd EINA_UNUSED, E_Config_Dialog_Data *cfdata)
{
   E_Toolbar *tbar;

   tbar = cfdata->tbar;
   if (!tbar) return 0;
   e_toolbar_orient(tbar, cfdata->orient);
   return 1;
}

static Evas_Object *
_basic_create(E_Config_Dialog *cfd EINA_UNUSED, Evas *evas, E_Config_Dialog_Data *cfdata)
{
   Evas_Object *o, *ot, *ow;
   E_Radio_Group *rg;

   o = e_widget_list_add(evas, 0, 0);
   ot = e_widget_frametable_add(evas, _("Layout"), 1);
   rg = e_widget_radio_group_new(&(cfdata->orient));
   /*
      ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-left",
                                24, 24, E_GADCON_ORIENT_LEFT, rg);
      e_widget_frametable_object_append(ot, ow, 0, 1, 1, 1, 1, 1, 1, 1);
    */
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-top",
                                24, 24, E_GADCON_ORIENT_TOP, rg);
   e_widget_frametable_object_append(ot, ow, 0, 0, 1, 1, 1, 1, 1, 1);
   /*
      ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-right",
                                24, 24, E_GADCON_ORIENT_RIGHT, rg);
      e_widget_frametable_object_append(ot, ow, 2, 1, 1, 1, 1, 1, 1, 1);
    */
   ow = e_widget_radio_icon_add(evas, NULL, "preferences-position-bottom",
                                24, 24, E_GADCON_ORIENT_BOTTOM, rg);
   e_widget_frametable_object_append(ot, ow, 0, 1, 1, 1, 1, 1, 1, 1);
   e_widget_list_object_append(o, ot, 1, 1, 0.5);
   return o;
}

#include "e.h"

EINTERN char *e_ipc_socket = NULL;

#ifdef USE_IPC
/* local subsystem functions */
static Eina_Bool _e_ipc_cb_client_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);
static Eina_Bool _e_ipc_cb_client_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event);

/* local subsystem globals */
static Ecore_Ipc_Server *_e_ipc_server = NULL;
static Eina_Stringshare *_e_ipc_dir = NULL;
#endif

/* externally accessible functions */
EINTERN int
e_ipc_init(void)
{
   char buf[PATH_MAX], buf2[128], buf3[PATH_MAX + 20];
   char *tmp, *user, *base;
   int pid, trynum = 0, id1 = 0;
   struct stat st;

   tmp = getenv("TMPDIR");
   if (!tmp) tmp = "/tmp";
   base = tmp;

   tmp = getenv("XDG_RUNTIME_DIR");
   if (tmp)
     {
        if (stat(tmp, &st) == 0)
          {
             if ((st.st_uid == getuid()) &&
                 ((st.st_mode & (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO)) ==
                  (S_IRWXU | S_IFDIR)))
               base = tmp;
             else
               ERR("XDG_RUNTIME_DIR of '%s' failed permissions check", tmp);
          }
        else
          ERR("XDG_RUNTIME_DIR of '%s' cannot be accessed", tmp);
     }

   tmp = getenv("SD_USER_SOCKETS_DIR");
   if (tmp)
     {
        if (stat(tmp, &st) == 0)
          {
             if ((st.st_uid == getuid()) &&
                 ((st.st_mode & (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO)) ==
                  (S_IRWXU | S_IFDIR)))
               base = tmp;
             else
               ERR("SD_USER_SOCKETS_DIR of '%s' failed permissions check", tmp);
          }
        else
          ERR("SD_USER_SOCKETS_DIR of '%s' cannot be accessed", tmp);
     }

   user = getenv("USER");
   if (!user)
     {
        int uidint;

        user = "__unknown__";
        uidint = getuid();
        if (uidint >= 0)
          {
             snprintf(buf2, sizeof(buf2), "%i", uidint);
             user = buf2;
          }
     }

   e_util_env_set("E_IPC_SOCKET", "");

   pid = (int)getpid();
   for (trynum = 0; trynum <= 4096; trynum++)
     {
        snprintf(buf, sizeof(buf), "%s/e-%s@%x",
                 base, user, id1);
        if (!mkdir(buf, S_IRWXU))
          {
#ifdef USE_IPC
             _e_ipc_dir = eina_stringshare_add(buf);
             snprintf(buf3, sizeof(buf3), "%s/%i",
                      buf, pid);
             _e_ipc_server = ecore_ipc_server_add
                (ECORE_IPC_LOCAL_SYSTEM, buf3, 0, NULL);
             if (_e_ipc_server)
#endif
               {
                  e_ipc_socket = strdup(ecore_file_file_get(buf));
                  break;
               }
          }
        id1 = rand();
     }
#ifdef USE_IPC
   if (!_e_ipc_server)
     {
        if (_e_ipc_dir)
          {
             ecore_file_recursive_rm(_e_ipc_dir);
             eina_stringshare_del(_e_ipc_dir);
             _e_ipc_dir = NULL;
          }
        ERR("Gave up after 4096 sockets in '%s'. All failed", base);
        return 0;
     }

   INF("E_IPC_SOCKET=%s", buf3);
   e_util_env_set("E_IPC_SOCKET", buf3);
   ecore_event_handler_add(ECORE_IPC_EVENT_CLIENT_DEL,
                           _e_ipc_cb_client_del, NULL);
   ecore_event_handler_add(ECORE_IPC_EVENT_CLIENT_DATA,
                           _e_ipc_cb_client_data, NULL);

   e_ipc_codec_init();
#endif
   return 1;
}

EINTERN int
e_ipc_shutdown(void)
{
#ifdef USE_IPC
   e_ipc_codec_shutdown();
   if (_e_ipc_server)
     {
        ecore_ipc_server_del(_e_ipc_server);
        _e_ipc_server = NULL;
     }
   if (_e_ipc_dir)
     {
        ecore_file_recursive_rm(_e_ipc_dir);
        eina_stringshare_del(_e_ipc_dir);
        _e_ipc_dir = NULL;
     }
#endif
   E_FREE(e_ipc_socket);
   return 1;
}

#ifdef USE_IPC
/* local subsystem globals */
static Eina_Bool
_e_ipc_cb_client_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Ipc_Event_Client_Del *e;

   e = event;
   if (ecore_ipc_client_server_get(e->client) != _e_ipc_server)
     return ECORE_CALLBACK_PASS_ON;
   /* delete client sruct */
   e_thumb_client_del(e);
   e_fm2_client_del(e);
   ecore_ipc_client_del(e->client);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_ipc_cb_client_data(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Ipc_Event_Client_Data *e;

   e = event;
   if (ecore_ipc_client_server_get(e->client) != _e_ipc_server)
     return ECORE_CALLBACK_PASS_ON;
   switch (e->major)
     {
      case E_IPC_DOMAIN_SETUP:
      case E_IPC_DOMAIN_REQUEST:
      case E_IPC_DOMAIN_REPLY:
      case E_IPC_DOMAIN_EVENT:
        break;

      case E_IPC_DOMAIN_THUMB:
        e_thumb_client_data(e);
        break;

      case E_IPC_DOMAIN_FM:
        e_fm2_client_data(e);
        break;

      case E_IPC_DOMAIN_ALERT:
      {
         switch (e->minor)
           {
            case E_ALERT_OP_RESTART:
              exit(111);
              break;

            case E_ALERT_OP_EXIT:
              exit(101);
              break;
           }
      }
      break;

      default:
        break;
     }
   return ECORE_CALLBACK_PASS_ON;
}

#endif
#include "e.h"

#ifdef USE_IPC
/* local subsystem functions */

/* encode functions, Should these be global? */

/* local subsystem globals */
static Eet_Data_Descriptor *_e_ipc_int_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_double_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_2int_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_2str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_2str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_int_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_int_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_2str_int_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_2str_int_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_4int_2str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_4int_2str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_5int_2str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_5int_2str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_3int_4str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_3int_4str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_3int_3str_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_3int_3str_list_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_4int_edd = NULL;
static Eet_Data_Descriptor *_e_ipc_str_4int_list_edd = NULL;

#define E_IPC_DD(Edd, Eddc, Name, Type) \
  Eddc.name = Name;                     \
  Eddc.size = sizeof (Type);            \
  Edd = eet_data_descriptor_stream_new(&Eddc);

/* externally accessible functions */
EINTERN int
e_ipc_codec_init(void)
{
   Eet_Data_Descriptor_Class eddc;

   if (!eet_eina_stream_data_descriptor_class_set(&eddc, sizeof (eddc), "int", sizeof (E_Ipc_Int)))
     return 0;
   _e_ipc_int_edd = eet_data_descriptor_stream_new(&eddc);
   E_CONFIG_VAL(_e_ipc_int_edd, E_Ipc_Int, val, INT);

   E_IPC_DD(_e_ipc_double_edd, eddc, "double", E_Ipc_Double);
   E_CONFIG_VAL(_e_ipc_double_edd, E_Ipc_Double, val, DOUBLE);

   E_IPC_DD(_e_ipc_2int_edd, eddc, "2int", E_Ipc_2Int);
   E_CONFIG_VAL(_e_ipc_2int_edd, E_Ipc_2Int, val1, INT);
   E_CONFIG_VAL(_e_ipc_2int_edd, E_Ipc_2Int, val2, INT);

   E_IPC_DD(_e_ipc_str_edd, eddc, "str", E_Ipc_Str);
   E_CONFIG_VAL(_e_ipc_str_edd, E_Ipc_Str, str, STR);

   E_IPC_DD(_e_ipc_str_list_edd, eddc, "str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_str_list_edd, E_Ipc_List, list, _e_ipc_str_edd);

   E_IPC_DD(_e_ipc_2str_edd, eddc, "2str", E_Ipc_2Str);
   E_CONFIG_VAL(_e_ipc_2str_edd, E_Ipc_2Str, str1, STR);
   E_CONFIG_VAL(_e_ipc_2str_edd, E_Ipc_2Str, str2, STR);

   E_IPC_DD(_e_ipc_2str_list_edd, eddc, "2str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_2str_list_edd, E_Ipc_List, list, _e_ipc_2str_edd);

   E_IPC_DD(_e_ipc_str_int_edd, eddc, "str_int", E_Ipc_Str_Int);
   E_CONFIG_VAL(_e_ipc_str_int_edd, E_Ipc_Str_Int, str, STR);
   E_CONFIG_VAL(_e_ipc_str_int_edd, E_Ipc_Str_Int, val, INT);

   E_IPC_DD(_e_ipc_str_int_list_edd, eddc, "str_int_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_str_int_list_edd, E_Ipc_List, list, _e_ipc_str_int_edd);

   E_IPC_DD(_e_ipc_2str_int_edd, eddc, "2str_int", E_Ipc_2Str_Int);
   E_CONFIG_VAL(_e_ipc_2str_int_edd, E_Ipc_2Str_Int, str1, STR);
   E_CONFIG_VAL(_e_ipc_2str_int_edd, E_Ipc_2Str_Int, str2, STR);
   E_CONFIG_VAL(_e_ipc_2str_int_edd, E_Ipc_2Str_Int, val, INT);

   E_IPC_DD(_e_ipc_2str_int_list_edd, eddc, "2str_int_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_2str_int_list_edd, E_Ipc_List, list, _e_ipc_2str_int_edd);

   E_IPC_DD(_e_ipc_4int_2str_edd, eddc, "4int_2str", E_Ipc_4Int_2Str);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, val1, INT);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, val2, INT);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, val3, INT);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, val4, INT);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, str1, STR);
   E_CONFIG_VAL(_e_ipc_4int_2str_edd, E_Ipc_4Int_2Str, str2, STR);

   E_IPC_DD(_e_ipc_4int_2str_list_edd, eddc, "4int_2str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_4int_2str_list_edd, E_Ipc_List, list, _e_ipc_4int_2str_edd);

   E_IPC_DD(_e_ipc_5int_2str_edd, eddc, "5int_2str", E_Ipc_5Int_2Str);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, val1, INT);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, val2, INT);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, val3, INT);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, val4, INT);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, val5, INT);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, str1, STR);
   E_CONFIG_VAL(_e_ipc_5int_2str_edd, E_Ipc_5Int_2Str, str2, STR);

   E_IPC_DD(_e_ipc_5int_2str_list_edd, eddc, "5int_2str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_5int_2str_list_edd, E_Ipc_List, list, _e_ipc_5int_2str_edd);

   E_IPC_DD(_e_ipc_3int_4str_edd, eddc, "3int_4str", E_Ipc_3Int_4Str);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, val1, INT);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, val2, INT);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, val3, INT);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, str1, STR);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, str2, STR);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, str3, STR);
   E_CONFIG_VAL(_e_ipc_3int_4str_edd, E_Ipc_3Int_4Str, str4, STR);

   E_IPC_DD(_e_ipc_3int_4str_list_edd, eddc, "3int_4str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_3int_4str_list_edd, E_Ipc_List, list, _e_ipc_3int_4str_edd);

   E_IPC_DD(_e_ipc_3int_3str_edd, eddc, "3int_3str", E_Ipc_3Int_3Str);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, val1, INT);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, val2, INT);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, val3, INT);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, str1, STR);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, str2, STR);
   E_CONFIG_VAL(_e_ipc_3int_3str_edd, E_Ipc_3Int_3Str, str3, STR);

   E_IPC_DD(_e_ipc_3int_3str_list_edd, eddc, "3int_3str_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_3int_3str_list_edd, E_Ipc_List, list, _e_ipc_3int_3str_edd);

   E_IPC_DD(_e_ipc_str_4int_edd, eddc, "str_4int", E_Ipc_Str_4Int);
   E_CONFIG_VAL(_e_ipc_str_4int_edd, E_Ipc_Str_4Int, str, STR);
   E_CONFIG_VAL(_e_ipc_str_4int_edd, E_Ipc_Str_4Int, val1, INT);
   E_CONFIG_VAL(_e_ipc_str_4int_edd, E_Ipc_Str_4Int, val2, INT);
   E_CONFIG_VAL(_e_ipc_str_4int_edd, E_Ipc_Str_4Int, val3, INT);
   E_CONFIG_VAL(_e_ipc_str_4int_edd, E_Ipc_Str_4Int, val4, INT);

   E_IPC_DD(_e_ipc_str_4int_list_edd, eddc, "str_4int_list", E_Ipc_List);
   E_CONFIG_LIST(_e_ipc_str_4int_list_edd, E_Ipc_List, list, _e_ipc_str_4int_edd);

   return 1;
}

EINTERN void
e_ipc_codec_shutdown(void)
{
   E_CONFIG_DD_FREE(_e_ipc_int_edd);
   E_CONFIG_DD_FREE(_e_ipc_double_edd);
   E_CONFIG_DD_FREE(_e_ipc_2int_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_2str_edd);
   E_CONFIG_DD_FREE(_e_ipc_2str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_int_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_int_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_2str_int_edd);
   E_CONFIG_DD_FREE(_e_ipc_2str_int_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_4int_2str_edd);
   E_CONFIG_DD_FREE(_e_ipc_4int_2str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_5int_2str_edd);
   E_CONFIG_DD_FREE(_e_ipc_5int_2str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_3int_4str_edd);
   E_CONFIG_DD_FREE(_e_ipc_3int_4str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_3int_3str_edd);
   E_CONFIG_DD_FREE(_e_ipc_3int_3str_list_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_4int_edd);
   E_CONFIG_DD_FREE(_e_ipc_str_4int_list_edd);
}

E_API int
e_ipc_codec_int_dec(char *data, int bytes, int *dest)
{
   E_Ipc_Int *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_int_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->val;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_int_enc(int val, int *size_ret)
{
   E_Ipc_Int dat;

   dat.val = val;
   return eet_data_descriptor_encode(_e_ipc_int_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_double_dec(char *data, int bytes, double *dest)
{
   E_Ipc_Double *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_double_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->val;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_double_enc(double val, int *size_ret)
{
   E_Ipc_Double dat;

   dat.val = val;
   return eet_data_descriptor_encode(_e_ipc_double_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_2int_dec(char *data, int bytes, int *dest, int *dest2)
{
   E_Ipc_2Int *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_2int_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->val1;
   if (dest2) *dest2 = dat->val2;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_2int_enc(int val1, int val2, int *size_ret)
{
   E_Ipc_2Int dat;

   dat.val1 = val1;
   dat.val2 = val2;
   return eet_data_descriptor_encode(_e_ipc_2int_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_str_dec(char *data, int bytes, char **dest)
{
   E_Ipc_Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->str;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_str_enc(const char *str, int *size_ret)
{
   E_Ipc_Str dat;

   dat.str = (char *)str;
   return eet_data_descriptor_encode(_e_ipc_str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_2str_dec(char *data, int bytes, E_Ipc_2Str **dest)
{
   E_Ipc_2Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_2str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_2str_enc(const char *str1, const char *str2, int *size_ret)
{
   E_Ipc_2Str dat;

   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   return eet_data_descriptor_encode(_e_ipc_2str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_2str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_2str_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_2str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_2str_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;
   Eina_List *list = NULL, *l;
   E_Ipc_Str *str_node;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_list_edd, data, bytes);
   if (!dat) return 0;
   EINA_LIST_FOREACH(dat->list, l, str_node)
     {
        list = eina_list_append(list, str_node->str);
     }
   if (dest) *dest = list;
   E_FREE_LIST(dat->list, free);
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   Eina_List *l;
   E_Ipc_Str *str_node;
   char *str;
   void *data;

   dat.list = NULL;
   EINA_LIST_FOREACH(list, l, str)
     {
        str_node = malloc(sizeof(E_Ipc_Str));
        str_node->str = str;
        dat.list = eina_list_append(dat.list, str_node);
     }
   data = eet_data_descriptor_encode(_e_ipc_str_list_edd, &dat, size_ret);
   E_FREE_LIST(dat.list, free);
   return data;
}

E_API int
e_ipc_codec_str_int_dec(char *data, int bytes, E_Ipc_Str_Int **dest)
{
   E_Ipc_Str_Int *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_int_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_str_int_enc(const char *str, int val, int *size_ret)
{
   E_Ipc_Str_Int dat;

   dat.str = (char *)str;
   dat.val = val;
   return eet_data_descriptor_encode(_e_ipc_str_int_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_str_int_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_int_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_str_int_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_str_int_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_2str_int_dec(char *data, int bytes, E_Ipc_2Str_Int **dest)
{
   E_Ipc_2Str_Int *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_2str_int_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_2str_int_enc(const char *str1, const char *str2, int val, int *size_ret)
{
   E_Ipc_2Str_Int dat;

   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   dat.val = val;
   return eet_data_descriptor_encode(_e_ipc_2str_int_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_2str_int_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_2str_int_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_2str_int_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_2str_int_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_4int_2str_dec(char *data, int bytes, E_Ipc_4Int_2Str **dest)
{
   E_Ipc_4Int_2Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_4int_2str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_4int_2str_enc(int val1, int val2, int val3, int val4, const char *str1, const char *str2, int *size_ret)
{
   E_Ipc_4Int_2Str dat;

   dat.val1 = val1;
   dat.val2 = val2;
   dat.val3 = val3;
   dat.val4 = val4;
   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   return eet_data_descriptor_encode(_e_ipc_4int_2str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_4int_2str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_4int_2str_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_4int_2str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_4int_2str_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_5int_2str_dec(char *data, int bytes, E_Ipc_5Int_2Str **dest)
{
   E_Ipc_5Int_2Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_5int_2str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_5int_2str_enc(int val1, int val2, int val3, int val4, int val5, const char *str1, const char *str2, int *size_ret)
{
   E_Ipc_5Int_2Str dat;

   dat.val1 = val1;
   dat.val2 = val2;
   dat.val3 = val3;
   dat.val4 = val4;
   dat.val5 = val5;
   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   return eet_data_descriptor_encode(_e_ipc_5int_2str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_5int_2str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_5int_2str_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_5int_2str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_5int_2str_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_3int_4str_dec(char *data, int bytes, E_Ipc_3Int_4Str **dest)
{
   E_Ipc_3Int_4Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_3int_4str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_3int_4str_enc(int val1, int val2, int val3, const char *str1, const char *str2, const char *str3, const char *str4, int *size_ret)
{
   E_Ipc_3Int_4Str dat;

   dat.val1 = val1;
   dat.val2 = val2;
   dat.val3 = val3;
   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   dat.str3 = (char *)str3;
   dat.str4 = (char *)str4;
   return eet_data_descriptor_encode(_e_ipc_3int_4str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_3int_4str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_3int_4str_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_3int_4str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_3int_4str_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_3int_3str_dec(char *data, int bytes, E_Ipc_3Int_3Str **dest)
{
   E_Ipc_3Int_3Str *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_3int_3str_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_3int_3str_enc(int val1, int val2, int val3, const char *str1, const char *str2, const char *str3, int *size_ret)
{
   E_Ipc_3Int_3Str dat;

   dat.val1 = val1;
   dat.val2 = val2;
   dat.val3 = val3;
   dat.str1 = (char *)str1;
   dat.str2 = (char *)str2;
   dat.str3 = (char *)str3;
   return eet_data_descriptor_encode(_e_ipc_3int_3str_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_3int_3str_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_3int_3str_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_3int_3str_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_3int_3str_list_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_str_4int_dec(char *data, int bytes, E_Ipc_Str_4Int **dest)
{
   E_Ipc_Str_4Int *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_4int_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat;
   return 1;
}

E_API void *
e_ipc_codec_str_4int_enc(const char *str1, int val1, int val2, int val3, int val4, int *size_ret)
{
   E_Ipc_Str_4Int dat;

   dat.str = (char *)str1;
   dat.val1 = val1;
   dat.val2 = val2;
   dat.val3 = val3;
   dat.val4 = val4;

   return eet_data_descriptor_encode(_e_ipc_str_4int_edd, &dat, size_ret);
}

E_API int
e_ipc_codec_str_4int_list_dec(char *data, int bytes, Eina_List **dest)
{
   E_Ipc_List *dat;

   if (!data) return 0;
   dat = eet_data_descriptor_decode(_e_ipc_str_4int_list_edd, data, bytes);
   if (!dat) return 0;
   if (dest) *dest = dat->list;
   free(dat);
   return 1;
}

E_API void *
e_ipc_codec_str_4int_list_enc(Eina_List *list, int *size_ret)
{
   E_Ipc_List dat;
   dat.list = list;
   return eet_data_descriptor_encode(_e_ipc_str_4int_list_edd, &dat, size_ret);
}

/* local subsystem globals */

#endif
#include "e.h"

/* FIXME: something is weird here - i had to reverse all stacking logic to make
 * it work... */

typedef struct _E_Smart_Data  E_Smart_Data;
typedef struct _E_Layout_Item E_Layout_Item;

struct _E_Smart_Data
{
   Evas_Coord    x, y, w, h;
   Evas_Coord    vw, vh;
   Evas_Object  *obj;
   Evas_Object  *clip;
   int           frozen;
   unsigned char changed E_BITFIELD;
   Eina_Inlist  *items;
};

struct _E_Layout_Item
{
   EINA_INLIST;
   E_Smart_Data *sd;
   Evas_Coord    x, y, w, h;
   Evas_Object  *obj;
};

/* local subsystem functions */
static E_Layout_Item *_e_layout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj);
static void           _e_layout_smart_disown(Evas_Object *obj);
static void           _e_layout_smart_item_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void           _e_layout_smart_reconfigure(E_Smart_Data *sd);
static void           _e_layout_smart_move_resize_item(E_Layout_Item *li);

static void           _e_layout_smart_init(void);
static void           _e_layout_smart_add(Evas_Object *obj);
static void           _e_layout_smart_del(Evas_Object *obj);
static void           _e_layout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void           _e_layout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void           _e_layout_smart_show(Evas_Object *obj);
static void           _e_layout_smart_hide(Evas_Object *obj);
static void           _e_layout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void           _e_layout_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void           _e_layout_smart_clip_unset(Evas_Object *obj);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_layout_add(Evas *evas)
{
   _e_layout_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API int
e_layout_freeze(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   sd->frozen++;
   return sd->frozen;
}

E_API int
e_layout_thaw(Evas_Object *obj)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERR(0);
   sd = evas_object_smart_data_get(obj);
   sd->frozen--;
   if (sd->frozen <= 0) _e_layout_smart_reconfigure(sd);
   return sd->frozen;
}

E_API void
e_layout_virtual_size_set(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (w < 1) w = 1;
   if (h < 1) h = 1;
   if ((sd->vw == w) && (sd->vh == h)) return;
   sd->vw = w;
   sd->vh = h;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_layout_smart_reconfigure(sd);
}

E_API void
e_layout_virtual_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   if (w) *w = sd->vw;
   if (h) *h = sd->vh;
}

E_API void
e_layout_coord_canvas_to_virtual(Evas_Object *obj, Evas_Coord cx, Evas_Coord cy, Evas_Coord *vx, Evas_Coord *vy)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);

   if (vx) *vx = (cx - sd->x) * ((double)(sd->vw) / sd->w);
   if (vy) *vy = (cy - sd->y) * ((double)(sd->vh) / sd->h);
}

E_API void
e_layout_coord_virtual_to_canvas(Evas_Object *obj, Evas_Coord vx, Evas_Coord vy, Evas_Coord *cx, Evas_Coord *cy)
{
   E_Smart_Data *sd;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);

   if (cx) *cx = vx * ((double)(sd->w) / sd->vw) + sd->x;
   if (cy) *cy = vy * ((double)(sd->h) / sd->vh) + sd->y;
}

E_API void
e_layout_pack(Evas_Object *obj, Evas_Object *child)
{
   E_Smart_Data *sd;
   E_Layout_Item *li;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR();
   sd = evas_object_smart_data_get(obj);
   li = _e_layout_smart_adopt(sd, child);
   sd->items = eina_inlist_append(sd->items, EINA_INLIST_GET(li));
   evas_object_lower(child);
   if (sd->frozen <= 0) _e_layout_smart_move_resize_item(li);
}

E_API void
e_layout_child_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   if ((li->x == x) && (li->y == y)) return;
   li->x = x;
   li->y = y;
   if (li->sd->frozen <= 0) _e_layout_smart_move_resize_item(li);
}

E_API Evas_Object *
e_layout_child_above_get(Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   EINA_SAFETY_ON_NULL_RETURN_VAL(li, NULL);
   li = (E_Layout_Item*)EINA_INLIST_GET(li)->next;
   return li ? li->obj : NULL;
}

E_API Evas_Object *
e_layout_child_below_get(Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   EINA_SAFETY_ON_NULL_RETURN_VAL(li, NULL);
   li = (E_Layout_Item*)EINA_INLIST_GET(li)->prev;
   return li ? li->obj : NULL;
}

E_API Evas_Object *
e_layout_top_child_get(Evas_Object *obj)
{
   E_Smart_Data *sd;
   E_Layout_Item *li;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR() NULL;
   sd = evas_object_smart_data_get(obj);
   if (!sd->items) return NULL;
   li = (E_Layout_Item*)sd->items->last;
   return li->obj;
}

E_API void
e_layout_child_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   if (w < 0) w = 0;
   if (h < 0) h = 0;
   if ((li->w == w) && (li->h == h)) return;
   li->w = w;
   li->h = h;
   if (li->sd->frozen <= 0) _e_layout_smart_move_resize_item(li);
}

E_API void
e_layout_child_lower(Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   if ((!li->sd->items) || (!EINA_INLIST_GET(li)->prev)) return;
   li->sd->items = eina_inlist_promote(li->sd->items, EINA_INLIST_GET(li));
   evas_object_lower(obj);
}

E_API void
e_layout_child_raise(Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   if ((!li->sd->items) || (!EINA_INLIST_GET(li)->next)) return;
   li->sd->items = eina_inlist_demote(li->sd->items, EINA_INLIST_GET(li));
   evas_object_raise(obj);
}

E_API void
e_layout_child_lower_below(Evas_Object *obj, Evas_Object *below)
{
   E_Layout_Item *li, *li2;

   EINA_SAFETY_ON_NULL_RETURN(obj);
   EINA_SAFETY_ON_NULL_RETURN(below);
   if (obj == below) return;
   li = evas_object_data_get(obj, "e_layout_data");
   li2 = evas_object_data_get(below, "e_layout_data");
   if ((!li) || (!li2) || (li->sd != li2->sd)) return;
   li->sd->items = eina_inlist_remove(li->sd->items, EINA_INLIST_GET(li));
   evas_object_stack_below(obj, below);
   li->sd->items = eina_inlist_prepend_relative(li->sd->items, EINA_INLIST_GET(li), EINA_INLIST_GET(li2));
}

E_API void
e_layout_child_raise_above(Evas_Object *obj, Evas_Object *above)
{
   E_Layout_Item *li, *li2;

   EINA_SAFETY_ON_NULL_RETURN(obj);
   EINA_SAFETY_ON_NULL_RETURN(above);
   if (obj == above) return;
   li = evas_object_data_get(obj, "e_layout_data");
   li2 = evas_object_data_get(above, "e_layout_data");
   if ((!li) || (!li2) || (li->sd != li2->sd)) return;
   li->sd->items = eina_inlist_remove(li->sd->items, EINA_INLIST_GET(li));
   evas_object_stack_above(obj, above);
   li->sd->items = eina_inlist_append_relative(li->sd->items, EINA_INLIST_GET(li), EINA_INLIST_GET(li2));
}

E_API void
e_layout_child_geometry_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y, Evas_Coord *w, Evas_Coord *h)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;

   if (x) *x = li->x;
   if (y) *y = li->y;
   if (w) *w = li->w;
   if (h) *h = li->h;
}

E_API void
e_layout_unpack(Evas_Object *obj)
{
   E_Layout_Item *li;
   E_Smart_Data *sd;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   sd = li->sd;
   sd->items = eina_inlist_remove(sd->items, EINA_INLIST_GET(li));
   _e_layout_smart_disown(obj);
}

E_API Eina_List *
e_layout_children_get(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Eina_List *l = NULL;
   E_Layout_Item *li;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR() NULL;
   sd = evas_object_smart_data_get(obj);
   EINA_INLIST_FOREACH(sd->items, li)
     l = eina_list_append(l, li->obj);
   return l;
}

E_API Evas_Object *
e_layout_top_child_at_xy_get(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Eina_Bool vis, const Eina_List *ignore)
{
   E_Smart_Data *sd;
   E_Layout_Item *li;

   if (evas_object_smart_smart_get(obj) != _e_smart) SMARTERRNR() NULL;
   sd = evas_object_smart_data_get(obj);
   if (!sd->items) return NULL;
   EINA_INLIST_REVERSE_FOREACH(sd->items, li)
     if (E_INSIDE(x, y, li->x, li->y, li->w, li->h))
       {
          if (eina_list_data_find(ignore, li->obj)) continue;
          if ((!vis) || evas_object_visible_get(li->obj))
            return li->obj;
       }
   return NULL;
}

/* local subsystem functions */
static E_Layout_Item *
_e_layout_smart_adopt(E_Smart_Data *sd, Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (li) e_layout_unpack(obj);
   li = calloc(1, sizeof(E_Layout_Item));
   if (!li) return NULL;
   li->sd = sd;
   li->obj = obj;
   /* defaults */
   li->x = 0;
   li->y = 0;
   li->w = 0;
   li->h = 0;
   evas_object_clip_set(obj, sd->clip);
   evas_object_smart_member_add(obj, li->sd->obj);
   evas_object_data_set(obj, "e_layout_data", li);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_FREE,
                                  _e_layout_smart_item_del_hook, NULL);
   if ((!evas_object_visible_get(sd->clip)) &&
       (evas_object_visible_get(sd->obj)))
     evas_object_show(sd->clip);
   return li;
}

static void
_e_layout_smart_disown(Evas_Object *obj)
{
   E_Layout_Item *li;

   li = evas_object_data_get(obj, "e_layout_data");
   if (!li) return;
   if (!li->sd->items)
     {
        if (evas_object_visible_get(li->sd->clip))
          evas_object_hide(li->sd->clip);
     }
   evas_object_event_callback_del(obj,
                                  EVAS_CALLBACK_FREE,
                                  _e_layout_smart_item_del_hook);
   evas_object_smart_member_del(obj);
   evas_object_data_del(obj, "e_layout_data");
   free(li);
}

static void
_e_layout_smart_item_del_hook(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_layout_unpack(obj);
}

static void
_e_layout_smart_reconfigure(E_Smart_Data *sd)
{
   E_Layout_Item *li;

   if (!sd->changed) return;

   EINA_INLIST_FOREACH(sd->items, li)
     _e_layout_smart_move_resize_item(li);
   sd->changed = 0;
}

static void
_e_layout_smart_move_resize_item(E_Layout_Item *li)
{
   evas_object_move(li->obj,
                    li->sd->x + ((li->x * li->sd->w) / li->sd->vw),
                    li->sd->y + ((li->y * li->sd->h) / li->sd->vh));
   evas_object_resize(li->obj,
                      MAX((li->w * li->sd->w) / li->sd->vw, 1),
                      MAX((li->h * li->sd->h) / li->sd->vh, 1));
}

static void
_e_layout_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         "e_layout",
         EVAS_SMART_CLASS_VERSION,
         _e_layout_smart_add,
         _e_layout_smart_del,
         _e_layout_smart_move,
         _e_layout_smart_resize,
         _e_layout_smart_show,
         _e_layout_smart_hide,
         _e_layout_smart_color_set,
         _e_layout_smart_clip_set,
         _e_layout_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

static void
_e_layout_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   sd->obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->vw = 1;
   sd->vh = 1;
   sd->clip = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(sd->clip, obj);
   evas_object_move(sd->clip, -100001, -100001);
   evas_object_resize(sd->clip, 200002, 200002);
   evas_object_color_set(sd->clip, 255, 255, 255, 255);
   evas_object_smart_data_set(obj, sd);
}

static void
_e_layout_smart_del(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   while (sd->items)
     {
        E_Layout_Item *li = (E_Layout_Item*)sd->items;
        sd->items = eina_inlist_remove(sd->items, EINA_INLIST_GET(li));
        _e_layout_smart_disown(li->obj);
     }
   evas_object_del(sd->clip);
   free(sd);
}

static void
_e_layout_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Smart_Data *sd;
   E_Layout_Item *li;
   Evas_Coord dx, dy;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((x == sd->x) && (y == sd->y)) return;
   dx = x - sd->x;
   dy = y - sd->y;
   EINA_INLIST_FOREACH(sd->items, li)
     {
        Evas_Coord ox, oy;

        evas_object_geometry_get(li->obj, &ox, &oy, NULL, NULL);
        evas_object_move(li->obj, ox + dx, oy + dy);
     }
   sd->x = x;
   sd->y = y;
}

static void
_e_layout_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((w == sd->w) && (h == sd->h)) return;
   sd->w = w;
   sd->h = h;
   sd->changed = 1;
   if (sd->frozen <= 0) _e_layout_smart_reconfigure(sd);
}

static void
_e_layout_smart_show(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->items) evas_object_show(sd->clip);
}

static void
_e_layout_smart_hide(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_hide(sd->clip);
}

static void
_e_layout_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_color_set(sd->clip, r, g, b, a);
}

static void
_e_layout_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_set(sd->clip, clip);
}

static void
_e_layout_smart_clip_unset(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_clip_unset(sd->clip);
}

#include "e.h"

#define SMART_NAME     "e_livethumb"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;
typedef struct _E_Smart_Item E_Smart_Item;

struct _E_Smart_Data
{
   Evas_Coord   x, y, w, h;

   Evas_Object *smart_obj;
   Evas_Object *evas_obj;
   Evas_Object *thumb_obj;
   Evas        *evas;
   Ecore_Evas  *ee;
   Evas_Coord   vw, vh;
};

/* local subsystem functions */
static void _e_smart_reconfigure(E_Smart_Data *sd);
static void _e_smart_add(Evas_Object *obj);
static void _e_smart_del(Evas_Object *obj);
static void _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _e_smart_show(Evas_Object *obj);
static void _e_smart_hide(Evas_Object *obj);
static void _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void _e_smart_clip_unset(Evas_Object *obj);
static void _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_livethumb_add(Evas *e)
{
   _e_smart_init();
   return evas_object_smart_add(e, _e_smart);
}

E_API Evas *
e_livethumb_evas_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->evas;
}

E_API void
e_livethumb_vsize_set(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   API_ENTRY return;
   if ((w == sd->vw) && (h == sd->vh)) return;
   sd->vw = w;
   sd->vh = h;
   evas_object_image_size_set(sd->evas_obj, sd->vw, sd->vh);
   if (sd->thumb_obj) evas_object_resize(sd->thumb_obj, sd->vw, sd->vh);
}

E_API void
e_livethumb_vsize_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   API_ENTRY return;
   if (w) *w = sd->vw;
   if (h) *h = sd->vh;
}

static void
_e_livethumb_edje_preloaded(void *data EINA_UNUSED, Evas_Object *obj,
                            const char *emission EINA_UNUSED,
                            const char *source EINA_UNUSED)
{
   evas_object_show(obj);
}

E_API void
e_livethumb_thumb_set(Evas_Object *obj, Evas_Object *thumb)
{
   API_ENTRY return;
   if (!thumb)
     {
        sd->thumb_obj = NULL;
        return;
     }
   sd->thumb_obj = thumb;
   if (!strcmp(evas_object_type_get(thumb), "edje"))
     {
        edje_object_signal_callback_add(thumb,
                                        "preload,done", "",
                                        _e_livethumb_edje_preloaded, NULL);
        edje_object_preload(thumb, EINA_FALSE);
     }
   else
     {
        evas_object_show(sd->thumb_obj);
     }
   evas_object_move(sd->thumb_obj, 0, 0);
   evas_object_resize(sd->thumb_obj, sd->vw, sd->vh);
}

E_API Evas_Object *
e_livethumb_thumb_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->thumb_obj;
}

/* local subsystem functions */

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   evas_object_move(sd->evas_obj, sd->x, sd->y);
   evas_object_resize(sd->evas_obj, sd->w, sd->h);
   evas_object_image_fill_set(sd->evas_obj, 0, 0, sd->w, sd->h);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->vw = 1;
   sd->vh = 1;

   sd->evas_obj = ecore_evas_object_image_new(ecore_evas_ecore_evas_get(evas_object_evas_get(obj)));
   sd->ee = evas_object_data_get(sd->evas_obj, "Ecore_Evas");
   ecore_evas_alpha_set(sd->ee, 1);
   evas_object_smart_member_add(sd->evas_obj, obj);
   evas_object_image_size_set(sd->evas_obj, sd->vw, sd->vh);
   sd->evas = ecore_evas_get(sd->ee);
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_del(sd->evas_obj);
   free(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   if ((sd->x == x) && (sd->y == y)) return;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   if ((sd->w == w) && (sd->h == h)) return;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->evas_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->evas_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->evas_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->evas_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->evas_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#include "e.h"

EINTERN int e_log_dom = -1;

static const char *_names[] = {
   "CRI",
   "ERR",
   "WRN",
   "INF",
   "DBG",
};

static void  EINA_PRINTF(6, 0)
_e_log_cb(const Eina_Log_Domain *d, Eina_Log_Level level, const char *file, const char *fnc EINA_UNUSED, int line, const char *fmt, void *data EINA_UNUSED, va_list args)
{
   const char *color;

   color = eina_log_level_color_get(level);
   fprintf(stdout,
           "%s%s<" EINA_COLOR_RESET "%s%s>" EINA_COLOR_RESET "%s:%d" EINA_COLOR_RESET " ",
           color, _names[level > EINA_LOG_LEVEL_DBG ? EINA_LOG_LEVEL_DBG : level],
           d->domain_str, color, file, line);
   vfprintf(stdout, fmt, args);
   putc('\n', stdout);
}

EINTERN int
e_log_init(void)
{
   const char *bt_level;

   e_log_dom = eina_log_domain_register("e", EINA_COLOR_WHITE);
   bt_level = getenv("EINA_LOG_BACKTRACE");
   if (bt_level)
     {
        int level;

        level = strtol(bt_level, NULL, 10);
        if (level < 1)
          eina_log_print_cb_set(_e_log_cb, NULL);
     }
   if (getenv("E_ABORT"))
     eina_log_abort_on_critical_set(1);
   return e_log_dom != -1;
}

EINTERN int
e_log_shutdown(void)
{
   eina_log_domain_unregister(e_log_dom);
   e_log_dom = -1;
   return 0;
}

#include "e.h"
#ifdef __linux__
# include <sys/prctl.h>
#endif

#include "valgrind.h"

#define MAX_LEVEL 80

#define TS_DO
#ifdef TS_DO
# define TS(x)                                                    \
  {                                                               \
     t1 = ecore_time_unix_get();                                  \
     printf("ESTART: %1.5f [%1.5f] - %s\n", t1 - t0, t1 - t2, x); \
     t2 = t1;                                                     \
  }
static double t0, t1, t2;
#else
# define TS(x)
#endif

/*
 * i need to make more use of these when i'm baffled as to when something is
 * up. other hooks:
 *
 *      void *(*__malloc_hook)(size_t size, const void *caller);
 *
 *      void *(*__realloc_hook)(void *ptr, size_t size, const void *caller);
 *
 *      void *(*__memalign_hook)(size_t alignment, size_t size,
 *                               const void *caller);
 *
 *      void (*__free_hook)(void *ptr, const void *caller);
 *
 *      void (*__malloc_initialize_hook)(void);
 *
 *      void (*__after_morecore_hook)(void);
 *

   static void my_init_hook(void);
   static void my_free_hook(void *p, const void *caller);

   static void (*old_free_hook)(void *ptr, const void *caller) = NULL;
   void (*__free_hook)(void *ptr, const void *caller);

   void (*__malloc_initialize_hook) (void) = my_init_hook;
   static void
   my_init_hook(void)
   {
   old_free_hook = __free_hook;
   __free_hook = my_free_hook;
   }

   //void *magicfree = NULL;

   static void
   my_free_hook(void *p, const void *caller)
   {
   __free_hook = old_free_hook;
   //   if ((p) && (p == magicfree))
   //     {
   //	printf("CAUGHT!!!!! %p ...\n", p);
   //	abort();
   //     }
   free(p);
   __free_hook = my_free_hook;
   }
 */

/* local function prototypes */
static void      _e_main_shutdown(int errcode);
static void      _e_main_shutdown_push(int (*func)(void));
static void      _e_main_parse_arguments(int argc, char **argv);
static Eina_Bool _e_main_cb_signal_exit(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev EINA_UNUSED);
static Eina_Bool _e_main_cb_signal_hup(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev EINA_UNUSED);
static Eina_Bool _e_main_cb_signal_user(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev);
static int       _e_main_dirs_init(void);
static int       _e_main_dirs_shutdown(void);
static int       _e_main_path_init(void);
static int       _e_main_path_shutdown(void);
static int       _e_main_screens_init(void);
static int       _e_main_screens_shutdown(void);
static void      _e_main_desk_save(void);
static void      _e_main_desk_restore(void);
static void      _e_main_efreet_paths_init(void);
static void      _e_main_modules_load(Eina_Bool safe_mode);
static Eina_Bool _e_main_cb_idle_before(void *data EINA_UNUSED);
static Eina_Bool _e_main_cb_idle_after(void *data EINA_UNUSED);
static Eina_Bool _e_main_cb_startup_fake_end(void *data EINA_UNUSED);

/* local variables */
static Eina_Bool really_know = EINA_FALSE;
static Eina_Bool locked = EINA_FALSE;
static Eina_Bool inloop = EINA_FALSE;
static jmp_buf x_fatal_buff;

static int _e_main_lvl = 0;
static int(*_e_main_shutdown_func[MAX_LEVEL]) (void);

static Ecore_Idle_Enterer *_idle_before = NULL;
static Ecore_Idle_Enterer *_idle_after = NULL;

static Ecore_Event_Handler *mod_init_end = NULL;

/* external variables */
E_API Eina_Bool e_precache_end = EINA_FALSE;
E_API Eina_Bool x_fatal = EINA_FALSE;
E_API Eina_Bool good = EINA_FALSE;
E_API Eina_Bool evil = EINA_FALSE;
E_API Eina_Bool starting = EINA_TRUE;
E_API Eina_Bool stopping = EINA_FALSE;
E_API Eina_Bool restart = EINA_FALSE;
E_API Eina_Bool e_nopause = EINA_FALSE;
E_API Eina_Bool after_restart = EINA_FALSE;
E_API Eina_Bool e_main_loop_running = EINA_FALSE;
EINTERN const char *e_first_frame = NULL;
EINTERN double e_first_frame_start_time = -1;

static Eina_Bool
_xdg_check_str(const char *env, const char *str)
{
   const char *p;
   size_t len;

   len = strlen(str);
   for (p = strstr(env, str); p; p++, p = strstr(p, str))
     {
        if ((!p[len]) || (p[len] == ':')) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
_xdg_data_dirs_augment(void)
{
   const char *s;
   const char *p = e_prefix_get();
   char newpath[PATH_MAX], buf[PATH_MAX + PATH_MAX + 200];

   if (!p) return;

   s = getenv("XDG_DATA_DIRS");
   if (s)
     {
        Eina_Bool pfxdata, pfx;

        pfxdata = !_xdg_check_str(s, e_prefix_data_get());
        snprintf(newpath, sizeof(newpath), "%s/share", p);
        pfx = !_xdg_check_str(s, newpath);
        if (pfxdata || pfx)
          {
             snprintf(buf, sizeof(buf), "%s%s%s%s%s",
               pfxdata ? e_prefix_data_get() : "",
               pfxdata ? ":" : "",
               pfx ? newpath : "",
               pfx ? ":" : "",
               s);
             e_util_env_set("XDG_DATA_DIRS", buf);
          }
     }
   else
     {
        snprintf(buf, sizeof(buf), "%s:%s/share:/usr/local/share:/usr/share", e_prefix_data_get(), p);
        e_util_env_set("XDG_DATA_DIRS", buf);
     }

   s = getenv("XDG_CONFIG_DIRS");
   snprintf(newpath, sizeof(newpath), "%s/etc/xdg", p);
   if (s)
     {
        if (!_xdg_check_str(s, newpath))
          {
             snprintf(buf, sizeof(buf), "%s:%s", newpath, s);
             e_util_env_set("XDG_CONFIG_DIRS", buf);
          }
     }
   else
     {
        snprintf(buf, sizeof(buf), "%s:/etc/xdg", newpath);
        e_util_env_set("XDG_CONFIG_DIRS", buf);
     }

   // N.B. XDG_RUNTIME_DIR is taken care of in e_start

   /* set menu prefix so we get our e menu */
   if (!getenv("XDG_MENU_PREFIX"))
     {
        e_util_env_set("XDG_MENU_PREFIX", "e-");
     }
}

static Eina_Bool
_precache_file(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data EINA_UNUSED, void *fdata)
{
   Eina_List **precache_files = fdata;
   *precache_files = eina_list_append(*precache_files, strdup(key));
   return EINA_TRUE;
}

static void *
_precache_thread(void *data, Eina_Thread thr EINA_UNUSED)
{
   Eina_List *precache_files = data;
   char *path;
   double t = ecore_time_get();
   unsigned int sum = 0;
   unsigned int reads = 0;


   printf("PRECACHE: BEGIN\n");
   EINA_LIST_FREE(precache_files, path)
     {
        double tt = ecore_time_get();
        FILE *f = fopen(path, "r");
        unsigned char buf[4096];

        if (f)
          {
             size_t sz;

             while ((sz = fread(buf, 1, sizeof(buf), f)) > 0)
               {
                  reads ++;
                  sum = ((sum << 1) ^ buf[0]) + reads;
               }
             fclose(f);
          }
        printf("PRECACHE: [%1.5f] [%s] DONE\n", ecore_time_get() - tt, path);
        free(path);
     }
   printf("PRECACHE: TOTAL [%1.5f]\n", ecore_time_get() - t);
   printf("PRECACHE: SUM=%08x, READS=%i\n", sum, reads);
   return NULL;
}

/* externally accessible functions */
int
main(int argc, char **argv)
{
   Eina_Bool nostartup = EINA_FALSE;
   Eina_Bool safe_mode = EINA_FALSE;
   Eina_Bool waslocked = EINA_FALSE;
   Eina_Stringshare *strshare;
   char *s = NULL;
   struct sigaction action;

#ifdef __linux__
# ifdef PR_SET_PTRACER
#  ifdef PR_SET_PTRACER_ANY
   prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
#  endif
# endif
#endif

#ifdef TS_DO
   t0 = t1 = t2 = ecore_time_unix_get();
#endif
   TS("Begin Startup");

   /* trap deadly bug signals and allow some form of sane recovery */
   /* or ability to gdb attach and debug at this point - better than your */
   /* wm/desktop vanishing and not knowing what happened */
   if (!getenv("NOTIFY_SOCKET"))
     {
        TS("Signal Trap");
        action.sa_sigaction = e_sigseg_act;
	action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
	sigemptyset(&action.sa_mask);
	sigaction(SIGSEGV, &action, NULL);

	action.sa_sigaction = e_sigill_act;
	action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
	sigemptyset(&action.sa_mask);
	sigaction(SIGILL, &action, NULL);

	action.sa_sigaction = e_sigfpe_act;
	action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
	sigemptyset(&action.sa_mask);
	sigaction(SIGFPE, &action, NULL);

#ifndef HAVE_WAYLAND_ONLY
	action.sa_sigaction = e_sigbus_act;
	action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
	sigemptyset(&action.sa_mask);
	sigaction(SIGBUS, &action, NULL);
#endif

	action.sa_sigaction = e_sigabrt_act;
	action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
	sigemptyset(&action.sa_mask);
	sigaction(SIGABRT, &action, NULL);
	TS("Signal Trap Done");
     }

   TS("Eina Init");
   if (!eina_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Eina!\n"));
        _e_main_shutdown(101);
     }
   eina_file_statgen_enable();

   _e_main_shutdown_push(eina_shutdown);

   if (!e_log_init())
     {
        e_error_message_show(_("Enlightenment could not create a logging domain!\n"));
        _e_main_shutdown(101);
     }
#ifdef TS_DO
#undef TS
# define TS(x)                                                    \
  {                                                               \
     t1 = ecore_time_unix_get();                                  \
     printf("ESTART: %1.5f [%1.5f] - %s\n", t1 - t0, t1 - t2, x); \
     t2 = t1;                                                     \
  }
#endif
   TS("Eina Init Done");
   _e_main_shutdown_push(e_log_shutdown);

   TS("Determine Prefix");
   if (!e_prefix_determine(argv[0]))
     {
        fprintf(stderr,
                "ERROR: Enlightenment cannot determine it's installed\n"
                "       prefix from the system or argv[0].\n"
                "       This is because it is not on Linux AND has been\n"
                "       executed strangely. This is unusual.\n");
     }
   TS("Determine Prefix Done");

   /* Eio's eio_init internally calls efreet_init. Set XDG_MENU_PREFIX here      */
   /* else efreet's efreet_menu_prefix symbol is set erroneously during eio_init. */
   _xdg_data_dirs_augment();

   /* for debugging by redirecting stdout of e to a log file to tail */
   setvbuf(stdout, NULL, _IONBF, 0);

   TS("Environment Variables");
   if (getenv("E_RESTART")) after_restart = EINA_TRUE;
   if (getenv("DESKTOP_STARTUP_ID"))
     e_util_env_set("DESKTOP_STARTUP_ID", NULL);
   e_util_env_set("E_RESTART_OK", NULL);
   e_util_env_set("PANTS", "ON");
   e_util_env_set("DESKTOP", "Enlightenment");
   e_util_env_set("XDG_CURRENT_DESKTOP", "Enlightenment");
   if (getenv("E_ALERT_FONT_DIR"))
     e_util_env_set("E_ALERT_FONT_DIR", NULL);
   if (getenv("E_ALERT_SYSTEM_BIN"))
     e_util_env_set("E_ALERT_SYSTEM_BIN", NULL);
   strshare = eina_stringshare_printf("%s/enlightenment_askpass",
                                      e_prefix_bin_get());
   if (strshare)
     {
        e_util_env_set("SUDO_ASKPASS", strshare);
        e_util_env_set("SSH_ASKPASS", strshare);
        eina_stringshare_del(strshare);
     }
   TS("Environment Variables Done");

   TS("Parse Arguments");
   _e_main_parse_arguments(argc, argv);
   TS("Parse Arguments Done");

   /*** Initialize Core EFL Libraries We Need ***/

   TS("Eet Init");
   if (!eet_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Eet!\n"));
        _e_main_shutdown(101);
     }
   TS("Eet Init Done");
   _e_main_shutdown_push(eet_shutdown);

   TS("Ecore Init");
   if (!ecore_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Ecore!\n"));
        _e_main_shutdown(101);
     }
   TS("Ecore Init Done");
   _e_main_shutdown_push(ecore_shutdown);

   TS("E Comp Canvas Intercept Init");
   e_comp_canvas_intercept();
   TS("E Comp Canvas Intercept Init Done");

   e_first_frame = getenv("E_FIRST_FRAME");
   if (e_first_frame && e_first_frame[0])
     e_first_frame_start_time = ecore_time_get();
   else
     e_first_frame = NULL;

   TS("EFX Init");
   if (!e_efx_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize EFX!\n"));
        _e_main_shutdown(101);
     }
   TS("EFX Init Done");
   _e_main_shutdown_push((void*)e_efx_shutdown);

   TS("EIO Init");
   if (!eio_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize EIO!\n"));
        _e_main_shutdown(101);
     }
   TS("EIO Init Done");
   _e_main_shutdown_push(eio_shutdown);

   ecore_app_args_set(argc, (const char **)argv);

   TS("Ecore Event Handlers");
   if (!ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT,
                                _e_main_cb_signal_exit, NULL))
     {
        e_error_message_show(_("Enlightenment cannot set up an exit signal handler.\n"
                               "Perhaps you are out of memory?"));
        _e_main_shutdown(101);
     }
   if (!ecore_event_handler_add(ECORE_EVENT_SIGNAL_HUP,
                                _e_main_cb_signal_hup, NULL))
     {
        e_error_message_show(_("Enlightenment cannot set up a HUP signal handler.\n"
                               "Perhaps you are out of memory?"));
        _e_main_shutdown(101);
     }
   if (!ecore_event_handler_add(ECORE_EVENT_SIGNAL_USER,
                                _e_main_cb_signal_user, NULL))
     {
        e_error_message_show(_("Enlightenment cannot set up a USER signal handler.\n"
                               "Perhaps you are out of memory?"));
        _e_main_shutdown(101);
     }
   TS("Ecore Event Handlers Done");

   TS("Ecore_File Init");
   if (!ecore_file_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Ecore_File!\n"));
        _e_main_shutdown(101);
     }
   TS("Ecore_File Init Done");
   _e_main_shutdown_push(ecore_file_shutdown);

   TS("Ecore_Con Init");
   if (!ecore_con_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Ecore_Con!\n"));
        _e_main_shutdown(101);
     }
   TS("Ecore_Con Init Done");
   _e_main_shutdown_push(ecore_con_shutdown);

   TS("Ecore_Ipc Init");
   if (!ecore_ipc_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Ecore_Ipc!\n"));
        _e_main_shutdown(101);
     }
   TS("Ecore_Ipc Init Done");
   _e_main_shutdown_push(ecore_ipc_shutdown);

   _idle_before = ecore_idle_enterer_before_add(_e_main_cb_idle_before, NULL);

   TS("Ecore_Evas Init");
   if (!ecore_evas_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Ecore_Evas!\n"));
        _e_main_shutdown(101);
     }
   TS("Ecore_Evas Init Done");
//   _e_main_shutdown_push(ecore_evas_shutdown);

   TS("Elementary Init");
   if (!elm_init(argc, argv))
     {
        e_error_message_show(_("Enlightenment cannot initialize Elementary!\n"));
        _e_main_shutdown(101);
     }
   TS("Elementary Init Done");
   //_e_main_shutdown_push(elm_shutdown);

   TS("Emotion Init");
   if (!emotion_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize Emotion!\n"));
        _e_main_shutdown(101);
     }
   TS("Emotion Init Done");
   /* triggers event flush: do not call */
   //_e_main_shutdown_push((void *)emotion_shutdown);

   /* e doesn't sync to compositor - it should be one */
   ecore_evas_app_comp_sync_set(0);

   TS("Ecore_Evas Engine Check");
#ifdef HAVE_WAYLAND_ONLY
   if (!ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_WAYLAND_SHM))
     {
        e_error_message_show(_("Enlightenment found ecore_evas doesn't support the Wayland SHM\n"
                               "rendering in Evas. Please check your installation of Evas and\n"
                                "Ecore and check they support the Wayland SHM rendering engine."));
        _e_main_shutdown(101);
     }
#else
   if (!ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_SOFTWARE_XCB))
     {
        if (!ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_SOFTWARE_XLIB))
          {
             e_error_message_show(_("Enlightenment found ecore_evas doesn't support the Software X11\n"
                                    "rendering in Evas. Please check your installation of Evas and\n"
                                    "Ecore and check they support the Software X11 rendering engine."));
             _e_main_shutdown(101);
          }
     }
#endif
   if (!ecore_evas_engine_type_supported_get(ECORE_EVAS_ENGINE_SOFTWARE_BUFFER))
     {
        e_error_message_show(_("Enlightenment found ecore_evas doesn't support the Software Buffer\n"
                               "rendering in Evas. Please check your installation of Evas and\n"
                               "Ecore and check they support the Software Buffer rendering engine."));
        _e_main_shutdown(101);
     }
   TS("Ecore_Evas Engine Check Done");

   edje_freeze();

   /*** Initialize E Subsystems We Need ***/

   TS("E Intl Init");
   if (!e_intl_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize E_Intl!\n"));
        _e_main_shutdown(101);
     }
   TS("E Intl Init Done");
   _e_main_shutdown_push(e_intl_shutdown);

#ifndef HAVE_WAYLAND_ONLY
   /* init white box of death alert */
   TS("E_Alert Init");
   if (!e_alert_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize its emergency alert system.\n"
                               "Have you set your DISPLAY variable?"));
        _e_main_shutdown(101);
     }
   TS("E_Alert Init Done");
   _e_main_shutdown_push(e_alert_shutdown);
#endif

   TS("E Directories Init");
   /* setup directories we will be using for configurations storage etc. */
   if (!_e_main_dirs_init())
     {
        e_error_message_show(_("Enlightenment cannot create directories in your home directory.\n"
                               "Perhaps you have no home directory or the disk is full?"));
        _e_main_shutdown(101);
     }
   TS("E Directories Init Done");
   _e_main_shutdown_push(_e_main_dirs_shutdown);

   TS("E_Filereg Init");
   if (!e_filereg_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its file registry system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Filereg Init Done");
   _e_main_shutdown_push(e_filereg_shutdown);

   TS("E_Config Init");
   if (!e_config_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its config system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Config Init Done");
   _e_main_shutdown_push(e_config_shutdown);

   if (e_config->xsettings.match_e17_theme)
     {

        /* KDE5 applications don't understand anything other then gnome or kde     */
        /* They expect everyone else to set QT_QPA_PLATFORMTHEME to tell them how  */
        /* to theme there apps otherwise they use a fallback mode which results in */
        /* missing icons and a inability to change the appearance of applications  */
        /* see https://bugzilla.suse.com/show_bug.cgi?id=920792 for more info.     */
        /* There are two sensible defaults for this variable, "kde" which will     */
        /* make apps appear the same as they do if they are run in kde. and gtk2   */
        /* which will make kde applications follow the gtk/gnome theme, we have    */
        /* decided on choosing gtk2 as it means that kde/qt apps will follow the   */
        /* app and icon theme set in the enlightenment settings dialog. Some users */
        /* who wish to use Qt apps without any gnome or gtk usage may choose to    */
        /* install qt5ct and overwrite this variable with qt5ct and use that to    */
        /* configure there Qt5 applications.                                       */
        e_util_env_set("QT_QPA_PLATFORMTHEME", "gtk2");
        e_util_env_set("QT_STYLE_OVERRIDE", "gtk2");
     }
   // make fonts NOT BLURRY. after 35 (v 38, v40 of interpreter) fonts become
   // horizontally blurry - they seemingly want the interpreter for hinting to
   // sub-pixel position on sub-pixel boundaries. this ends up with blurry
   // horizontal positioning/hinting that is on a sub-pixel. yes - this
   // requires logging out and logging in to get e to not set this env var.
   // for now that's good enough. the aim is to get everyone to render the
   // same way and this does it. efl, gtk, qt, chromium, firtefox, ...
   if (e_config->scale.set_xapp_dpi)
     {
        s = getenv("FREETYPE_PROPERTIES");
        if (!s)
          {
             e_util_env_set("FREETYPE_PROPERTIES", "truetype:interpreter-version=35");
          }
     }

   TS("E_Env Init");
   if (!e_env_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its environment.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Env Init Done");
   _e_main_shutdown_push(e_env_shutdown);

   efreet_desktop_environment_set(e_config->desktop_environment);
   e_util_env_set("E_ICON_THEME", e_config->icon_theme);
   ecore_exe_run_priority_set(e_config->priority);
   locked |= e_config->desklock_start_locked;

   s = getenv("E_DESKLOCK_LOCKED");
   if ((s) && (!strcmp(s, "locked"))) waslocked = EINA_TRUE;
   putenv("E_DESKLOCK_LOCKED=");

   TS("E Paths Init");
   if (!_e_main_path_init())
     {
        e_error_message_show(_("Enlightenment cannot set up paths for finding files.\n"
                               "Perhaps you are out of memory?"));
        _e_main_shutdown(101);
     }
   TS("E Paths Init Done");
   _e_main_shutdown_push(_e_main_path_shutdown);

   TS("E_Precache");
   if (!getenv("E_NO_PRECACHE"))
     {
        const Eina_List *l;
        const Eina_List *theme_items = elm_theme_list_get(NULL);
        Eina_List *precache_files = NULL;
        Eina_Hash *files = eina_hash_string_superfast_new(NULL);
        Eina_Thread thr;
        int scr, dx, dy;
        // find all theme edj files to precache
        EINA_LIST_FOREACH(theme_items, l, s)
          {
             Eina_Bool search = EINA_FALSE;
             char *path = elm_theme_list_item_path_get(s, &search);
             if (path)
               {
                  eina_hash_del(files, path, files);
                  eina_hash_add(files, path, files);
                  free(path);
               }
          }
        // go over the first 4 screens and all desks and find possible
        // background files and add them to our hash to precache
        for (scr = 0; scr < 4; scr++)
          {
             for (dy = 0; dy < e_config->zone_desks_y_count; dy++)
               {
                  for (dx = 0; dx < e_config->zone_desks_x_count; dx++)
                    {
                       const char *bgfile = e_bg_file_get(scr, dx, dy);
                       eina_hash_del(files, bgfile, files);
                       eina_hash_add(files, bgfile, files);
                       eina_stringshare_del(bgfile);
                    }
               }
          }
        eina_hash_foreach(files, _precache_file, &precache_files);
        eina_hash_free(files);
        printf("PRECACHE: SPAWN\n");
        if (!eina_thread_create(&thr, EINA_THREAD_BACKGROUND, -1,
                                _precache_thread, precache_files))
          {
             ERR("Can't spawn file precache thread");
          }
     }
   TS("E_Precache Done");

   TS("E_Ipc Init");
   if (!e_ipc_init()) _e_main_shutdown(101);
   TS("E_Ipc Init Done");
   _e_main_shutdown_push(e_ipc_shutdown);

   TS("E_Font Init");
   if (!e_font_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its font system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Font Init Done");
   _e_main_shutdown_push(e_font_shutdown);

   TS("E_Font Apply");
   e_font_apply();
   TS("E_Font Apply Done");

   TS("E_Theme Init");
   if (!e_theme_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its theme system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Theme Init Done");
   _e_main_shutdown_push(e_theme_shutdown);

   TS("E_Moveresize Init");
   e_moveresize_init();
   TS("E_Moveresize Init Done");
   _e_main_shutdown_push(e_moveresize_shutdown);

   TS("E_Msgbus Init");
   if (!e_msgbus_init())
     {
        if (!getenv("E_NO_DBUS_SESSION"))
          {
             e_error_message_show(_("Enlightenment cannot create a dbus session connection.\n"
                                    "At best this will break many things, at worst it will hard lock your machine.\n"
                                    "If you're sure you know what you're doing, export E_NO_DBUS_SESSION=1"));
             _e_main_shutdown(101);
          }
     }
   else
     _e_main_shutdown_push(e_msgbus_shutdown);
   TS("E_Msgbus Init Done");

   TS("Efreet Init");
   if (!efreet_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the FDO desktop system.\n"
                               "Perhaps you lack permissions on ~/.cache/efreet or are\n"
                               "out of memory or disk space?"));
        _e_main_shutdown(101);
     }
   TS("Efreet Init Done");
   _e_main_shutdown_push(efreet_shutdown);

   TS("E_Intl Post Init");
   if (!e_intl_post_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its intl system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Intl Post Init Done");
   _e_main_shutdown_push(e_intl_post_shutdown);

   TS("E_Configure Init");
   e_configure_init();
   TS("E_Configure Init Done");

   e_screensaver_preinit();

   TS("E_Actions Init");
   if (!e_actions_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its actions system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Actions Init Done");
   _e_main_shutdown_push(e_actions_shutdown);

   /* these just add event handlers and can't fail
    * timestamping them is dumb.
    */
   e_zone_init();
   e_desk_init();
   e_exehist_init();

   TS("E_System Init");
   if (!e_system_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the Privilege System access system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_System Init Done");
   _e_main_shutdown_push(e_system_shutdown);

   TS("E_Powersave Init");
   if (!e_powersave_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its powersave modes.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Powersave Init Done");
   _e_main_shutdown_push(e_powersave_shutdown);

   TS("Screens Init");
   if (!_e_main_screens_init())
     {
        e_error_message_show(_("Enlightenment set up window management for all the screens on your system\n"
                               "failed. Perhaps another window manager is running?\n"));
        _e_main_shutdown(101);
     }
   TS("Screens Init Done");
   _e_main_shutdown_push(_e_main_screens_shutdown);

   TS("E_Pointer Init");
   if (!e_pointer_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its pointer system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Pointer Init Done");
   _e_main_shutdown_push(e_pointer_shutdown);
   e_menu_init();

   TS("E_Scale Init");
   if (!e_scale_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its scale system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Scale Init Done");
   _e_main_shutdown_push(e_scale_shutdown);

   TS("E_Splash Init");
   if (!e_init_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its init screen.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Splash Init Done");
   _e_main_shutdown_push(e_init_shutdown);
   if (!after_restart)
     {
        TS("E_Splash Show");
        e_init_show();
        TS("E_Splash Show Done");
     }

   TS("Add Icon Extensions");
   efreet_icon_extension_add(".svg");
   efreet_icon_extension_add(".svgz");
   efreet_icon_extension_add(".svg.gz");
   efreet_icon_extension_add(".jpg");
   efreet_icon_extension_add(".jpeg");
   efreet_icon_extension_add(".png");
   efreet_icon_extension_add(".edj");
   TS("Add Icon Extensions Done");

   TS("E_Acpi Init");
   e_acpi_init();
   TS("E_Acpi Init Done");
   _e_main_shutdown_push(e_acpi_shutdown);

   TS("E_Auth Init");
   e_auth_init();
   TS("E_Auth Init Done");
   _e_main_shutdown_push(e_auth_shutdown);

   TS("E_Backlight Init");
   if (!e_backlight_init())
     {
        e_error_message_show(_("Enlightenment cannot configure the backlight.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Backlight Init Done");

   TS("E_Dpms Init");
   if (!e_dpms_init())
     {
        e_error_message_show(_("Enlightenment cannot configure the DPMS settings.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Dpms Init Done");
   _e_main_shutdown_push(e_dpms_shutdown);

   TS("E_Desklock Init");
   if (!e_desklock_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its desk locking system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Desklock Init Done");
   _e_main_shutdown_push(e_desklock_shutdown);

   if (waslocked || (locked && ((!after_restart))))
     {
        e_desklock_show_manual(EINA_TRUE);
        e_screensaver_update();
     }

   TS("Efreet Paths");
   _e_main_efreet_paths_init();
   TS("Efreet Paths Done");

   TS("E_Sys Init");
   if (!e_sys_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the System Command system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Sys Init Done");
   _e_main_shutdown_push(e_sys_shutdown);

   TS("E_Exec Init");
   if (!e_exec_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its exec system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Exec Init Done");

   TS("E_Comp Freeze");
   e_comp_all_freeze();
   TS("E_Comp Freeze Done");

   TS("E_Fm2 Init");
   if (!e_fm2_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the File manager.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Fm2 Init Done");
   _e_main_shutdown_push(e_fm2_shutdown);

   TS("E_Msg Init");
   if (!e_msg_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its msg system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Msg Init Done");
   _e_main_shutdown_push(e_msg_shutdown);

   TS("E_Grabinput Init");
   if (!e_grabinput_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its grab input handling system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Grabinput Init Done");
   _e_main_shutdown_push(e_grabinput_shutdown);

   TS("E_Module Init");
   if (!e_module_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its module system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Module Init Done");
   _e_main_shutdown_push(e_module_shutdown);

   TS("E_Remember Init");
   if (!e_remember_init(after_restart ? E_STARTUP_RESTART : E_STARTUP_START))
     {
        e_error_message_show(_("Enlightenment cannot setup remember settings.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Remember Init Done");
   _e_main_shutdown_push(e_remember_shutdown);

   TS("E_Gadcon Init");
   if (!e_gadcon_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its gadget control system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Gadcon Init Done");
   _e_main_shutdown_push(e_gadcon_shutdown);

   TS("E_Toolbar Init");
   if (!e_toolbar_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its toolbars.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Toolbar Init Done");
   _e_main_shutdown_push(e_toolbar_shutdown);

   TS("E_Bg Init");
   if (!e_bg_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its desktop background system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Bg Init Done");
   _e_main_shutdown_push(e_bg_shutdown);

   TS("E_Mouse Init");
   if (!e_mouse_update())
     {
        e_error_message_show(_("Enlightenment cannot configure the mouse settings.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Mouse Init Done");

   TS("E_Bindings Init");
   if (!e_bindings_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its bindings system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Bindings Init Done");
   _e_main_shutdown_push(e_bindings_shutdown);

   TS("E_Thumb Init");
   if (!e_thumb_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the Thumbnailing system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Thumb Init Done");
   _e_main_shutdown_push(e_thumb_shutdown);

   TS("E_Icon Init");
   if (!e_icon_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the Icon Cache system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Icon Init Done");
   _e_main_shutdown_push(e_icon_shutdown);

   TS("E_Update Init");
   if (!e_update_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize the Update system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Update Init Done");
   _e_main_shutdown_push(e_update_shutdown);

   TS("E_Deskenv Init");
   if (!e_deskenv_init())
     {
        e_error_message_show(_("Enlightenment cannot initialize its desktop environment.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Deskenv Init Done");
   _e_main_shutdown_push(e_deskenv_shutdown);

   TS("E_Order Init");
   if (!e_order_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its order file system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Order Init Done");
   _e_main_shutdown_push(e_order_shutdown);

   TS("E_Comp_Canvas Keys Grab");
   e_comp_canvas_keys_grab();
   TS("E_Comp_Canvas Keys Grab Done");
#ifdef HAVE_ELPUT
   if (e_config->gesture_open_input_devices)
     {
        TS("E_Gesture Init");
        e_gesture_init();
        TS("E_Gesture Init Done");
        _e_main_shutdown_push(e_gesture_shutdown);
     }
#endif

   TS("Run Startup Apps");
   if (!nostartup)
     {
        if (after_restart)
          e_startup_mode_set(E_STARTUP_RESTART);
        else
          e_startup_mode_set(E_STARTUP_START);
     }
   TS("Run Startup Apps Done");

   TS("E_Comp Thaw");
   e_comp_all_thaw();
   TS("E_Comp Thaw Done");

   TS("E_Test Init");
   e_test();
   TS("E_Test Done");

   TS("Load Modules");
   _e_main_modules_load(safe_mode);
   TS("Load Modules Done");

   TS("E_Shelf Init");
   if (!e_shelf_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its module system.\n"));
        _e_main_shutdown(101);
     }
   e_shelf_config_update();
   TS("E_Shelf Init Done");

   _idle_after = ecore_idle_enterer_add(_e_main_cb_idle_after, NULL);

   e_init_status_set(_("Welcome to Enlightenment"));

   starting = EINA_FALSE;
   inloop = EINA_TRUE;

   e_util_env_set("E_RESTART", "1");

   if (!after_restart)
     ecore_timer_add(2.0, _e_main_cb_startup_fake_end, NULL);

   if (after_restart)
     {
        E_LIST_FOREACH(e_comp->zones, e_comp_canvas_zone_restarted);
     }

//   e_watchdog_begin();
   TS("MAIN LOOP AT LAST");
   if (!setjmp(x_fatal_buff))
     {
        e_main_loop_running = EINA_TRUE;
        ecore_main_loop_begin();
     }
   else
     CRI("FATAL: X Died. Connection gone. Abbreviated Shutdown\n");
//   e_watchdog_end();
   e_main_loop_running = EINA_FALSE;

   inloop = EINA_FALSE;
   stopping = EINA_TRUE;

   //if (!x_fatal) e_canvas_idle_flush();

   e_config_save_flush();
   _e_main_desk_save();
   e_remember_internal_save();
   e_comp_internal_save();

   _e_main_shutdown(0);

   if (restart)
     {
        e_system_shutdown();
        exit(111); // return code so e_start restrts e freshly
     }

   e_prefix_shutdown();

   return 0;
}

E_API double
e_main_ts(const char *str)
{
   double ret;
   t1 = ecore_time_unix_get();
   printf("ESTART: %1.5f [%1.5f] - %s\n", t1 - t0, t1 - t2, str);
   ret = t1 - t2;
   t2 = t1;
   return ret;
}

/* local functions */
static void
_e_main_shutdown(int errcode)
{
   int i = 0;

   printf("E: Begin Shutdown Procedure!\n");

   if (_idle_before) ecore_idle_enterer_del(_idle_before);
   _idle_before = NULL;
   if (_idle_after) ecore_idle_enterer_del(_idle_after);
   _idle_after = NULL;

   for (i = (_e_main_lvl - 1); i >= 0; i--)
     (*_e_main_shutdown_func[i])();
   if (errcode != 0) exit(errcode);
}

static void
_e_main_shutdown_push(int (*func)(void))
{
   _e_main_lvl++;
   if (_e_main_lvl > MAX_LEVEL)
     {
        _e_main_lvl--;
        e_error_message_show("WARNING: too many init levels. MAX = %i\n",
                             MAX_LEVEL);
        return;
     }
   _e_main_shutdown_func[_e_main_lvl - 1] = func;
}

static void
_e_main_parse_arguments(int argc, char **argv)
{
   char *s = NULL;
   int i = 0;

   /* handle some command-line parameters */
   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-display")) && (i < (argc - 1)))
          {
             i++;
             e_util_env_set("DISPLAY", argv[i]);
          }
        else if ((!strcmp(argv[i], "-fake-xinerama-screen")) && (i < (argc - 1)))
          {
             int x, y, w, h;

             i++;
             if (sscanf(argv[i], "%ix%i+%i+%i", &w, &h, &x, &y) == 4)
               e_xinerama_fake_screen_add(x, y, w, h);
          }
        else if (!strcmp(argv[i], "-good"))
          {
             good = EINA_TRUE;
             evil = EINA_FALSE;
             printf("LA LA LA\n");
          }
        else if (!strcmp(argv[i], "-evil"))
          {
             good = EINA_FALSE;
             evil = EINA_TRUE;
             printf("MUHAHAHAHHAHAHAHAHA\n");
          }
        else if (!strcmp(argv[i], "-psychotic"))
          {
             good = EINA_TRUE;
             evil = EINA_TRUE;
             printf("MUHAHALALALALALALALA\n");
          }
        else if ((!strcmp(argv[i], "-profile")) && (i < (argc - 1)))
          {
             i++;
             if (!getenv("E_CONF_PROFILE"))
               e_util_env_set("E_CONF_PROFILE", argv[i]);
          }
        else if (!strcmp(argv[i], "-i-really-know-what-i-am-doing-and-accept-full-responsibility-for-it"))
          really_know = EINA_TRUE;
        else if (!strcmp(argv[i], "-locked"))
          locked = EINA_TRUE;
        else if (!strcmp(argv[i], "-nopause"))
          e_nopause = EINA_TRUE;
        else if ((!strcmp(argv[i], "-version")) ||
                 (!strcmp(argv[i], "--version")))
          {
             printf(_("Version: %s\n"), PACKAGE_VERSION);
             _e_main_shutdown(11);
          }
        else if ((!strcmp(argv[i], "-h")) ||
                 (!strcmp(argv[i], "-help")) ||
                 (!strcmp(argv[i], "--help")))
          {
             printf
               (_(
                 "Options:\n"
                 "  -display DISPLAY\n"
                 "    Connect to display named DISPLAY.\n"
                 "    EG: -display :1.0\n"
                 "  -fake-xinerama-screen WxH+X+Y\n"
                 "    Add a FAKE xinerama screen (instead of the real ones)\n"
                 "    given the geometry. Add as many as you like. They all\n"
                 "    replace the real xinerama screens, if any. This can\n"
                 "    be used to simulate xinerama.\n"
                 "    EG: -fake-xinerama-screen 800x600+0+0 -fake-xinerama-screen 800x600+800+0\n"
                 "  -profile CONF_PROFILE\n"
                 "    Use the configuration profile CONF_PROFILE instead of the user selected default or just \"default\".\n"
                 "  -good\n"
                 "    Be good.\n"
                 "  -evil\n"
                 "    Be evil.\n"
                 "  -psychotic\n"
                 "    Be psychotic.\n"
                 "  -locked\n"
                 "    Start with desklock on, so password will be asked.\n"
                 "  -i-really-know-what-i-am-doing-and-accept-full-responsibility-for-it\n"
                 "    If you need this help, you don't need this option.\n"
                 "  -version\n"
                 )
               );
             _e_main_shutdown(11);
          }
     }

   /* fix up DISPLAY to be :N.0 if no .screen is in it */
   s = getenv("DISPLAY");
   if (s)
     {
        char *p, buff[4096];

        if (!(p = strrchr(s, ':')))
          {
             snprintf(buff, sizeof(buff), "%s:0.0", s);
             e_util_env_set("DISPLAY", buff);
          }
        else
          {
             if (!strrchr(p, '.'))
               {
                  snprintf(buff, sizeof(buff), "%s.0", s);
                  e_util_env_set("DISPLAY", buff);
               }
          }
     }

   /* we want to have been launched by enlightenment_start. there is a very */
   /* good reason we want to have been launched this way, thus check */
   if ((!getenv("E_START")) && (!RUNNING_ON_VALGRIND))
     {
        e_error_message_show(_("You are executing enlightenment directly. This is\n"
                               "bad. Please do not execute the \"enlightenment\"\n"
                               "binary. Use the \"enlightenment_start\" launcher. It\n"
                               "will handle setting up environment variables, paths,\n"
                               "and launching any other required services etc.\n"
                               "before enlightenment itself begins running.\n"));
        _e_main_shutdown(101);
     }
}

EINTERN void
_e_main_cb_x_fatal(void *data EINA_UNUSED)
{
   fprintf(stderr, "X I/O Error - fatal. Exiting.\n");
   exit(101);
}

static Eina_Bool
_e_main_cb_signal_exit(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev EINA_UNUSED)
{
   /* called on ctrl-c, kill (pid) (also SIGINT, SIGTERM and SIGQIT) */
   e_sys_action_do(E_SYS_EXIT, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Ecore_Timer *hup_timer = NULL;

static Eina_Bool
_cb_hup_timer(void *data EINA_UNUSED)
{
   hup_timer = NULL;
   if (!e_sys_on_the_way_out_get()) e_sys_action_do(E_SYS_RESTART, NULL);
   return EINA_FALSE;
}

static Eina_Bool
_e_main_cb_signal_hup(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev EINA_UNUSED)
{
   if (hup_timer) ecore_timer_del(hup_timer);
   hup_timer = ecore_timer_add(0.5, _cb_hup_timer, NULL);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_main_cb_signal_user(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev)
{
   Ecore_Event_Signal_User *e = ev;

   if (e->number == 1)
     {
//        E_Action *a = e_action_find("configuration");
//        if ((a) && (a->func.go)) a->func.go(NULL, NULL);
     }
   else if (e->number == 2)
     {
        // comp module has its own handler for this for enabling/disabling fps debug
     }
   return ECORE_CALLBACK_RENEW;

}

static int
_e_main_dirs_init(void)
{
   const char *base;
   const char *dirs[] =
   {
      "images",
      "fonts",
      "themes",
      "icons",
      "backgrounds",
      "applications",
      "applications/menu",
      "applications/menu/favorite",
      "applications/menu/all",
      "applications/bar",
      "applications/bar/default",
      "applications/startup",
      "applications/restart",
      "applications/trash",
      "applications/desk-lock",
      "applications/desk-unlock",
      "modules",
      "config",
      "locale",
      "input_methods",
      NULL
   };

   base = e_user_dir_get();
   if (ecore_file_mksubdirs(base, dirs) != sizeof(dirs) / sizeof(dirs[0]) - 1)
     {
        e_error_message_show("Could not create one of the required "
                             "subdirectories of '%s'\n", base);
        return 0;
     }

   return 1;
}

static int
_e_main_dirs_shutdown(void)
{
   return 1;
}

static int
_e_main_path_init(void)
{
   char buf[PATH_MAX];

   /* setup data paths */
   path_data = e_path_new();
   if (!path_data)
     {
        e_error_message_show("Cannot allocate path for path_data\n");
        return 0;
     }
   e_prefix_data_concat_static(buf, "data");
   e_path_default_path_append(path_data, buf);
   e_path_user_path_set(path_data, &(e_config->path_append_data));

   /* setup image paths */
   path_images = e_path_new();
   if (!path_images)
     {
        e_error_message_show("Cannot allocate path for path_images\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/images");
   e_path_default_path_append(path_images, buf);
   e_prefix_data_concat_static(buf, "data/images");
   e_path_default_path_append(path_images, buf);
   e_path_user_path_set(path_images, &(e_config->path_append_images));

   /* setup font paths */
   path_fonts = e_path_new();
   if (!path_fonts)
     {
        e_error_message_show("Cannot allocate path for path_fonts\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/fonts");
   e_path_default_path_append(path_fonts, buf);
   e_prefix_data_concat_static(buf, "data/fonts");
   e_path_default_path_append(path_fonts, buf);
   e_path_user_path_set(path_fonts, &(e_config->path_append_fonts));

   /* setup icon paths */
   path_icons = e_path_new();
   if (!path_icons)
     {
        e_error_message_show("Cannot allocate path for path_icons\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/icons");
   e_path_default_path_append(path_icons, buf);
   e_prefix_data_concat_static(buf, "data/icons");
   e_path_default_path_append(path_icons, buf);
   e_path_user_path_set(path_icons, &(e_config->path_append_icons));

   /* setup module paths */
   path_modules = e_path_new();
   if (!path_modules)
     {
        e_error_message_show("Cannot allocate path for path_modules\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/modules");
   e_path_default_path_append(path_modules, buf);
   snprintf(buf, sizeof(buf), "%s/enlightenment/modules", e_prefix_lib_get());
   e_path_default_path_append(path_modules, buf);
   /* FIXME: eventually this has to go - moduels should have installers that
    * add appropriate install paths (if not installed to user homedir) to
    * e's module search dirs
    */
   snprintf(buf, sizeof(buf), "%s/enlightenment/modules_extra", e_prefix_lib_get());
   e_path_default_path_append(path_modules, buf);
   e_path_user_path_set(path_modules, &(e_config->path_append_modules));

   /* setup background paths */
   path_backgrounds = e_path_new();
   if (!path_backgrounds)
     {
        e_error_message_show("Cannot allocate path for path_backgrounds\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/backgrounds");
   e_path_default_path_append(path_backgrounds, buf);
   e_prefix_data_concat_static(buf, "data/backgrounds");
   e_path_default_path_append(path_backgrounds, buf);
   e_path_user_path_set(path_backgrounds, &(e_config->path_append_backgrounds));

   path_messages = e_path_new();
   if (!path_messages)
     {
        e_error_message_show("Cannot allocate path for path_messages\n");
        return 0;
     }
   e_user_dir_concat_static(buf, "/locale");
   e_path_default_path_append(path_messages, buf);
   e_path_default_path_append(path_messages, e_prefix_locale_get());
   e_path_user_path_set(path_messages, &(e_config->path_append_messages));

   return 1;
}

static int
_e_main_path_shutdown(void)
{
   if (path_data)
     {
        e_object_del(E_OBJECT(path_data));
        path_data = NULL;
     }
   if (path_images)
     {
        e_object_del(E_OBJECT(path_images));
        path_images = NULL;
     }
   if (path_fonts)
     {
        e_object_del(E_OBJECT(path_fonts));
        path_fonts = NULL;
     }
   if (path_icons)
     {
        e_object_del(E_OBJECT(path_icons));
        path_icons = NULL;
     }
   if (path_modules)
     {
        e_object_del(E_OBJECT(path_modules));
        path_modules = NULL;
     }
   if (path_backgrounds)
     {
        e_object_del(E_OBJECT(path_backgrounds));
        path_backgrounds = NULL;
     }
   if (path_messages)
     {
        e_object_del(E_OBJECT(path_messages));
        path_messages = NULL;
     }
   return 1;
}

static int
_e_main_screens_init(void)
{
   TS("  screens: client");
   if (!e_client_init()) return 0;
   TS("E_Screensaver Init");
   if (!e_screensaver_init()) return 0;
   TS("  screens: client volume");
   if (!e_client_volume_init()) return 0;
   TS("  screens: win");
   if (!e_win_init()) return 0;
   TS("Compositor Init");
   if (!e_comp_init())
     {
        e_error_message_show(_("Enlightenment cannot create a compositor.\n"));
        _e_main_shutdown(101);
     }
   TS("Compositor Init Done");

   TS("Desk Restore");
   _e_main_desk_restore();
   TS("Desk Restore Done");

#ifndef HAVE_WAYLAND_ONLY
   TS("E_Dnd Init");
   if (!e_dnd_init())
     {
        e_error_message_show(_("Enlightenment cannot set up its dnd system.\n"));
        _e_main_shutdown(101);
     }
   TS("E_Dnd Init Done");
#endif

   return 1;
}

static int
_e_main_screens_shutdown(void)
{
   e_win_shutdown();
   e_menu_shutdown();
   e_shelf_shutdown();
   e_dnd_shutdown();
   e_comp_shutdown();
   e_client_volume_shutdown();
   e_screensaver_shutdown();
   e_client_shutdown();
   e_exehist_shutdown();
   e_backlight_shutdown();
   e_exec_shutdown();

   e_desk_shutdown();
   e_zone_shutdown();
   return 1;
}

static void
_e_main_desk_save(void)
{
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_x())
     {
        const Eina_List *l;
        E_Zone *zone;
        char buf[256];
        unsigned int desk[2];
        Ecore_X_Atom a;

        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          {
             desk[0] = zone->desk_x_current;
             desk[1] = zone->desk_y_current;
             snprintf(buf, sizeof(buf), "E_DESK_%i", zone->num);
             a = ecore_x_atom_get(buf);
             ecore_x_window_prop_card32_set(e_comp->root, a, desk, 2);
          }
     }
#endif
}

static void
_e_main_desk_restore(void)
{
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_x())
     {
        const Eina_List *l;
        E_Zone *zone;
        E_Client *ec;
        E_Desk *desk;
        int ret;
        char buf[256];
        unsigned int desks[2];
        Ecore_X_Atom a;

        // hide all windows first as desk show will restore
        E_CLIENT_FOREACH(ec)
          {
             ec->hidden = 1;
             evas_object_show(ec->frame);
             ec->changes.visible = 0;
             evas_object_hide(ec->frame);
             e_client_comp_hidden_set(ec, EINA_TRUE);
          }
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          {
             desk = NULL;

             snprintf(buf, sizeof(buf), "E_DESK_%i", zone->num);
             a = ecore_x_atom_get(buf);
             ret = ecore_x_window_prop_card32_get(e_comp->root, a, desks, 2);
             if (ret == 2)
               {
                  desk = e_desk_at_xy_get(zone, desks[0], desks[1]);
                  if (desk) e_desk_show(desk);
               }
             if (!desk) desk = e_desk_current_get(zone);
             if (desk)
               {
                  // ensure windows for this desk are shown
                  E_CLIENT_REVERSE_FOREACH(ec)
                    {
                       if (ec->iconic) continue;
                       if ((ec->desk == desk) || (ec->sticky))
                         {
                            ec->hidden = 0;
                            e_client_comp_hidden_set(ec, ec->hidden || ec->shaded);
                            evas_object_show(ec->frame);
                         }
                    }
               }
          }
        E_CLIENT_REVERSE_FOREACH(ec)
          {
             if ((!e_client_util_ignored_get(ec)) &&
                 e_client_util_desk_visible(ec, e_desk_current_get(ec->zone)))
               {
                  ec->want_focus = ec->take_focus = 1;
                  break;
               }
          }
     }
#endif
}

static void
_e_main_efreet_paths_init(void)
{
   Eina_List **list;

   if ((list = efreet_icon_extra_list_get()))
     {
        char buff[PATH_MAX];

        e_user_dir_concat_static(buff, "icons");
        *list = eina_list_prepend(*list, (void *)eina_stringshare_add(buff));
        e_prefix_data_concat_static(buff, "data/icons");
        *list = eina_list_prepend(*list, (void *)eina_stringshare_add(buff));
     }
}

static Eina_Bool
_e_main_modules_load_after(void *d EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
   e_int_config_modules(NULL, NULL);
   E_FREE_FUNC(mod_init_end, ecore_event_handler_del);
   return ECORE_CALLBACK_RENEW;
}

static void
_e_main_modules_load(Eina_Bool safe_mode)
{
   if (!safe_mode)
     e_module_all_load();
   else
     {
        E_Module *m;
        char *crashmodule;

        crashmodule = getenv("E_MODULE_LOAD");
        if (crashmodule) m = e_module_new(crashmodule);

        if ((crashmodule) && (m))
          {
             e_module_disable(m);
             e_object_del(E_OBJECT(m));

             e_error_message_show
               (_("Enlightenment crashed early on start and has<ps/>"
                  "been restarted. There was an error loading the<ps/>"
                  "module named: %s. This module has been disabled<ps/>"
                  "and will not be loaded."), crashmodule);
             e_util_dialog_show
               (_("Enlightenment crashed early on start and has been restarted"),
               _("Enlightenment crashed early on start and has been restarted.<ps/>"
                 "There was an error loading the module named: %s<ps/><ps/>"
                 "This module has been disabled and will not be loaded."), crashmodule);
             e_module_all_load();
          }
        else
          {
             e_error_message_show
               (_("Enlightenment crashed early on start and has<ps/>"
                  "been restarted. All modules have been disabled<ps/>"
                  "and will not be loaded to help remove any problem<ps/>"
                  "modules from your configuration. The module<ps/>"
                  "configuration dialog should let you select your<ps/>"
                  "modules again.\n"));
             e_util_dialog_show
               (_("Enlightenment crashed early on start and has been restarted"),
               _("Enlightenment crashed early on start and has been restarted.<ps/>"
                 "All modules have been disabled and will not be loaded to help<ps/>"
                 "remove any problem modules from your configuration.<ps/><ps/>"
                 "The module configuration dialog should let you select your<ps/>"
                 "modules again."));
          }
        mod_init_end = ecore_event_handler_add(E_EVENT_MODULE_INIT_END, _e_main_modules_load_after, NULL);
     }
}

static Eina_Bool
_e_main_cb_idle_before(void *data EINA_UNUSED)
{
   e_menu_idler_before();
   e_client_idler_before();
   e_pointer_idler_before();
   edje_thaw();
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_main_cb_idle_after(void *data EINA_UNUSED)
{
   static int first_idle = 1;

   eet_clearcache();
   edje_freeze();

   if (first_idle)
     {
        TS("SLEEP");
        first_idle = 0;
     }
   e_precache_end = EINA_TRUE;

// every now and again trim malloc memory to stay lean-ish
#ifdef HAVE_MALLOC_TRIM
   static double t_last_clean = 0.0;
   double t = ecore_time_get();
   if ((t - t_last_clean) > 10.0)
     {
        t_last_clean = t;
        malloc_trim(0);
     }
#endif

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_main_cb_startup_fake_end(void *data EINA_UNUSED)
{
   e_init_done();
   return ECORE_CALLBACK_CANCEL;
}
#include "e.h"

typedef struct _E_Maximize_Rect E_Maximize_Rect;

struct _E_Maximize_Rect
{
   int x1, yy1, x2, y2;
};

#define OBSTACLE(_x1, _y1, _x2, _y2)                              \
  {                                                               \
     r = E_NEW(E_Maximize_Rect, 1);                               \
     r->x1 = (_x1); r->yy1 = (_y1); r->x2 = (_x2); r->y2 = (_y2); \
     rects = eina_list_append(rects, r);                          \
  }

static void _e_maximize_client_rects_fill(const E_Client *ec, Eina_List *list, int *x1, int *yy1, int *x2, int *y2, E_Maximize dir);
static void _e_maximize_client_rects_fill_both(const E_Client *ec, Eina_List *rects, int *x1, int *yy1, int *x2, int *y2);
static void _e_maximize_client_rects_fill_horiz(const E_Client *ec, Eina_List *rects, int *x1, int *x2, int *bx, int *by, int *bw, int *bh);
static void _e_maximize_client_rects_fill_vert(const E_Client *ec, Eina_List *rects, int *yy1, int *y2, int *bx, int *by, int *bw, int *bh);

E_API void
e_maximize_client_shelf_fit(const E_Client *ec, int *x1, int *yy1, int *x2, int *y2, E_Maximize dir)
{
   e_maximize_client_shelf_fill(ec, x1, yy1, x2, y2, dir);
}

E_API void
e_maximize_client_dock_fit(const E_Client *ec, int *x1, int *yy1, int *x2, int *y2)
{
   E_Client *ec2;
   int cx1, cx2, cy1, cy2;

   cx1 = ec->zone->x;
   if (x1) cx1 = *x1;

   cy1 = ec->zone->y;
   if (yy1) cy1 = *yy1;

   cx2 = ec->zone->x + ec->zone->w;
   if (x2) cx2 = *x2;

   cy2 = ec->zone->y + ec->zone->h;
   if (y2) cy2 = *y2;

   E_CLIENT_FOREACH(ec2)
     {
        enum
        {
           NONE,
           TOP,
           RIGHT,
           BOTTOM,
           LEFT
        } edge = NONE;

        if ((ec2->zone != ec->zone) || (ec2 == ec) ||
            (ec2->netwm.type != E_WINDOW_TYPE_DOCK))
          continue;

        if (((ec2->x == ec2->zone->x) || ((ec2->x + ec2->w) == (ec2->zone->x + ec2->zone->w))) &&
            ((ec2->y == ec2->zone->y) || ((ec2->x + ec2->h) == (ec2->zone->x + ec2->zone->h))))
          {
             /* corner */
             if (ec2->w > ec2->h)
               {
                  if (ec2->y == ec2->zone->y)
                    edge = TOP;
                  else if ((ec2->y + ec2->h) == (ec2->zone->y + ec2->zone->h))
                    edge = BOTTOM;
               }
             else
               {
                  if ((ec2->x + ec2->w) == (ec2->zone->x + ec2->zone->w))
                    edge = RIGHT;
                  else if (ec2->x == ec2->zone->x)
                    edge = LEFT;
               }
          }
        else
          {
             if (ec2->y == ec2->zone->y)
               edge = TOP;
             else if ((ec2->y + ec2->h) == (ec2->zone->y + ec2->zone->h))
               edge = BOTTOM;
             else if (ec2->x == ec2->zone->x)
               edge = LEFT;
             else if ((ec2->x + ec2->w) == (ec2->zone->x + ec2->zone->w))
               edge = RIGHT;
          }

        switch (edge)
          {
           case TOP:
             if ((ec2->y + ec2->h) > cy1)
               cy1 = (ec2->y + ec2->h);
             break;

           case RIGHT:
             if (ec2->x < cx2)
               cx2 = ec2->x;
             break;

           case BOTTOM:
             if (ec2->y < cy2)
               cy2 = ec2->y;
             break;

           case LEFT:
             if ((ec2->x + ec2->w) > cx1)
               cx1 = (ec2->x + ec2->w);
             break;

           case NONE:
             printf("Crazy people. Dock isn't at the edge.\n");
             break;
          }
     }

   if (x1) *x1 = cx1;
   if (yy1) *yy1 = cy1;
   if (x2) *x2 = cx2;
   if (y2) *y2 = cy2;
}

E_API void
e_maximize_client_shelf_fill(const E_Client *ec, int *x1, int *yy1, int *x2, int *y2, E_Maximize dir)
{
   Eina_List *l, *rects = NULL;
   E_Shelf *es;
   E_Maximize_Rect *r;

   l = e_shelf_list_all();
   EINA_LIST_FREE(l, es)
     {
        if (es->cfg->overlap) continue;
        if (!e_shelf_desk_visible(es, ec->desk)) continue;
        OBSTACLE(es->x + es->zone->x, es->y + es->zone->y,
                 es->x + es->zone->x + es->w, es->y + es->zone->y + es->h);
     }
   if (rects)
     {
        _e_maximize_client_rects_fill(ec, rects, x1, yy1, x2, y2, dir);
        E_FREE_LIST(rects, free);
     }
}

E_API void
e_maximize_client_client_fill(const E_Client *ec, int *x1, int *yy1, int *x2, int *y2, E_Maximize dir)
{
   Eina_List *rects = NULL;
   E_Maximize_Rect *r;
   E_Client *ec2;

   E_CLIENT_FOREACH(ec2)
     {
        if ((ec2->zone != ec->zone) || (ec == ec2) || (ec2->desk != ec->desk && !ec2->sticky) || (ec2->iconic))
          continue;
        OBSTACLE(ec2->x, ec2->y, ec2->x + ec2->w, ec2->y + ec2->h);
     }
   if (rects)
     {
        _e_maximize_client_rects_fill(ec, rects, x1, yy1, x2, y2, dir);
        E_FREE_LIST(rects, free);
     }
}

static void
_e_maximize_client_rects_fill(const E_Client *ec, Eina_List *rects, int *x1, int *yy1, int *x2, int *y2, E_Maximize dir)
{
   if ((dir & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_BOTH)
     {
        _e_maximize_client_rects_fill_both(ec, rects, x1, yy1, x2, y2);
     }
   else
     {
        int bx, by, bw, bh;

        bx = E_CLAMP(ec->x, ec->zone->x, ec->zone->x + ec->zone->w);
        by = E_CLAMP(ec->y, ec->zone->y, ec->zone->y + ec->zone->h);
        bw = E_CLAMP(ec->w, 0, ec->zone->w);
        bh = E_CLAMP(ec->h, 0, ec->zone->h);

        if ((dir & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_HORIZONTAL)
          _e_maximize_client_rects_fill_horiz(ec, rects, x1, x2, &bx, &by, &bw, &bh);
        else if (((dir & E_MAXIMIZE_DIRECTION) == E_MAXIMIZE_VERTICAL) ||
                 ((dir & E_MAXIMIZE_LEFT) == E_MAXIMIZE_LEFT) ||
                 ((dir & E_MAXIMIZE_RIGHT) == E_MAXIMIZE_RIGHT))
          _e_maximize_client_rects_fill_vert(ec, rects, yy1, y2, &bx, &by, &bw, &bh);
     }
}

static void
_e_maximize_client_rects_fill_both(const E_Client *ec, Eina_List *rects, int *x1, int *yy1, int *x2, int *y2)
{
   int hx1, hy1, hx2, hy2;
   int vx1, vy1, vx2, vy2;
   int bx, by, bw, bh;

   hx1 = vx1 = ec->zone->x;
   if (x1) hx1 = vx1 = *x1;

   hy1 = vy1 = ec->zone->y;
   if (yy1) hy1 = vy1 = *yy1;

   hx2 = vx2 = ec->zone->x + ec->zone->w;
   if (x2) hx2 = vx2 = *x2;

   hy2 = vy2 = ec->zone->y + ec->zone->h;
   if (y2) hy2 = vy2 = *y2;

   /* Init working values, try maximizing horizontally first */
   bx = ec->saved.x ?: ec->x;
   by = ec->saved.y ?: ec->y;
   bw = ec->saved.w ?: ec->w;
   bh = ec->saved.h ?: ec->h;
   _e_maximize_client_rects_fill_horiz(ec, rects, &hx1, &hx2, &bx, &by, &bw, &bh);
   _e_maximize_client_rects_fill_vert(ec, rects, &hy1, &hy2, &bx, &by, &bw, &bh);

   /* Reset working values, try maximizing vertically first */
   bx = ec->saved.x ?: ec->x;
   by = ec->saved.y ?: ec->y;
   bw = ec->saved.w ?: ec->w;
   bh = ec->saved.h ?: ec->h;
   _e_maximize_client_rects_fill_vert(ec, rects, &vy1, &vy2, &bx, &by, &bw, &bh);
   _e_maximize_client_rects_fill_horiz(ec, rects, &vx1, &vx2, &bx, &by, &bw, &bh);

   /* Use the result set that gives the largest volume */
   if (((hx2 - hx1) * (hy2 - hy1)) > ((vx2 - vx1) * (vy2 - vy1)))
     {
        if (x1) *x1 = hx1;
        if (yy1) *yy1 = hy1;
        if (x2) *x2 = hx2;
        if (y2) *y2 = hy2;
     }
   else
     {
        if (x1) *x1 = vx1;
        if (yy1) *yy1 = vy1;
        if (x2) *x2 = vx2;
        if (y2) *y2 = vy2;
     }
}

static void
_e_maximize_client_rects_fill_horiz(const E_Client *ec, Eina_List *rects, int *x1, int *x2, int *bx, int *by, int *bw, int *bh)
{
   Eina_List *l;
   E_Maximize_Rect *rect;
   int cx1, cx2;

   cx1 = ec->zone->x;
   if (x1) cx1 = *x1;

   cx2 = ec->zone->x + ec->zone->w;
   if (x2) cx2 = *x2;

   /* Expand left */
   EINA_LIST_FOREACH(rects, l, rect)
     {
        if ((rect->x2 > cx1) && (rect->x2 <= *bx) &&
            E_INTERSECTS(0, rect->yy1, ec->zone->w, (rect->y2 - rect->yy1), 0, *by, ec->zone->w, *bh))
          {
             cx1 = rect->x2;
          }
     }
   *bw += (*bx - cx1);
   *bx = cx1;

   /* Expand right */
   EINA_LIST_FOREACH(rects, l, rect)
     {
        if ((rect->x1 < cx2) && (rect->x1 >= (*bx + *bw)) &&
            E_INTERSECTS(0, rect->yy1, ec->zone->w, (rect->y2 - rect->yy1), 0, *by, ec->zone->w, *bh))
          {
             cx2 = rect->x1;
          }
     }
   *bw = (cx2 - cx1);

   if (x1) *x1 = cx1;
   if (x2) *x2 = cx2;
}

static void
_e_maximize_client_rects_fill_vert(const E_Client *ec, Eina_List *rects, int *yy1, int *y2, int *bx, int *by, int *bw, int *bh)
{
   Eina_List *l;
   E_Maximize_Rect *rect;
   int cy1, cy2;

   cy1 = ec->zone->y;
   if (yy1) cy1 = *yy1;

   cy2 = ec->zone->y + ec->zone->h;
   if (y2) cy2 = *y2;

   /* Expand up */
   EINA_LIST_FOREACH(rects, l, rect)
     {
        if ((rect->y2 > cy1) && ((rect->yy1 <= *by) || (rect->y2 <= *by)) &&
            E_INTERSECTS(rect->x1, 0, (rect->x2 - rect->x1), ec->zone->h, *bx, 0, *bw, ec->zone->h))
          {
             cy1 = rect->y2;
          }
     }
   *bh += (*by - cy1);
   *by = cy1;

   /* Expand down */
   EINA_LIST_FOREACH(rects, l, rect)
     {
        if ((rect->yy1 < cy2) && (rect->yy1 >= (*by + *bh)) &&
            E_INTERSECTS(rect->x1, 0, (rect->x2 - rect->x1), ec->zone->h, *bx, 0, *bw, ec->zone->h))
          {
             cy2 = rect->yy1;
          }
     }
   *bh = (cy2 - cy1);

   if (yy1) *yy1 = cy1;
   if (y2) *y2 = cy2;
}

#include "e.h"

/* TODO List:
 *
 * * support change of menu items after realize
 * * support add/del of menu items after realize
 * * support text/color classes
 * * refcount menu up while looping thru and calling other fn's
 * * support alignment (x, y) as well as spawn direction
 * * need different menu style support for different menus
 * * add menu icon/title support
 * * use event timestamps not clock for "click and release" detect
 * * menu icons can set if/how they will be scaled
 * * support move/resize of "box" that spawned the menu
 * * add image item (label is replaced by image/icon)
 * * add generic evas object item type (label replaced by object)
 * * allow menus to stretch width/height to fit spawner widget/box
 * * allow menus to auto-shrink (horizontally) if forced to
 * * support auto left/right direction spawn
 * * support obscures to indicate offscreen/not visible menu parts
 */

/* local subsystem data types */
typedef struct _E_Menu_Category E_Menu_Category;

struct _E_Menu_Category
{
   void      *data;
   Eina_List *callbacks;
};

/* local subsystem functions */
static void         _e_menu_free(E_Menu *m);
static void         _e_menu_item_free(E_Menu_Item *mi);
static void         _e_menu_item_realize(E_Menu_Item *mi);
static void         _e_menu_realize(E_Menu *m);
static void         _e_menu_items_layout_update(E_Menu *m);
static void         _e_menu_item_unrealize(E_Menu_Item *mi);
static void         _e_menu_unrealize(E_Menu *m);
static void         _e_menu_activate_internal(E_Menu *m, E_Zone *zone);
static void         _e_menu_deactivate_all(void);
static void         _e_menu_deactivate_above(E_Menu *m);
static void         _e_menu_submenu_activate(E_Menu_Item *mi);
static void         _e_menu_submenu_deactivate(E_Menu_Item *mi);
static void         _e_menu_reposition(E_Menu *m);
static int          _e_menu_active_call(void);
static int          _e_menu_realize_call(E_Menu_Item *mi);
static void         _e_menu_item_activate_next(void);
static void         _e_menu_item_activate_previous(void);
static void         _e_menu_item_activate_first(void);
static void         _e_menu_item_activate_last(void);
static void         _e_menu_item_activate_nth(int n);
static void         _e_menu_item_activate_char(const char *key_compose);
static void         _e_menu_activate_next(void);
static void         _e_menu_activate_previous(void);
static void         _e_menu_activate_first(void);
static void         _e_menu_activate_last(void);
#if 0
static void         _e_menu_activate_nth(int n);
#endif
static E_Menu      *_e_menu_active_get(void);
static E_Menu_Item *_e_menu_item_active_get(void);
static Eina_List   *_e_menu_list_item_active_get(void);
static int          _e_menu_outside_bounds_get(int xdir, int ydir);
static void         _e_menu_scroll_by(int dx, int dy);
static void         _e_menu_mouse_autoscroll_check(void);
static void         _e_menu_item_ensure_onscreen(E_Menu_Item *mi);
static int          _e_menu_auto_place(E_Menu *m, int x, int y, int w, int h);
static void         _e_menu_cb_intercept_item_move(void *data, Evas_Object *o, Evas_Coord x, Evas_Coord y);
static void         _e_menu_cb_intercept_item_resize(void *data, Evas_Object *o, Evas_Coord w, Evas_Coord h);
static void         _e_menu_cb_intercept_container_move(void *data, Evas_Object *o, Evas_Coord x, Evas_Coord y);
static void         _e_menu_cb_intercept_container_resize(void *data, Evas_Object *o, Evas_Coord w, Evas_Coord h);
static void         _e_menu_cb_item_in(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void         _e_menu_cb_item_out(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static Eina_Bool    _e_menu_cb_key_down(void *data EINA_UNUSED, Ecore_Event_Key *ev);
static Eina_Bool    _e_menu_cb_mouse_down(void *data, int type, void *event);
static Eina_Bool    _e_menu_cb_mouse_up(void *data, int type, void *event);
static Eina_Bool    _e_menu_cb_mouse_move(void *data, int type, void *event);
static Eina_Bool    _e_menu_cb_mouse_wheel(void *data, int type, void *event);
static Eina_Bool    _e_menu_cb_scroll_animator(void *data);
static void         _e_menu_cb_item_submenu_post_default(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_menu_category_free_cb(E_Menu_Category *cat);
static void         _e_menu_cb_mouse_evas_down(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED);
static void         _e_menu_hide_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);

typedef enum
{
   NAV_BY_NONE,
   NAV_BY_MOUSE,
   NAV_BY_WHEEL,
   NAV_BY_KEY
} Nav_By;

/* local subsystem globals */
static Ecore_Window _e_menu_win = UINT_MAX;
static Eina_Bool _e_menu_grabbed = EINA_FALSE;
static Eina_List *_e_active_menus = NULL;
static E_Menu_Item *_e_active_menu_item = NULL;
static E_Menu_Item *_e_prev_active_menu_item = NULL;
/*static Eina_Hash	   *_e_menu_category_items	= NULL;*/
static Eina_Hash *_e_menu_categories = NULL;
static unsigned int _e_menu_activate_time = 0;
static int _e_menu_activate_floating = 0;
static int _e_menu_activate_maybe_drag = 0;
static int _e_menu_activate_dragging = 0;
static Ecore_Animator *_e_menu_scroll_animator = NULL;
static double _e_menu_scroll_start = 0.0;
static int _e_menu_x = 0;
static int _e_menu_y = 0;
static unsigned int _e_menu_time = 0;
static int _e_menu_autoscroll_x = 0;
static int _e_menu_autoscroll_y = 0;
static Eina_List *handlers = NULL;
static Eina_Bool _e_menu_lock = EINA_FALSE;
static Nav_By _e_menu_nav_by = NAV_BY_NONE;

static Eina_Bool pending_feed;
static unsigned int pending_activate_time;

static Eina_List *
_e_active_menus_copy_ref(void)
{
   E_Object *o;
   Eina_List *l, *ret = NULL;

   EINA_LIST_FOREACH(_e_active_menus, l, o)
     {
        ret = eina_list_append(ret, o);
        e_object_ref(o);
     }
   return ret;
}

static Eina_List *
_e_menu_list_free_unref(Eina_List *l)
{
   E_Object *o;
   Eina_List *ll, *lll;

   /* list must be freed in reverse to ensure that submenus
    * (which are added to the end of the list)
    * are deleted before their parents
    */
   EINA_LIST_REVERSE_FOREACH_SAFE(l, ll, lll, o)
   {
      e_object_unref(o);
      l = eina_list_remove_list(l, ll);
   }
   return l;
}

/* macros for debugging menu refcounts */
#if 0
#define e_object_ref(X)   do {      \
       int xx;                      \
       xx = e_object_ref(X);        \
       INF("REF: %p || %d", X, xx); \
  } while (0)
#define e_object_unref(X) do {        \
       int xx;                        \
       xx = e_object_unref(X);        \
       INF("UNREF: %p || %d", X, xx); \
  } while (0)
#endif

/* externally accessible functions */
EINTERN int
e_menu_init(void)
{
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_BUTTON_DOWN, _e_menu_cb_mouse_down, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_BUTTON_UP, _e_menu_cb_mouse_up, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_MOVE, _e_menu_cb_mouse_move, NULL);
   E_LIST_HANDLER_APPEND(handlers, ECORE_EVENT_MOUSE_WHEEL, _e_menu_cb_mouse_wheel, NULL);
   _e_menu_categories = eina_hash_string_superfast_new((Eina_Free_Cb)_e_menu_category_free_cb);

   e_int_menus_init();
   return 1;
}

EINTERN int
e_menu_shutdown(void)
{
   E_FREE_LIST(handlers, ecore_event_handler_del);

   if (!x_fatal)
     e_menu_hide_all();
   _e_active_menus = NULL;
   E_FREE_FUNC(_e_menu_categories, eina_hash_free);

   _e_menu_lock = EINA_FALSE;
   e_int_menus_shutdown();

   return 1;
}

E_API void
e_menu_hide_all(void)
{
   E_Menu *m;

   EINA_LIST_FREE(_e_active_menus, m)
     {
        if (m->post_deactivate_cb.func)
          m->post_deactivate_cb.func(m->post_deactivate_cb.data, m);
        m->active = 0;
        if (m->comp_object == e_comp->autoclose.obj)
          e_comp_object_util_autoclose(NULL, NULL, NULL, NULL);
        _e_menu_unrealize(m);
        m->in_active_list = 0;
        e_object_unref(E_OBJECT(m));
     }
   if (_e_menu_grabbed)
     {
        e_comp_ungrab_input(1, 1);
        _e_menu_grabbed = EINA_FALSE;
     }
}

E_API E_Menu *
e_menu_new(void)
{
   E_Menu *m;

   m = E_OBJECT_ALLOC(E_Menu, E_MENU_TYPE, _e_menu_free);
   if (!m) return NULL;
   m->cur.w = 1;
   m->cur.h = 1;
   m->category = NULL;
   m->hold_mode = EINA_TRUE;
   return m;
}

E_API void
e_menu_hold_mode_set(E_Menu *m, Eina_Bool hold_mode)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   m->hold_mode = hold_mode;
}

E_API void
e_menu_activate_key(E_Menu *m, E_Zone *zone, int x, int y, int w, int h, int dir)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   if (_e_active_menus) e_menu_hide_all();
   _e_menu_activate_time = 0;
   _e_menu_activate_floating = 0;
   _e_menu_activate_internal(m, zone);
   if (!m->zone)
     {
        e_menu_deactivate(m);
        return;
     }
   _e_menu_nav_by = NAV_BY_KEY;
   switch (dir)
     {
      case E_MENU_POP_DIRECTION_LEFT:
        _e_menu_realize(m);
        m->cur.x = x - m->cur.w;
        m->cur.y = y;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_RIGHT:
        _e_menu_realize(m);
        m->cur.x = x + w;
        m->cur.y = y;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_UP:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_last();
        break;

      case E_MENU_POP_DIRECTION_DOWN:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y + h;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_AUTO:
          {
             int pos = 0;

             pos = _e_menu_auto_place(m, x, y, w, h);
             e_menu_idler_before();
             if (pos == 4) _e_menu_activate_last();
             else _e_menu_activate_first();
          }
      break;

      default:
        m->cur.x = x + w;
        m->cur.y = y + h;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;
     }
}

E_API void
e_menu_activate_mouse(E_Menu *m, E_Zone *zone, int x, int y, int w, int h, int dir, unsigned int activate_time)
{
   E_Menu_Item *pmi;

   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   e_object_ref(E_OBJECT(m));
   e_menu_hide_all();
   _e_menu_activate_time = 0;
   _e_menu_activate_floating = 0;
   _e_menu_activate_internal(m, zone);
   if (!m->zone)
     {
        e_menu_deactivate(m);
        e_object_unref(E_OBJECT(m));
        return;
     }
   _e_menu_nav_by = NAV_BY_MOUSE;
   switch (dir)
     {
      case E_MENU_POP_DIRECTION_LEFT:
        _e_menu_realize(m);
        m->cur.x = x - m->cur.w;
        m->cur.y = y;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        break;

      case E_MENU_POP_DIRECTION_RIGHT:
        _e_menu_realize(m);
        m->cur.x = x + w;
        m->cur.y = y;
        e_menu_idler_before();
        break;

      case E_MENU_POP_DIRECTION_UP:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y - m->cur.h;
        e_menu_idler_before();
        break;

      case E_MENU_POP_DIRECTION_DOWN:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y + h;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        break;

      case E_MENU_POP_DIRECTION_AUTO:
        e_menu_idler_before();
        _e_menu_auto_place(m, x, y, w, h);
        break;

      default:
        m->cur.x = x + w;
        m->cur.y = y + h;
        e_menu_idler_before();
        break;
     }
   pmi = _e_menu_item_active_get();
   if (pmi) e_menu_item_active_set(pmi, 0);
   pending_feed = 1;
   if (!activate_time) activate_time = lround(ecore_loop_time_get() * 1000);
   _e_menu_activate_time = pending_activate_time = activate_time;
   e_object_unref(E_OBJECT(m));
}

E_API void
e_menu_activate(E_Menu *m, E_Zone *zone, int x, int y, int w, int h, int dir)
{
   E_Menu_Item *pmi;

   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   if (_e_active_menus) e_menu_hide_all();
   _e_menu_activate_time = 0;
   _e_menu_activate_floating = 0;
   _e_menu_activate_internal(m, zone);
   if (!m->zone)
     {
        e_menu_deactivate(m);
        return;
     }
   _e_menu_nav_by = NAV_BY_NONE;
   switch (dir)
     {
      case E_MENU_POP_DIRECTION_LEFT:
        _e_menu_realize(m);
        m->cur.x = x - m->cur.w;
        m->cur.y = y;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_RIGHT:
        _e_menu_realize(m);
        m->cur.x = x + w;
        m->cur.y = y;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_UP:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_last();
        break;

      case E_MENU_POP_DIRECTION_DOWN:
        _e_menu_realize(m);
        m->cur.x = x;
        if ((m->cur.x + m->cur.w) > (m->zone->x + m->zone->w))
          m->cur.x = x + w - m->cur.w;
        m->cur.y = y + h;
        if ((m->cur.y + m->cur.h) > (m->zone->y + m->zone->h))
          m->cur.y = y + h - m->cur.h;
        e_menu_idler_before();
        _e_menu_activate_first();
        break;

      case E_MENU_POP_DIRECTION_AUTO:
        e_menu_idler_before();
        _e_menu_auto_place(m, x, y, w, h);
        break;

      default:
        m->cur.x = x + w;
        m->cur.y = y + h;
        e_menu_idler_before();
        break;
     }
   pmi = _e_menu_item_active_get();
   if (pmi) e_menu_item_active_set(pmi, 0);
}

static void
_e_menu_item_unhilight(E_Menu_Item *mi)
{
   if (!mi->hilighted) return;
   mi->hilighted = 0;
   if (mi->bg_object)
     edje_object_signal_emit(mi->bg_object, "e,state,unselected", "e");
   if (mi->icon_bg_object)
     edje_object_signal_emit(mi->icon_bg_object, "e,state,unselected", "e");
   if (isedje(mi->label_object))
     edje_object_signal_emit(mi->label_object, "e,state,unselected", "e");
   if (isedje(mi->submenu_object))
     edje_object_signal_emit(mi->submenu_object, "e,state,unselected", "e");
   if (isedje(mi->toggle_object))
     edje_object_signal_emit(mi->toggle_object, "e,state,unselected", "e");
   if (mi->icon_key)
     {
        if (mi->icon_object)
          {
             if (isedje(mi->icon_object))
               edje_object_signal_emit(mi->icon_object, "e,state,unselected", "e");
             else
               e_icon_selected_set(mi->icon_object, EINA_FALSE);
          }
     }
   edje_object_signal_emit(mi->menu->bg_object, "e,state,unselected", "e");
}

E_API void
e_menu_deactivate(E_Menu *m)
{
   Eina_List *l;
   E_Menu_Item *mi;

   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   if (!m->active) return;

   EINA_LIST_FOREACH(m->items, l, mi)
     {
        if (mi->submenu) e_menu_deactivate(mi->submenu);
     }

   m->cur.visible = 0;
   m->active = 0;
   if (m->post_deactivate_cb.func)
     m->post_deactivate_cb.func(m->post_deactivate_cb.data, m);
   if (m->parent_item)
     _e_menu_item_unhilight(m->parent_item);
}

E_API int
e_menu_freeze(E_Menu *m)
{
   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, 0);
   m->frozen++;
   return m->frozen;
}

E_API int
e_menu_thaw(E_Menu *m)
{
   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, 0);
   m->frozen--;
   if (m->frozen < 0) m->frozen = 0;
   return m->frozen;
}

E_API void
e_menu_title_set(E_Menu *m, const char *title)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   if ((m->header.title) && (title) && (!strcmp(m->header.title, title)))
     return;
   if (m->header.title)
     {
        eina_stringshare_del(m->header.title);
        m->header.title = NULL;
     }
   if (title) m->header.title = eina_stringshare_add(title);
   else m->header.title = NULL;
   m->changed = 1;
   if (!m->realized) return;
   edje_object_part_text_set(m->bg_object, "e.text.title", m->header.title);
   if (m->header.title)
     edje_object_signal_emit(m->bg_object, "e,action,show,title", "e");
   else
     edje_object_signal_emit(m->bg_object, "e,action,hide,title", "e");
   edje_object_message_signal_process(m->bg_object);
}

E_API void
e_menu_icon_file_set(E_Menu *m EINA_UNUSED, const char *icon EINA_UNUSED)
{
   /* FIXME: support menu icons
      E_OBJECT_CHECK(m);
      E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
    */
}

E_API void
e_menu_category_set(E_Menu *m, const char *category)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   if (m->category)
     {
        eina_stringshare_del(m->category);
        m->category = NULL;
     }
   if (category)
     m->category = eina_stringshare_add(category);
   else
     m->category = NULL;
   m->changed = 1;
}

E_API void
e_menu_category_data_set(char *category, void *data)
{
   E_Menu_Category *cat;

   cat = eina_hash_find(_e_menu_categories, category);
   if (cat)
     cat->data = data;
   else   /* if it isn't found create the new hash */
     {
        cat = calloc(1, sizeof(E_Menu_Category));
        cat->data = data;
        eina_hash_add(_e_menu_categories, category, cat);
     }
}

E_API E_Menu_Category_Callback *
e_menu_category_callback_add(char *category, void (*create_cb)(void *data, E_Menu *m, void *category_data), Ecore_Cb free_cb, void *data)
{
   E_Menu_Category *cat;
   E_Menu_Category_Callback *cb = NULL;

   cat = eina_hash_find(_e_menu_categories, category);
   if (!cat)   /* if it isn't found create the new hash */
     {
        cat = calloc(1, sizeof(E_Menu_Category));
        eina_hash_add(_e_menu_categories, category, cat);
     }
   if (cat)
     {
        cb = calloc(1, sizeof(E_Menu_Category_Callback));
        if (cb)
          {
             cb->data = data;
             cb->create = create_cb;
             cb->free = free_cb;
             cb->category = eina_stringshare_add(category);
             cat->callbacks = eina_list_append(cat->callbacks, cb);
          }
     }
   return cb;
}

E_API void
e_menu_category_callback_del(E_Menu_Category_Callback *cb)
{
   E_Menu_Category *cat;

   if (cb)
     {
        cat = eina_hash_find(_e_menu_categories, cb->category);
        if (cat)
          cat->callbacks = eina_list_remove(cat->callbacks, cb);
        eina_stringshare_del(cb->category);
        free(cb);
     }
}

E_API void
e_menu_pre_activate_callback_set(E_Menu *m, void (*func)(void *data, E_Menu *m), void *data)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   m->pre_activate_cb.func = func;
   m->pre_activate_cb.data = data;
}

E_API void
e_menu_post_deactivate_callback_set(E_Menu *m, void (*func)(void *data, E_Menu *m), void *data)
{
   E_OBJECT_CHECK(m);
   E_OBJECT_TYPE_CHECK(m, E_MENU_TYPE);
   m->post_deactivate_cb.func = func;
   m->post_deactivate_cb.data = data;
}

E_API E_Menu *
e_menu_root_get(E_Menu *m)
{
   E_Menu *ret;

   E_OBJECT_CHECK_RETURN(m, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, NULL);
   ret = m;
   while ((ret->parent_item) && (ret->parent_item->menu))
     ret = ret->parent_item->menu;

   return ret;
}

E_API E_Menu_Item *
e_menu_item_new(E_Menu *m)
{
   E_Menu_Item *mi;

   E_OBJECT_CHECK_RETURN(m, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, NULL);
   mi = E_OBJECT_ALLOC(E_Menu_Item, E_MENU_ITEM_TYPE, _e_menu_item_free);
   mi->menu = m;
   mi->menu->items = eina_list_append(mi->menu->items, mi);
   mi->list_position = eina_list_last(mi->menu->items);
   return mi;
}

E_API E_Menu_Item *
e_menu_item_new_relative(E_Menu *m, E_Menu_Item *rel)
{
   E_Menu_Item *mi;
   E_OBJECT_CHECK_RETURN(m, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, NULL);
   if (rel)
     {
        E_OBJECT_CHECK_RETURN(rel, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(rel, E_MENU_ITEM_TYPE, NULL);
        if (rel->menu != m) return NULL;
     }

   mi = E_OBJECT_ALLOC(E_Menu_Item, E_MENU_ITEM_TYPE, _e_menu_item_free);
   mi->menu = m;

   if (rel)
     {
        Eina_List *l;

        l = eina_list_data_find_list(m->items, rel);
        m->items = eina_list_append_relative_list(m->items, mi, l);
        mi->list_position = l->next;
     }
   else
     {
        m->items = eina_list_prepend(m->items, mi);
        mi->list_position = m->items;
     }

   return mi;
}

E_API E_Menu_Item *
e_menu_item_nth(E_Menu *m, int n)
{
   E_OBJECT_CHECK_RETURN(m, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MENU_TYPE, NULL);
   return (E_Menu_Item *)eina_list_nth(m->items, n);
}

E_API int
e_menu_item_num_get(const E_Menu_Item *mi)
{
   const Eina_List *l;
   const E_Menu_Item *mi2;
   int i = 0;

   E_OBJECT_CHECK_RETURN(mi, -1);
   E_OBJECT_CHECK_RETURN(mi->menu, -1);
   E_OBJECT_TYPE_CHECK_RETURN(mi, E_MENU_TYPE, -1);
   EINA_LIST_FOREACH(mi->menu->items, l, mi2)
     {
        if (mi2 == mi) return i;
        i++;
     }
   return -1;
}

E_API void
e_menu_item_icon_file_set(E_Menu_Item *mi, const char *icon)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->icon) && (icon) && (!strcmp(icon, mi->icon))) ||
       ((!mi->icon) && (!icon)))
     return;
   if (mi->icon) eina_stringshare_del(mi->icon);
   if (mi->icon_key) eina_stringshare_del(mi->icon_key);
   mi->icon = NULL;
   mi->icon_key = NULL;
   if (icon)
     {
        mi->icon = eina_stringshare_add(icon);
        if (eina_str_has_extension(mi->icon, ".edj"))
          mi->icon_key = eina_stringshare_add("icon");
     }
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_icon_edje_set(E_Menu_Item *mi, const char *icon, const char *key)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->icon) && (icon) && (!strcmp(icon, mi->icon))) ||
       ((!mi->icon) && (!icon)) ||
       ((key) && (mi->icon_key) && (!strcmp(key, mi->icon_key))))
     return;
   eina_stringshare_replace(&mi->icon, icon);
   eina_stringshare_replace(&mi->icon_key, key);
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_label_set(E_Menu_Item *mi, const char *label)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->label) && (label) && (!strcmp(label, mi->label))) ||
       ((!mi->label) && (!label)))
     return;
   if (mi->label) eina_stringshare_del(mi->label);
   mi->label = NULL;
   if (label) mi->label = eina_stringshare_add(label);
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_submenu_set(E_Menu_Item *mi, E_Menu *sub)
{
   Eina_Bool submenu = EINA_FALSE;
   Evas_Object *o;
   Eina_List *tmp = NULL;
   int ww, hh;
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);

   if (mi->submenu == sub) return;
   tmp = _e_active_menus_copy_ref();
   submenu = !!mi->submenu;
   if (mi->submenu) e_object_unref(E_OBJECT(mi->submenu));
   if (sub) e_object_ref(E_OBJECT(sub));
   mi->submenu = sub;
   mi->changed = 1;
   mi->menu->changed = 1;
   if (!!sub == submenu) goto out;
   if (!mi->bg_object) goto out;
   if (sub) e_object_ref(E_OBJECT(sub));
   _e_menu_lock = EINA_TRUE;
   if ((mi->submenu) || (mi->submenu_pre_cb.func))
     {
        if (mi->submenu_object)
          {
             if (isedje(mi->submenu_object))
               {
                  /* already have a correct submenu object, don't re-set it */
                  _e_menu_lock = EINA_FALSE;
                  if (sub) e_object_unref(E_OBJECT(sub));
                  edje_object_size_min_calc(mi->submenu_object, &ww, &hh);
                  mi->submenu_w = ww;
                  mi->submenu_h = hh;
                  E_WEIGHT(mi->submenu_object, 0, 1);
                  E_FILL(mi->submenu_object);
                  evas_object_size_hint_min_set(mi->submenu_object, ww, hh);
                  goto out;
               }
             evas_object_del(mi->submenu_object);
          }
        o = edje_object_add(mi->menu->evas);
        if (sub && (mi->submenu != sub)) e_object_ref(E_OBJECT(sub));
        mi->submenu = sub;
        mi->submenu_object = o;
        e_theme_edje_object_set(o, "base/theme/menus",
                                "e/widgets/menu/default/submenu");
        evas_object_pass_events_set(o, 1);
        elm_box_pack_end(mi->container_object, o);
        evas_object_show(o);
        edje_object_size_min_calc(mi->submenu_object, &ww, &hh);
        mi->submenu_w = ww;
        mi->submenu_h = hh;
        E_WEIGHT(mi->submenu_object, 0, 1);
        E_FILL(mi->submenu_object);
        evas_object_size_hint_min_set(mi->submenu_object, ww, hh);
        edje_object_part_swallow(mi->bg_object, "e.swallow.content",
                                 mi->container_object);
        edje_object_size_min_calc(mi->bg_object, &ww, &hh);
        E_WEIGHT(mi->bg_object, 1, 0);
        E_FILL(mi->bg_object);
        evas_object_size_hint_min_set(mi->bg_object, ww, hh);
     }
   else
     {
        if (mi->submenu_object) evas_object_del(mi->submenu_object);
        o = evas_object_rectangle_add(mi->menu->evas);
        mi->submenu_object = o;
        evas_object_color_set(o, 0, 0, 0, 0);
        evas_object_pass_events_set(o, 1);
        elm_box_pack_end(mi->container_object, o);
     }
   _e_menu_lock = EINA_FALSE;
   if (sub) e_object_unref(E_OBJECT(sub));
   if ((mi->submenu) || (mi->submenu_pre_cb.func))
     {
        if (e_theme_edje_object_set(mi->bg_object, "base/theme/menus",
                                    "e/widgets/menu/default/submenu_bg"))
          goto out;
     }

   e_theme_edje_object_set(mi->bg_object, "base/theme/menus",
                           "e/widgets/menu/default/item_bg");
out:
   _e_menu_list_free_unref(tmp);
}

E_API void
e_menu_item_separator_set(E_Menu_Item *mi, int sep)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->separator) && (sep)) || ((!mi->separator) && (!sep))) return;
   mi->separator = sep;
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_check_set(E_Menu_Item *mi, int chk)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->check) && (chk)) || ((!mi->check) && (!chk))) return;
   mi->check = chk;
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_radio_set(E_Menu_Item *mi, int rad)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (((mi->radio) && (rad)) || ((!mi->radio) && (!rad))) return;
   mi->radio = rad;
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_radio_group_set(E_Menu_Item *mi, int radg)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (mi->radio_group == radg) return;
   mi->radio_group = radg;
   mi->changed = 1;
   mi->menu->changed = 1;
}

E_API void
e_menu_item_toggle_set(E_Menu_Item *mi, int tog)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (mi->separator) return;
   if (tog)
     {
        mi->toggle = 1;
        if (mi->bg_object)
          edje_object_signal_emit(mi->bg_object, "e,state,on", "e");
        if (mi->icon_bg_object)
          edje_object_signal_emit(mi->icon_bg_object, "e,state,on", "e");
        if (isedje(mi->label_object))
          edje_object_signal_emit(mi->label_object, "e,state,on", "e");
        if (isedje(mi->submenu_object))
          edje_object_signal_emit(mi->submenu_object, "e,state,on", "e");
        if (isedje(mi->toggle_object))
          edje_object_signal_emit(mi->toggle_object, "e,state,on", "e");
        if (mi->menu->bg_object)
          edje_object_signal_emit(mi->menu->bg_object, "e,state,on", "e");
     }
   else
     {
        mi->toggle = 0;
        if (mi->bg_object)
          edje_object_signal_emit(mi->bg_object, "e,state,off", "e");
        if (mi->icon_bg_object)
          edje_object_signal_emit(mi->icon_bg_object, "e,state,off", "e");
        if (isedje(mi->label_object))
          edje_object_signal_emit(mi->label_object, "e,state,off", "e");
        if (isedje(mi->submenu_object))
          edje_object_signal_emit(mi->submenu_object, "e,state,off", "e");
        if (isedje(mi->toggle_object))
          edje_object_signal_emit(mi->toggle_object, "e,state,off", "e");
        if (mi->menu->bg_object)
          edje_object_signal_emit(mi->menu->bg_object, "e,state,off", "e");
     }
   if (tog)
     {
        if (mi->radio)
          {
             const Eina_List *l;
             E_Menu_Item *mi2;

             EINA_LIST_FOREACH(mi->menu->items, l, mi2)
               {
                  if ((mi2 != mi) && (mi2->radio) &&
                      (mi2->radio_group == mi->radio_group))
                    e_menu_item_toggle_set(mi2, 0);
               }
          }
     }
}

E_API int
e_menu_item_toggle_get(E_Menu_Item *mi)
{
   E_OBJECT_CHECK_RETURN(mi, 0);
   E_OBJECT_TYPE_CHECK_RETURN(mi, E_MENU_ITEM_TYPE, 0);
   return mi->toggle;
}

E_API void
e_menu_item_callback_set(E_Menu_Item *mi, void (*func)(void *data, E_Menu *m, E_Menu_Item *mi), const void *data)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   mi->cb.func = func;
   mi->cb.data = (void *)data;
}

E_API void
e_menu_item_realize_callback_set(E_Menu_Item *mi, void (*func)(void *data, E_Menu *m, E_Menu_Item *mi), void *data)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   mi->realize_cb.func = func;
   mi->realize_cb.data = data;
}

E_API void
e_menu_item_submenu_pre_callback_set(E_Menu_Item *mi, void (*func)(void *data, E_Menu *m, E_Menu_Item *mi), const void *data)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   mi->submenu_pre_cb.func = func;
   mi->submenu_pre_cb.data = (void *)data;
   if (!mi->submenu_post_cb.func)
     mi->submenu_post_cb.func = _e_menu_cb_item_submenu_post_default;
}

E_API void
e_menu_item_submenu_post_callback_set(E_Menu_Item *mi, void (*func)(void *data, E_Menu *m, E_Menu_Item *mi), const void *data)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   mi->submenu_post_cb.func = func;
   mi->submenu_post_cb.data = (void *)data;
}

E_API void
e_menu_item_drag_callback_set(E_Menu_Item *mi, void (*func)(void *data, E_Menu *m, E_Menu_Item *mi), void *data)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   mi->drag_cb.func = func;
   mi->drag_cb.data = data;
}

E_API void
e_menu_item_active_set(E_Menu_Item *mi, int active)
{
   Eina_List *tmp = NULL;

   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (mi->separator) return;
   active = !!active;
   if (active == mi->active) return;
   if ((active) && (!mi->active))
     {
        E_Menu_Item *pmi;

        if (mi->disable) return;
        pmi = _e_menu_item_active_get();
        if (mi == pmi) return;
        if (pmi)
          {
             tmp = _e_active_menus_copy_ref();
             e_menu_item_active_set(pmi, 0);
          }
        if (_e_prev_active_menu_item && (mi != _e_prev_active_menu_item))
          {
             if (mi->menu->parent_item && (_e_prev_active_menu_item != mi->menu->parent_item))
               _e_menu_submenu_deactivate(_e_prev_active_menu_item);
          }
        mi->active = 1;
        mi->hilighted = 1;
        _e_active_menu_item = mi;
        if (mi->bg_object)
          edje_object_signal_emit(mi->bg_object, "e,state,selected", "e");
        if (mi->icon_bg_object)
          edje_object_signal_emit(mi->icon_bg_object, "e,state,selected", "e");
        if (isedje(mi->label_object))
          edje_object_signal_emit(mi->label_object, "e,state,selected", "e");
        if (isedje(mi->submenu_object))
          edje_object_signal_emit(mi->submenu_object, "e,state,selected", "e");
        if (isedje(mi->toggle_object))
          edje_object_signal_emit(mi->toggle_object, "e,state,selected", "e");
        if (mi->icon_key)
          {
             if (mi->icon_object)
               {
                  if (isedje(mi->icon_object))
                    edje_object_signal_emit(mi->icon_object, "e,state,selected", "e");
                  else
                    e_icon_selected_set(mi->icon_object, EINA_TRUE);
               }
          }
        edje_object_signal_emit(mi->menu->bg_object, "e,state,selected", "e");
        _e_menu_submenu_activate(mi);
     }
   else if ((!active) && (mi->active))
     {
        tmp = _e_active_menus_copy_ref();
        mi->active = 0;
        _e_prev_active_menu_item = mi;
        _e_active_menu_item = NULL;
        if (!((mi->submenu) && (mi->submenu->active)))
          _e_menu_item_unhilight(mi);
     }
   _e_menu_list_free_unref(tmp);
}

E_API E_Menu_Item *
e_menu_item_active_get(void)
{
   return _e_active_menu_item;
}

E_API void
e_menu_active_item_activate(void)
{
   _e_menu_active_call();
   _e_menu_deactivate_all();
}

E_API void
e_menu_item_disabled_set(E_Menu_Item *mi, int disable)
{
   E_OBJECT_CHECK(mi);
   E_OBJECT_TYPE_CHECK(mi, E_MENU_ITEM_TYPE);
   if (mi->separator) return;
   if ((disable))
     {
        if (mi->active) e_menu_item_active_set(mi, 0);
        mi->disable = 1;
        if (mi->icon_bg_object)
          edje_object_signal_emit(mi->icon_bg_object, "e,state,disable", "e");
        if (isedje(mi->label_object))
          edje_object_signal_emit(mi->label_object, "e,state,disable", "e");
        if (isedje(mi->toggle_object))
          edje_object_signal_emit(mi->toggle_object, "e,state,disable", "e");
     }
   else
     {
        mi->disable = 0;
        if (mi->icon_bg_object)
          edje_object_signal_emit(mi->icon_bg_object, "e,state,enable", "e");
        if (isedje(mi->label_object))
          edje_object_signal_emit(mi->label_object, "e,state,enable", "e");
        if (isedje(mi->toggle_object))
          edje_object_signal_emit(mi->toggle_object, "e,state,enable", "e");
     }
}

E_API void
e_menu_idler_before(void)
{
   /* when e goes "idle" this gets called so leave all our hard work till */
   /* idle time to avoid falling behind the user. just evaluate the high */
   /* level state machine */
   Eina_List *l, *removals = NULL, *tmp;
   E_Menu *m;
   int active_count = 0;

   if (pending_feed)
     {
        e_comp_canvas_feed_mouse_up(pending_activate_time);
        pending_feed = 0;
     }

   /* add refcount to all menus we will work with */
   tmp = _e_active_menus_copy_ref();
   /* phase 1. hide all the menus that want to be hidden */
   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if ((!m->cur.visible) && (m->prev.visible))
          {
             m->prev.visible = m->cur.visible;
             e_object_ref(E_OBJECT(m));
             evas_object_pass_events_set(m->comp_object, 1);
             if (m->container_object)
               {
                  evas_object_intercept_move_callback_del(m->container_object, _e_menu_cb_intercept_container_move);
                  evas_object_intercept_resize_callback_del(m->container_object, _e_menu_cb_intercept_container_resize);
               }
             evas_object_hide(m->comp_object);
          }
     }
   /* phase 2. move & reisze all the menus that want to moves/resized */
   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if (m->frozen || (!m->active) || (!m->zone)) continue;
        if (!m->realized) _e_menu_realize(m);
        if (!m->realized) continue;
        if (((m->cur.w) != (m->prev.w)) ||
            ((m->cur.h) != (m->prev.h)))
          {
             int w, h;

             m->prev.w = m->cur.w;
             m->prev.h = m->cur.h;
             w = m->cur.w;
             h = m->cur.h;
             evas_object_resize(m->comp_object, w, h);
          }
        if (((m->cur.x) != (m->prev.x)) ||
            ((m->cur.y) != (m->prev.y)))
          {
             if (!m->parent_item)
               {
                  int x, y, w, h;

                  e_zone_useful_geometry_get(m->zone, &x, &y, &w, &h);
                  if (m->cur.w <= w)
                    {
                       if ((m->cur.x + m->cur.w) > (x + w))
                         m->cur.x = x + w - m->cur.w;
                    }
                  if (m->cur.h <= h)
                    {
                       if ((m->cur.y + m->cur.h) > (y + h))
                         m->cur.y = y + h - m->cur.h;
                    }
               }
             m->prev.x = m->cur.x;
             m->prev.y = m->cur.y;
             _e_menu_lock = 1;
             evas_object_move(m->comp_object, m->cur.x, m->cur.y);
             _e_menu_lock = 0;
          }
     }
   /* phase 3. show all the menus that want to be shown */
   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if (m->frozen) continue;
        if (!m->realized) continue;
        if (m->cur.visible)
          {
             m->prev.visible = m->cur.visible;
             evas_object_pass_events_set(m->comp_object, 0);
             evas_object_show(m->comp_object);
          }
     }
   /* phase 4. de-activate... */
   EINA_LIST_REVERSE_FOREACH(_e_active_menus, l, m)
     {
        if (!m->active)
          {
             if ((m->realized) &&
                 (!evas_object_visible_get(m->comp_object)))
               {
                  _e_menu_unrealize(m);
                  removals = eina_list_append(removals, m);
               }
          }
     }
   EINA_LIST_FREE(removals, m)
     {
        if (m->in_active_list)
          {
             _e_active_menus = eina_list_remove(_e_active_menus, m);
             m->in_active_list = 0;
             e_object_unref(E_OBJECT(m));
          }
     }
   /* del refcount to all menus we worked with */
   _e_menu_list_free_unref(tmp);

   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if (m->active) active_count++;
     }
   if (!active_count)
     {
        if (_e_menu_win == e_comp->ee_win)
          {
             if (_e_menu_grabbed)
               {
                  e_comp_ungrab_input(1, 1);
                  _e_menu_grabbed = EINA_FALSE;
               }
             _e_menu_win = UINT_MAX;
             e_bindings_disabled_set(0);
          }
     }
}

E_API Eina_Bool
e_menu_is_active(void)
{
   return _e_menu_win == e_comp->ee_win;
}

E_API E_Menu *
e_menu_active_get(void)
{
   return _e_active_menus ? eina_list_last_data_get(_e_active_menus) : NULL;
}

/* local subsystem functions */
static void
_e_menu_dangling_cb(void *data)
{
   E_Menu *m = data;

   WRN("DANGLING SUBMENU: REF(%d)||MENU(%p)", e_object_ref_get(data), data);
   m->dangling_job = NULL;
}

static void
_e_menu_free(E_Menu *m)
{
   Eina_List *l, *l_next;
   E_Menu_Item *mi;
   E_Menu_Category *cat = NULL;

   /* the foreign menu items */
   if (m->category) cat = eina_hash_find(_e_menu_categories, m->category);
   if (cat)
     {
        E_Menu_Category_Callback *cb;

        EINA_LIST_FOREACH(cat->callbacks, l, cb)
          {
             if (cb->free) cb->free(cb->data);
          }
     }
   eina_stringshare_replace(&m->category, NULL);
   if (m->parent_item)
     m->parent_item->submenu = NULL;
   _e_menu_unrealize(m);
   if (m->realized) return;
   EINA_LIST_FOREACH_SAFE(m->items, l, l_next, mi)
     e_object_del(E_OBJECT(mi));
   if (m->in_active_list)
     {
        _e_active_menus = eina_list_remove(_e_active_menus, m);
        m->in_active_list = 0;
     }
   if (m->header.title) eina_stringshare_del(m->header.title);
   if (m->header.icon_file) eina_stringshare_del(m->header.icon_file);
   if (m->dangling_job) ecore_job_del(m->dangling_job);
   free(m);
}

static void
_e_menu_item_free(E_Menu_Item *mi)
{
   if (mi == _e_active_menu_item) _e_active_menu_item = NULL;
   if (mi == _e_prev_active_menu_item) _e_prev_active_menu_item = NULL;
   if (mi->submenu)
     {
        int ref = 0;

        /* parent_item gets unset in a few places, reapply it for use in cleanup */
        if (!mi->submenu->parent_item)
          mi->submenu->parent_item = mi;
        /* menu may not have been deactivated, ensure deactivate callback is called */
        if (mi->active)
          _e_menu_submenu_deactivate(mi);
        /* submenus CANNOT exist without their parent menu+item, so ensure that they get deleted */
        if (mi->submenu)
          {
             ref = e_object_ref_get(E_OBJECT(mi->submenu)) - 1;
             e_object_unref(E_OBJECT(mi->submenu));
          }
        if (ref)
          {
             if (!mi->submenu->dangling_job)
               mi->submenu->dangling_job = ecore_job_add(_e_menu_dangling_cb, mi->submenu);
             mi->submenu->parent_item = NULL;
          }
     }
   if (mi->menu->realized) _e_menu_item_unrealize(mi);
   mi->menu->items = eina_list_remove(mi->menu->items, mi);
   if (mi->icon) eina_stringshare_del(mi->icon);
   if (mi->icon_key) eina_stringshare_del(mi->icon_key);
   if (mi->label) eina_stringshare_del(mi->label);
   free(mi);
}

static void
_e_menu_cb_intercept_item_move(void *data, Evas_Object *o, Evas_Coord x, Evas_Coord y)
{
   E_Menu_Item *mi;

   mi = data;
   mi->x = x;
   mi->y = y;
   evas_object_move(o, x, y);
   if ((mi->submenu) && (mi->submenu->parent_item))
     {
        mi->submenu->zone = mi->menu->zone;
        _e_menu_reposition(mi->submenu);
     }
}

static void
_e_menu_cb_intercept_item_resize(void *data, Evas_Object *o, Evas_Coord w, Evas_Coord h)
{
   E_Menu_Item *mi;

   mi = data;
   mi->w = w;
   mi->h = h;
   evas_object_resize(o, w, h);
   if ((mi->submenu) && (mi->submenu->parent_item))
     _e_menu_reposition(mi->submenu);
}

static void
_e_menu_cb_intercept_container_move(void *data, Evas_Object *o, Evas_Coord x, Evas_Coord y)
{
   E_Menu *m;

   m = data;
   m->container_x = x;
   m->container_y = y;
   if (m->parent_item) _e_menu_reposition(m);
   evas_object_move(o, x, y);
}

static void
_e_menu_cb_intercept_container_resize(void *data, Evas_Object *o, Evas_Coord w, Evas_Coord h)
{
   E_Menu *m;

   m = data;
   m->container_w = w;
   m->container_h = h;
   if (m->parent_item) _e_menu_reposition(m);
   evas_object_resize(o, w, h);
}

static void
_e_menu_hide_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_unref(data);
}

static void
_e_menu_item_realize(E_Menu_Item *mi)
{
   Evas_Object *o;
   Evas_Coord ww = 1, hh = 1;

   /* and set up initial item state */
   if (mi->separator)
     {
        o = edje_object_add(mi->menu->evas);
        mi->separator_object = o;
        e_theme_edje_object_set(o, "base/theme/menus",
                                "e/widgets/menu/default/separator");
        edje_object_size_min_calc(mi->separator_object, &ww, &hh);
        E_FILL(mi->separator_object);
        mi->separator_w = ww;
        mi->separator_h = hh;
        evas_object_size_hint_min_set(mi->separator_object, ww, hh);
        elm_box_pack_end(mi->menu->container_object, mi->separator_object);
        evas_object_show(o);
     }
   else
     {
        o = edje_object_add(mi->menu->evas);
        mi->bg_object = o;
        evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_IN, _e_menu_cb_item_in, mi);
        evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_OUT, _e_menu_cb_item_out, mi);
        evas_object_intercept_move_callback_add(o, _e_menu_cb_intercept_item_move, mi);
        evas_object_intercept_resize_callback_add(o, _e_menu_cb_intercept_item_resize, mi);
        if ((mi->submenu) || (mi->submenu_pre_cb.func))
          {
             if (!e_theme_edje_object_set(mi->bg_object, "base/theme/menus",
                                          "e/widgets/menu/default/submenu_bg"))
               goto no_submenu_item;
          }
        else
          {
no_submenu_item:
             e_theme_edje_object_set(mi->bg_object, "base/theme/menus",
                                     "e/widgets/menu/default/item_bg");
          }
        o = elm_box_add(e_comp->elm);
        mi->container_object = o;
        elm_box_horizontal_set(o, 1);
        evas_object_show(o);


        if (mi->check)
          {
             o = edje_object_add(mi->menu->evas);
             mi->toggle_object = o;
             e_theme_edje_object_set(o, "base/theme/menus",
                                     "e/widgets/menu/default/check");
             edje_object_size_min_calc(mi->toggle_object, &ww, &hh);
             mi->toggle_w = ww;
             mi->toggle_h = hh;
             E_WEIGHT(mi->toggle_object, 0, 1);
             E_FILL(mi->toggle_object);
             evas_object_size_hint_min_set(mi->toggle_object, ww, hh);
             elm_box_pack_end(mi->container_object, o);
             evas_object_show(o);
          }
        else if (mi->radio)
          {
             o = edje_object_add(mi->menu->evas);
             mi->toggle_object = o;
             e_theme_edje_object_set(o, "base/theme/menus",
                                     "e/widgets/menu/default/radio");
             edje_object_size_min_calc(mi->toggle_object, &ww, &hh);
             mi->toggle_w = ww;
             mi->toggle_h = hh;
             E_WEIGHT(mi->toggle_object, 0, 1);
             E_FILL(mi->toggle_object);
             evas_object_size_hint_min_set(mi->toggle_object, ww, hh);
             elm_box_pack_end(mi->container_object, o);
             evas_object_show(o);
          }
        else
          {
             o = evas_object_rectangle_add(mi->menu->evas);
             mi->toggle_object = o;
             evas_object_color_set(o, 0, 0, 0, 0);
             elm_box_pack_end(mi->container_object, o);
          }
        if ((!e_config->menu_icons_hide) && ((mi->icon) || (mi->realize_cb.func)))
          {
             int icon_w = 0, icon_h = 0;

             o = edje_object_add(mi->menu->evas);
             if (e_theme_edje_object_set(o, "base/theme/menus",
                                         "e/widgets/menu/default/icon"))
               {
                  mi->icon_bg_object = o;
               }
             else
               {
                  evas_object_del(o);
                  o = NULL;
               }
             //if (o) evas_object_pass_events_set(o, 1);

             /* FIXME: Not sure why there are two different tries to get the icon size, surely only the last one is needed. */
             /* FIXME: Do it this way later, when e_app_icon_add() just registers a request for an icon to be filled in when it's ready.
                if (mi->app)
                {
                  o = e_app_icon_add(mi->menu->evas, mi->app);
                  mi->icon_object = o;
                  e_icon_size_get(mi->icon_object, &icon_w, &icon_h);
                }
                else
              */
             if (mi->icon)
               {
                  /* This is done this way to match up with how e_app_icon_add does it. */
                  if (mi->icon_key)
                    {
                       Evas_Coord iww, ihh;

                       o = edje_object_add(mi->menu->evas);
                       if (edje_object_file_set(o, mi->icon, mi->icon_key))
                         {
                            mi->icon_object = o;
                            edje_object_size_max_get(o, &iww, &ihh);
                            icon_w = iww;
                            icon_h = ihh;
                         }
                       else
                         {
                            evas_object_del(o);
                            o = NULL;
                         }
                    }
                  if (!mi->icon_object)
                    {
                       o = e_icon_add(mi->menu->evas);
                       mi->icon_object = o;
                       e_icon_scale_size_set(o, e_util_icon_size_normalize(96 * e_scale));
                       e_icon_preload_set(mi->icon_object, 1);
                       e_icon_file_set(o, mi->icon);
                       e_icon_fill_inside_set(mi->icon_object, 1);
                       e_icon_size_get(mi->icon_object, &icon_w, &icon_h);
                    }
               }
             if (_e_menu_realize_call(mi))
               {
                  o = mi->icon_object;
                  e_icon_fill_inside_set(o, 1);
                  e_icon_size_get(o, &icon_w, &icon_h);
               }

             evas_object_show(o);

             if (mi->icon_bg_object)
               {
                  evas_object_size_hint_min_set(mi->icon_object, 0, 0);
                  edje_object_part_swallow(mi->icon_bg_object,
                                           "e.swallow.content",
                                           mi->icon_object);
                  edje_object_size_min_calc(mi->icon_bg_object, &ww, &hh);
                  mi->icon_w = ww;
                  mi->icon_h = hh;
                  evas_object_size_hint_min_set(mi->icon_bg_object, ww, hh);
                  E_WEIGHT(mi->icon_bg_object, 0, 1);
                  E_FILL(mi->icon_bg_object);
                  elm_box_pack_end(mi->container_object, mi->icon_bg_object);
                  evas_object_show(mi->icon_bg_object);
               }
             else
               {
                  o = edje_object_add(mi->menu->evas);
                  e_icon_size_get(mi->icon_object, &icon_w, &icon_h);
                  mi->icon_w = icon_w;
                  mi->icon_h = icon_h;
                  E_WEIGHT(mi->icon_object, 0, 1);
                  E_FILL(mi->icon_object);
                  evas_object_size_hint_min_set(mi->icon_object, ww, hh);
                  elm_box_pack_end(mi->container_object, o);
                  evas_object_show(o);
               }
          }
        else
          {
             o = evas_object_rectangle_add(mi->menu->evas);
             mi->icon_object = o;
             evas_object_color_set(o, 0, 0, 0, 0);
             elm_box_pack_end(mi->container_object, o);
          }

        if (mi->label)
          {
             o = edje_object_add(mi->menu->evas);
             mi->label_object = o;
             e_theme_edje_object_set(o, "base/theme/menus",
                                     "e/widgets/menu/default/label");
             /* default label */
             edje_object_part_text_set(o, "e.text.label", mi->label);
             edje_object_size_min_calc(mi->label_object, &ww, &hh);
             mi->label_w = ww;
             mi->label_h = hh;
             evas_object_size_hint_min_set(mi->label_object, ww, hh);
             E_EXPAND(mi->label_object);
             E_FILL(mi->label_object);
             elm_box_pack_end(mi->container_object, o);
             evas_object_show(o);
          }
        else
          {
             o = evas_object_rectangle_add(mi->menu->evas);
             mi->label_object = o;
             evas_object_color_set(o, 0, 0, 0, 0);
             elm_box_pack_end(mi->container_object, o);
          }
        if ((mi->submenu) || (mi->submenu_pre_cb.func))
          {
             o = edje_object_add(mi->menu->evas);
             mi->submenu_object = o;
             e_theme_edje_object_set(o, "base/theme/menus",
                                     "e/widgets/menu/default/submenu");
             edje_object_size_min_calc(mi->submenu_object, &ww, &hh);
             mi->submenu_w = ww;
             mi->submenu_h = hh;
             E_WEIGHT(mi->submenu_object, 0, 1);
             E_FILL(mi->submenu_object);
             evas_object_size_hint_min_set(mi->submenu_object, ww, hh);
             elm_box_pack_end(mi->container_object, o);
             evas_object_show(o);
          }
        else
          {
             o = evas_object_rectangle_add(mi->menu->evas);
             mi->submenu_object = o;
             evas_object_color_set(o, 0, 0, 0, 0);
             elm_box_pack_end(mi->container_object, o);
          }

        edje_object_part_swallow(mi->bg_object, "e.swallow.content",
                                 mi->container_object);

        elm_box_pack_end(mi->menu->container_object, mi->bg_object);
        evas_object_show(mi->container_object);
        evas_object_show(mi->bg_object);
     }
   if (mi->active) e_menu_item_active_set(mi, 1);
   if (mi->toggle) e_menu_item_toggle_set(mi, 1);
   if (mi->disable) e_menu_item_disabled_set(mi, 1);
}

static void
_e_menu_realize(E_Menu *m)
{
   Evas_Object *o;
   Eina_List *l;
   E_Menu_Item *mi;

   if (m->realized || (!m->items)) return;

   if (m->parent_item && m->parent_item->menu)
     m->zone = m->parent_item->menu->zone;
   if (!m->zone) return; //menu not ready!
   m->evas = evas_object_evas_get(e_comp->elm);
   evas_event_freeze(m->evas);

   o = edje_object_add(m->evas);
   m->bg_object = o;
   e_theme_edje_object_set(o, "base/theme/menus", "e/widgets/menu/default/background");
   if (m->header.title)
     {
        edje_object_part_text_set(o, "e.text.title", m->header.title);
        edje_object_signal_emit(o, "e,action,show,title", "e");
        edje_object_message_signal_process(o);
     }

   m->comp_object = e_comp_object_util_add(o, E_COMP_OBJECT_TYPE_MENU);
   if (!m->parent_item)
     e_comp_object_util_autoclose(m->comp_object, _e_menu_cb_mouse_evas_down, _e_menu_cb_key_down, m);
   evas_object_event_callback_add(m->comp_object, EVAS_CALLBACK_HIDE, _e_menu_hide_cb, m);
   evas_object_layer_set(m->comp_object, E_LAYER_MENU);
   evas_object_geometry_set(m->comp_object, m->cur.x, m->cur.y, m->cur.w, m->cur.h);

   o = elm_box_add(e_comp->elm);
   m->container_object = o;
   evas_object_intercept_move_callback_add(o, _e_menu_cb_intercept_container_move, m);
   evas_object_intercept_resize_callback_add(o, _e_menu_cb_intercept_container_resize, m);

   EINA_LIST_FOREACH(m->items, l, mi)
     _e_menu_item_realize(mi);

   edje_object_part_swallow(m->bg_object, "e.swallow.content", m->container_object);

   _e_menu_items_layout_update(m);


   evas_event_thaw(m->evas);
   evas_event_thaw_eval(m->evas);
   m->realized = 1;
}

static void
_e_menu_items_layout_update(E_Menu *m)
{
   Eina_List *l;
   E_Menu_Item *mi;
   Evas_Coord bw, bh, mw = 0, mh = 0;
   int toggles_on = 0;
   int icons_on = 0;
   int labels_on = 0;
   int submenus_on = 0;
   int min_icon_w = 0, min_icon_h = 0;
   int min_label_w = 0, min_label_h = 0;
   int min_submenu_w = 0, min_submenu_h = 0;
   int min_toggle_w = 0, min_toggle_h = 0;
   int min_w = 0, min_h = 1;
   int zh = 0, ms = 0, maxh = 0;
   unsigned int cur_items = 0, max_items = -1;

   if (!m->zone) return;
   EINA_LIST_FOREACH(m->items, l, mi)
     {
        if (mi->icon) icons_on = 1;
        if (mi->icon_object) icons_on = 1;
        if (mi->label) labels_on = 1;
        if (mi->submenu) submenus_on = 1;
        if (mi->submenu_pre_cb.func) submenus_on = 1;
        if (mi->check) toggles_on = 1;
        if (mi->radio) toggles_on = 1;

        if (mi->icon_w > min_icon_w) min_icon_w = mi->icon_w;
        if (mi->icon_h > min_icon_h) min_icon_h = mi->icon_h;
        if (mi->label_w > min_label_w) min_label_w = mi->label_w;
        if (mi->label_h > min_label_h) min_label_h = mi->label_h;
        if (mi->submenu_w > min_submenu_w) min_submenu_w = mi->submenu_w;
        if (mi->submenu_h > min_submenu_h) min_submenu_h = mi->submenu_h;
        if (mi->toggle_w > min_toggle_w) min_toggle_w = mi->toggle_w;
        if (mi->toggle_h > min_toggle_h) min_toggle_h = mi->toggle_h;
     }
   if (labels_on)
     {
        if (submenus_on)
          {
             if (min_label_h < min_submenu_h)
               min_label_h = min_submenu_h;
          }
        if (toggles_on)
          {
             if (min_label_h < min_toggle_h)
               min_label_h = min_toggle_h;
          }
        if ((icons_on) && (min_icon_h > 0))
          {
             min_icon_w = (min_icon_w * min_label_h) / min_icon_h;
             min_icon_h = min_label_h;
          }
        min_w = min_label_w + min_icon_w + min_submenu_w + min_toggle_w;
        min_h = min_label_h;
     }
   else if (icons_on)
     {
        if (submenus_on)
          {
             if (min_icon_h < min_submenu_h)
               min_icon_h = min_submenu_h;
          }
        if (toggles_on)
          {
             if (min_icon_h < min_toggle_h)
               min_icon_h = min_toggle_h;
          }
        min_w = min_icon_w + min_toggle_w + min_submenu_w;
        min_h = min_icon_h;
     }
   else if (toggles_on)
     {
        if (submenus_on)
          {
             if (min_toggle_h < min_submenu_h)
               min_toggle_h = min_submenu_h;
          }
        min_w = min_toggle_w + min_submenu_w;
        min_h = min_toggle_h;
     }
   if (min_h * eina_list_count(m->items) >= (unsigned int)m->zone->h)
     {
        e_zone_useful_geometry_get(m->zone, NULL, NULL, NULL, &zh);
        maxh = zh * 4;
        if (maxh > 30000) maxh = 30000;  // 32k x 32k mx coord limit for wins
        max_items = (maxh / min_h) - 1;
     }
   EINA_LIST_FOREACH(m->items, l, mi)
     {
        if (cur_items >= max_items)
          {
             _e_menu_item_unrealize(mi);
             continue;
          }
        cur_items++;
        if (mi->separator)
          {
             E_WEIGHT(mi->separator_object, 1, 0);
             E_FILL(mi->separator_object);
             evas_object_size_hint_min_set(mi->separator_object, mi->separator_w, mi->separator_h);
             evas_object_size_hint_max_set(mi->separator_object, -1, mi->separator_h);
             ms += mi->separator_h;
             continue;
          }
        E_WEIGHT(mi->toggle_object, 0, toggles_on);
        E_FILL(mi->toggle_object);
        evas_object_size_hint_min_set(mi->toggle_object, min_toggle_w * toggles_on, min_toggle_h * toggles_on);
        if (icons_on)
          {
             E_WEIGHT(mi->icon_bg_object ?: mi->icon_object, 0, 1);
             E_FILL(mi->icon_bg_object ?: mi->icon_object);
             evas_object_size_hint_min_set(mi->icon_bg_object ?: mi->icon_object, min_icon_w, min_icon_h);
          }
        else
          {
             E_WEIGHT(mi->icon_object, 0, 1);
             E_FILL(mi->icon_object);
             evas_object_size_hint_min_set(mi->icon_object, 0, 0);
          }

        E_WEIGHT(mi->label_object, 0, 1 * labels_on);
        E_FILL(mi->label_object);
        evas_object_size_hint_min_set(mi->label_object, min_label_w * labels_on, min_label_h * labels_on);

        E_WEIGHT(mi->submenu_object, 0, 1 * submenus_on);
        E_FILL(mi->submenu_object);
        evas_object_size_hint_min_set(mi->submenu_object, min_submenu_w * submenus_on, min_submenu_h * submenus_on);

        evas_object_size_hint_min_set(mi->container_object,
                                        min_w, min_h);
        edje_object_size_min_calc(mi->bg_object, &mw, &mh);
        E_WEIGHT(mi->bg_object, 0, 1);
        E_FILL(mi->bg_object);
        evas_object_size_hint_min_set(mi->bg_object, mw, mh);
        ms += mh;
     }
   elm_box_recalculate(m->container_object);
   evas_object_size_hint_min_get(m->container_object, &bw, &bh);
   evas_object_size_hint_max_set(m->container_object, bw, bh);
   edje_object_size_min_calc(m->bg_object, &mw, &mh);
   m->cur.w = mw;
   m->cur.h = mh;
}

static void
_e_menu_item_unrealize(E_Menu_Item *mi)
{
   if (mi->separator_object) evas_object_del(mi->separator_object);
   mi->separator_object = NULL;
   if (mi->bg_object) evas_object_del(mi->bg_object);
   mi->bg_object = NULL;
   if (mi->container_object) evas_object_del(mi->container_object);
   mi->container_object = NULL;
   if (mi->toggle_object) evas_object_del(mi->toggle_object);
   mi->toggle_object = NULL;
   if (mi->icon_bg_object) evas_object_del(mi->icon_bg_object);
   mi->icon_bg_object = NULL;
   if (mi->icon_object) evas_object_del(mi->icon_object);
   mi->icon_object = NULL;
   if (mi->label_object) evas_object_del(mi->label_object);
   mi->label_object = NULL;
   if (mi->submenu_object) evas_object_del(mi->submenu_object);
   mi->submenu_object = NULL;
}

static void
_e_menu_unrealize(E_Menu *m)
{
   Eina_List *l;
   E_Menu_Item *mi;

   if (!m->realized) return;
   /* freeze+thaw here breaks the universe. don't do it. */
   //evas_event_freeze(m->evas);
   if (m->comp_object)
     evas_object_event_callback_del(m->comp_object, EVAS_CALLBACK_HIDE, _e_menu_hide_cb);
   if (m->cur.visible && m->comp_object && (!stopping))
     {
        /* force unref in smart object */
        if (m->container_object)
          {
             evas_object_intercept_move_callback_del(m->container_object, _e_menu_cb_intercept_container_move);
             evas_object_intercept_resize_callback_del(m->container_object, _e_menu_cb_intercept_container_resize);
          }
        evas_object_pass_events_set(m->comp_object, 1);
        evas_object_hide(m->comp_object);
        E_FREE_FUNC(m->comp_object, evas_object_del);
        return;
     }
   evas_object_hide(m->comp_object);
   evas_object_del(m->comp_object);
   if (stopping && m->comp_object) evas_object_unref(m->comp_object);
   EINA_LIST_FOREACH(m->items, l, mi)
     _e_menu_item_unrealize(mi);
   E_FREE_FUNC(m->header.icon, evas_object_del);
   E_FREE_FUNC(m->bg_object, evas_object_del);
   E_FREE_FUNC(m->container_object, evas_object_del);
   m->cur.visible = 0;
   m->prev.visible = 0;
   m->realized = 0;
   m->zone = NULL;
   //evas_event_thaw(m->evas);
   m->evas = NULL;
}

static void
_e_menu_activate_internal(E_Menu *m, E_Zone *zone)
{
   E_Menu_Category *cat = NULL;
   Eina_List *l;

   if (m->pre_activate_cb.func)
     m->pre_activate_cb.func(m->pre_activate_cb.data, m);
   m->fast_mouse = 0;
   m->pending_new_submenu = 0;
   if (_e_menu_win != e_comp->ee_win)
     {
        _e_menu_win = e_comp->ee_win;
        if (!e_comp_grab_input(1, 1))
          {
             _e_menu_win = UINT_MAX;
             return;
          }
        _e_menu_grabbed = EINA_TRUE;
        e_bindings_disabled_set(1);
     }
   m->zone = zone;
   if (!m->active)
     {
        /* this remove is in case the menu is marked as inactive but hasn't */
        /* been removed from the list yet */
        if (m->in_active_list)
          _e_active_menus = eina_list_remove(_e_active_menus, m);
        _e_active_menus = eina_list_append(_e_active_menus, m);
        if (!m->in_active_list)
          e_object_ref(E_OBJECT(m));
        m->in_active_list = 1;
        m->active = 1;
     }
   /* the foreign menu items */
   if (m->category)
     {
        cat = eina_hash_find(_e_menu_categories, m->category);
        if (cat)
          {
             E_Menu_Category_Callback *cb;
             EINA_LIST_FOREACH(cat->callbacks, l, cb)
               if (cb->create) cb->create(cb->data, m, cat->data);
          }
     }
   m->cur.visible = 1;
}

static void
_e_menu_deactivate_all(void)
{
   Eina_List *tmp;
   E_Menu *m;

   tmp = _e_active_menus_copy_ref();

   EINA_LIST_FREE(tmp, m)
     {
        e_menu_deactivate(m);
        m->parent_item = NULL;
        e_object_unref(E_OBJECT(m));
     }
   _e_menu_activate_floating = 0;
   _e_menu_activate_maybe_drag = 0;
   _e_menu_activate_dragging = 0;
}

static void
_e_menu_deactivate_above(E_Menu *ma)
{
   Eina_List *tmp;
   int above = 0;
   E_Menu *m;

   tmp = _e_active_menus_copy_ref();

   EINA_LIST_FREE(tmp, m)
     {
        if (above)
          {
             e_menu_deactivate(m);
             m->parent_item = NULL;
          }
        if (ma == m) above = 1;
        e_object_unref(E_OBJECT(m));
     }
}

static void
_e_menu_submenu_activate(E_Menu_Item *mi)
{
   if (!mi->menu->active) return;
   if (mi->submenu && mi->submenu->active) return;
   if (mi->menu->fast_mouse)
     {
        mi->menu->pending_new_submenu = 1;
        return;
     }
   mi->menu->pending_new_submenu = 0;
   _e_menu_deactivate_above(mi->menu);
   if (mi->submenu_pre_cb.func)
     mi->submenu_pre_cb.func(mi->submenu_pre_cb.data, mi->menu, mi);
   if (mi->submenu)
     {
        E_Menu *m;

        m = mi->submenu;
        e_object_ref(E_OBJECT(m));
        m->parent_item = mi;
        _e_menu_activate_internal(m, mi->menu->zone);
        _e_menu_reposition(m);
        e_object_unref(E_OBJECT(m));
        mi->menu->have_submenu = 1;
     }
}

static void
_e_menu_submenu_deactivate(E_Menu_Item *mi)
{
   if (!mi->menu->active) return;
   mi->menu->have_submenu = 0;
   if (mi->submenu_post_cb.func)
     mi->submenu_post_cb.func(mi->submenu_post_cb.data, mi->menu, mi);
}

static void
_e_menu_reposition(E_Menu *m)
{
   Eina_List *l, *tmp;
   E_Menu_Item *mi;
   int parent_item_bottom;

   if (!m->zone) return;
   if (!m->parent_item) return;
   m->cur.x = m->parent_item->menu->cur.x + m->parent_item->menu->cur.w;

   parent_item_bottom = m->parent_item->y;
   if (m->cur.h > m->zone->h)
     {
#if 0 // we can't win - we just flip back and forth, so let it go off and use scrolling
        /* menu is larger than screen */
        if (parent_item_bottom > (m->zone->y + (m->zone->h / 2)))
          /* more is shown if menu goes up */
          m->cur.y = (parent_item_bottom - (m->zone->h + 1));
        else
          /* more is shown if menu goes down */
          m->cur.y = parent_item_bottom - m->zone->y;
#endif
     }
   else
     {
        /* menu is on top or bottom half of screen */
        if (parent_item_bottom > (m->zone->y + (m->zone->h / 2)))
          m->cur.y = parent_item_bottom - m->cur.h + m->parent_item->h;
        else
          m->cur.y = parent_item_bottom;
     }

   /* FIXME: this will suck for big menus */
   tmp = _e_active_menus_copy_ref();

   EINA_LIST_FOREACH(m->items, l, mi)
     if ((mi->active) && (mi->submenu)) _e_menu_reposition(mi->submenu);

   _e_menu_list_free_unref(tmp);
}

static int
_e_menu_active_call(void)
{
   E_Menu_Item *mi;

   mi = _e_menu_item_active_get();
   if (mi)
     {
        if (mi->check)
          e_menu_item_toggle_set(mi, !mi->toggle);
        if ((mi->radio) && (!e_menu_item_toggle_get(mi)))
          e_menu_item_toggle_set(mi, 1);
        if (mi->cb.func)
          mi->cb.func(mi->cb.data, mi->menu, mi);
        return 1;
     }
   return -1;
}

static int
_e_menu_realize_call(E_Menu_Item *mi)
{
   if (mi)
     {
        if (mi->realize_cb.func)
          {
             mi->realize_cb.func(mi->realize_cb.data, mi->menu, mi);
             return 1;
          }
     }
   return 0;
}

static void
_e_menu_item_activate_next(void)
{
   E_Menu_Item *mi;
   Eina_List *ll;

   ll = _e_menu_list_item_active_get();
   mi = _e_menu_item_active_get();
   if (ll && mi)
     {
        /* Look at the next item and then cycle until we're not on
         * a separator. */
        do
          {
             if (!eina_list_next(ll))
               ll = mi->menu->items;
             else
               ll = eina_list_next(ll);
             mi = eina_list_data_get(ll);
          }
        while (mi->separator || mi->disable);

        e_menu_item_active_set(mi, 1);
        _e_menu_item_ensure_onscreen(mi);
        return;
     }

   _e_menu_activate_first();
}

static void
_e_menu_item_activate_previous(void)
{
   E_Menu_Item *mi;
   Eina_List *ll;

   ll = _e_menu_list_item_active_get();
   mi = _e_menu_item_active_get();
   if (ll && mi)
     {
        /* Look at the prev item and then cycle until we're not on
         * a separator. */
        do
          {
             if (!eina_list_prev(ll))
               ll = eina_list_last(ll);
             else
               ll = eina_list_prev(ll);
             mi = eina_list_data_get(ll);
          }
        while ((mi->separator) || (mi->disable));

        e_menu_item_active_set(mi, 1);
        _e_menu_item_ensure_onscreen(mi);
        return;
     }

   _e_menu_activate_first();
}

static void
_e_menu_item_activate_first(void)
{
   E_Menu *m;
   Eina_List *ll;
   E_Menu_Item *mi;

   m = _e_menu_active_get();
   if (m)
     {
        ll = m->items;
        mi = eina_list_data_get(ll);
        while ((mi->separator) && eina_list_next(ll))
          {
             ll = eina_list_next(ll);
             mi = eina_list_data_get(ll);
          }
        if (mi->separator) return;
        e_menu_item_active_set(mi, 1);
        _e_menu_item_ensure_onscreen(mi);
        return;
     }
   _e_menu_activate_first();
}

static void
_e_menu_item_activate_last(void)
{
   E_Menu *m;
   Eina_List *ll;
   E_Menu_Item *mi;

   m = _e_menu_active_get();
   if (m)
     {
        ll = eina_list_last(m->items);
        mi = eina_list_data_get(ll);
        while ((mi->separator) && eina_list_prev(ll))
          {
             ll = eina_list_prev(ll);
             mi = eina_list_data_get(ll);
          }
        if (mi->separator) return;
        e_menu_item_active_set(mi, 1);
        _e_menu_item_ensure_onscreen(mi);
        return;
     }
   _e_menu_activate_first();
}

static void
_e_menu_item_activate_nth(int n)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *ll;
   int i = -1;

   mi = _e_menu_item_active_get();
   if (!mi)
     {
        _e_menu_activate_first();
        mi = _e_menu_item_active_get();
        if (!mi) return;
     }
   m = mi->menu;
   EINA_LIST_FOREACH(m->items, ll, mi)
     {
        if (!mi->separator) i++;
        if (i == n) break;
     }
   if (!mi) return;
   e_menu_item_active_set(mi, 1);
   _e_menu_item_ensure_onscreen(mi);
}

static void
_e_menu_item_activate_char(const char *key_compose)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *ll, *ll_orig;

   /* Ignore modifiers and such. */
   if (!key_compose) return;

   /* Check we've got a menu and it's active. */
   m = _e_menu_active_get();
   if (!m)
     {
        if (!_e_active_menus) return;
        m = eina_list_data_get(_e_active_menus);
        if (!m) return;
     }

   ll = _e_menu_list_item_active_get();
   /* If we don't have an active item, start from the top of the list. */
   if (!ll)
     {
        ll = m->items;
        mi = eina_list_data_get(ll);
        /* Only check the current item if it wasn't active before. */
        if (!mi->separator && mi->label && !strncasecmp(key_compose, mi->label, strlen(key_compose)))
          {
             e_menu_item_active_set(mi, 1);
             _e_menu_item_ensure_onscreen(mi);
             return;
          }
     }

   ll_orig = ll;

   mi = eina_list_data_get(ll);
   if (!eina_list_next(ll))
     ll = mi->menu->items;
   else
     ll = eina_list_next(ll);
   mi = eina_list_data_get(ll);

   /* While we don't have a label OR we don't match  AND we haven't
    * wrapped around */
   while ((!mi->label || strncasecmp(key_compose, mi->label, strlen(key_compose)))
          && ll != ll_orig)
     {
        do
          {
             if (!eina_list_next(ll))
               ll = mi->menu->items;
             else
               ll = eina_list_next(ll);
             mi = eina_list_data_get(ll);
          }
        while (mi->separator);
     }

   e_menu_item_active_set(mi, 1);
   _e_menu_item_ensure_onscreen(mi);
   return;
}

static void
_e_menu_activate_next(void)
{
   E_Menu_Item *mi;

   mi = _e_menu_item_active_get();
   if (mi)
     {
        if (mi->submenu)
          {
             if (mi->submenu->items)
               {
                  Eina_List *l;
                  EINA_LIST_FOREACH(mi->submenu->items, l, mi)
                    if (!mi->disable)
                      {
                         e_menu_item_active_set(mi, 1);
                         _e_menu_item_ensure_onscreen(mi);
                         break;
                      }
               }
          }
        return;
     }
   _e_menu_activate_first();
}

static void
_e_menu_activate_previous(void)
{
   E_Menu_Item *mi;

   mi = _e_menu_item_active_get();
   if (mi)
     {
        if (mi->menu->parent_item)
          {
             Eina_List *l;
             E_Menu_Item *mi2;

             EINA_LIST_FOREACH(mi->menu->items, l, mi2)
               {
                  if (!mi2->disable)
                    {
                       e_menu_item_active_set(mi2, 0);
                       _e_menu_item_unhilight(mi2);
                    }
               }
             mi = mi->menu->parent_item;
             e_menu_item_active_set(mi, 1);
             _e_menu_item_ensure_onscreen(mi);
          }
        return;
     }
   _e_menu_activate_last();
}

static void
_e_menu_activate_first(void)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *ll;

   if (!_e_active_menus) return;
   m = eina_list_data_get(_e_active_menus);
   if (!m->items) return;
   ll = m->items;
   mi = eina_list_data_get(ll);
   while ((mi->separator) && eina_list_next(ll))
     {
        ll = eina_list_next(ll);
        mi = eina_list_data_get(ll);
     }
   if (mi->separator) return;
   e_menu_item_active_set(mi, 1);
   _e_menu_item_ensure_onscreen(mi);
}

static void
_e_menu_activate_last(void)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *ll;

   if (!_e_active_menus) return;
   m = eina_list_data_get(_e_active_menus);
   if (!m->items) return;
   ll = eina_list_last(m->items);
   mi = eina_list_data_get(ll);
   while ((mi->separator) && eina_list_prev(ll))
     {
        ll = eina_list_prev(ll);
        mi = eina_list_data_get(ll);
     }
   if (mi->separator) return;
   e_menu_item_active_set(mi, 1);
   _e_menu_item_ensure_onscreen(mi);
}

#if 0
static void
_e_menu_activate_nth(int n)
{
   E_Menu *m;
   E_Menu_Item *mi;
   Eina_List *ll;
   int i = -1;

   mi = _e_menu_item_active_get();
   if (!mi)
     {
        _e_menu_activate_first();
        mi = _e_menu_item_active_get();
        if (!mi) return;
     }
   m = mi->menu;
   EINA_LIST_FOREACH(m->items, ll, mi)
     {
        if (!mi->separator) i++;
        if (i == n)
          {
             e_menu_item_active_set(mi, 1);
             _e_menu_item_ensure_onscreen(mi);
             return;
          }
     }
}

#endif

static E_Menu *
_e_menu_active_get(void)
{
   if (_e_active_menu_item) return _e_active_menu_item->menu;
   return NULL;
}

static E_Menu_Item *
_e_menu_item_active_get(void)
{
   return _e_active_menu_item;
}

static Eina_List *
_e_menu_list_item_active_get(void)
{
   if (_e_active_menu_item)
     return _e_active_menu_item->list_position;
   else
     return NULL;
}

static int
_e_menu_outside_bounds_get(int xdir, int ydir)
{
   Eina_List *l;
   E_Menu *m;
   int outl = 0;
   int outr = 0;
   int outt = 0;
   int outb = 0;
   int i = 0;

   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if (!m->zone) continue;
        if (m->cur.x < m->zone->x + e_config->menu_autoscroll_margin)
          {
             i = m->zone->x - m->cur.x + e_config->menu_autoscroll_margin;
             if (i > outl) outl = i;
          }
        if (m->cur.y < m->zone->y + e_config->menu_autoscroll_margin)
          {
             i = m->zone->y - m->cur.y + e_config->menu_autoscroll_margin;
             if (i > outt) outt = i;
          }
        if ((m->cur.x + m->cur.w) > (m->zone->w - e_config->menu_autoscroll_margin))
          {
             i = m->cur.x + m->cur.w - (m->zone->x + m->zone->w - e_config->menu_autoscroll_margin);
             if (i > outr) outr = i;
          }
        if ((m->cur.y + m->cur.h) > (m->zone->h - e_config->menu_autoscroll_margin))
          {
             i = m->cur.y + m->cur.h - (m->zone->y + m->zone->h - e_config->menu_autoscroll_margin);
             if (i > outb) outb = i;
          }
     }
   if (xdir == -1)
     {
        if (outl) return outl;
     }
   else if (xdir == 1)
     {
        if (outr) return outr;
     }
   else if (ydir == -1)
     {
        if (outt) return outt;
     }
   else if (ydir == 1)
     {
        if (outb) return outb;
     }
   return 0;
}

static void
_e_menu_scroll_by(int dx, int dy)
{
   Eina_List *l;
   E_Menu *m;

   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        m->cur.x += dx;
        m->cur.y += dy;
     }
}

static void
_e_menu_mouse_autoscroll_check(void)
{
   int autoscroll_x = 0;
   int autoscroll_y = 0;
   E_Menu *m = NULL;
   int mx, my;

   mx = _e_menu_x;
   my = _e_menu_y;
   if (_e_active_menus)
     {
        m = eina_list_data_get(_e_active_menus);
        if ((m) && (m->zone))
          {
             mx -= m->zone->x;
             my -= m->zone->y;
          }
     }
   if (mx - e_config->menu_autoscroll_cursor_margin <= 0)
     {
        if (_e_menu_outside_bounds_get(-1, 0)) autoscroll_x = -1;
     }
   if (my - e_config->menu_autoscroll_cursor_margin <= 0)
     {
        if (_e_menu_outside_bounds_get(0, -1)) autoscroll_y = -1;
     }
   if ((!autoscroll_x) && (!autoscroll_y))
     {
        if ((m) && (m->zone))
          {
             if (mx + e_config->menu_autoscroll_cursor_margin >= (m->zone->w - 1))
               {
                  if (_e_menu_outside_bounds_get(1, 0)) autoscroll_x = 1;
               }
             if (my + e_config->menu_autoscroll_cursor_margin >= (m->zone->h - 1))
               {
                  if (_e_menu_outside_bounds_get(0, 1)) autoscroll_y = 1;
               }
          }
     }
   _e_menu_autoscroll_x = autoscroll_x;
   _e_menu_autoscroll_y = autoscroll_y;
   if ((!autoscroll_x) && (!autoscroll_y)) return;
   if (_e_menu_scroll_animator) return;
   _e_menu_scroll_animator = ecore_animator_add(_e_menu_cb_scroll_animator,
                                                NULL);
   _e_menu_scroll_start = ecore_loop_time_get();
}

static void
_e_menu_item_ensure_onscreen(E_Menu_Item *mi)
{
   int x = 0, y = 0, w = 0, h = 0;
   int dx = 0, dy = 0;

   if (!mi->menu) return;
   if (!mi->menu->zone) return;
   evas_object_geometry_get(mi->container_object, &x, &y, &w, &h);
   if ((x + w) > (mi->menu->zone->x + mi->menu->zone->w))
     dx = (mi->menu->zone->x + mi->menu->zone->w) - (x + w);
   else if (x < mi->menu->zone->x)
     dx = mi->menu->zone->x - x;
   if ((y + h) > (mi->menu->zone->y + mi->menu->zone->h))
     dy = (mi->menu->zone->y + mi->menu->zone->h) - (y + h);
   else if (y < mi->menu->zone->y)
     dy = mi->menu->zone->y - y;
   if ((dx != 0) || (dy != 0))
     _e_menu_scroll_by(dx, dy);
}

static void
_e_menu_auto_place_vert(E_Menu *m, int x, int y, int w, int h)
{
   int zx, zy, zw, zh;

   if (!m->zone) return;
   e_zone_useful_geometry_get(m->zone, &zx, &zy, &zw, &zh);
   if (E_CONTAINS(zx, zy, zw, zh, m->cur.x, y, m->cur.w, m->cur.h))
     {
        if (w + h > 2)
          {
             if (E_INTERSECTS(m->cur.x, y, m->cur.w, m->cur.h, x, y, w, h))
               m->cur.y = y + h - m->cur.h;
             else
               m->cur.y = y;
          }
        else
          m->cur.y = y;
     }
   else
     m->cur.y = y + h - m->cur.h;
}

static int
_e_menu_auto_place(E_Menu *m, int x, int y, int w, int h)
{
   double xr, yr;

   _e_menu_realize(m);
   /* +-----+
    * |\ T /|
    * | \ / |
    * |L X R|
    * | / \ |
    * |/ B \|
    * +-----+
    *
    * quadrants... which one
    */
   if (!m->zone) return 0;
   if (w != m->zone->w)
     xr = (double)(x - m->zone->x) /
       (double)(m->zone->w - w);
   else
     xr = 0.0;
   if (h != m->zone->h)
     yr = (double)(y - m->zone->y) /
       (double)(m->zone->h - h);
   else
     yr = 0.0;

   if ((xr + yr) < 0.99) /* top or left */
     {
        if (((1.0 - yr) + xr) <= 1.0)
          {
             /* L */
             m->cur.x = x + w;
             _e_menu_auto_place_vert(m, x, y, w, h);
             return 1;
          }
        else
          {
             /* T */
             m->cur.y = y + h;
             if (x < (m->zone->x + ((m->zone->w * 1) / 3)))
               m->cur.x = x;
             else if (x < (m->zone->x + ((m->zone->w * 2) / 3)))
               m->cur.x = x + ((w - m->cur.w) / 2);
             else
               m->cur.x = x + w - m->cur.w;
             return 3;
          }
     }
   else /* bottom or right */
     {
        if (((1.0 - yr) + xr) <= 1.01)
          {
             /* B */
             m->cur.y = y - m->cur.h;
             if (x < (m->zone->x + ((m->zone->w * 1) / 3)))
               m->cur.x = x;
             else if (x < (m->zone->x + ((m->zone->w * 2) / 3)))
               m->cur.x = x + ((w - m->cur.w) / 2);
             else
               m->cur.x = x + w - m->cur.w;
             return 4;
          }
        else
          {
             /* R */
             m->cur.x = x - m->cur.w;
             _e_menu_auto_place_vert(m, x, y, w, h);
             return 2;
          }
     }
   return 0;
}

static void
_e_menu_cb_item_in(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Menu_Item *mi = data;

   if (_e_menu_lock) return;
   // ignore in/out not due to deliberate mouse move by user
   if (_e_menu_nav_by != NAV_BY_MOUSE) return;
   /* this can be triggered when creating menus if the new menu is on top of its parent */
   if (!mi->menu->realized) return;
   e_menu_item_active_set(mi, 1);
}

static void
_e_menu_cb_item_out(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Menu_Item *mi = data;
   // ignore in/out not due to deliberate mouse move by user
   if (_e_menu_nav_by != NAV_BY_MOUSE) return;
   /* this can be triggered when creating menus if the new menu is on top of its parent */
   if (!mi->menu->realized) return;
   e_menu_item_active_set(mi, 0);
}

static Eina_Bool
_e_menu_cb_key_down(void *data EINA_UNUSED, Ecore_Event_Key *ev)
{
   _e_menu_nav_by = NAV_BY_KEY;
   if ((!strcmp(ev->key, "Up")) || (!strcmp(ev->key, "KP_Up")))
     _e_menu_item_activate_previous();
   else if ((!strcmp(ev->key, "Down")) || (!strcmp(ev->key, "KP_Down")))
     _e_menu_item_activate_next();
   else if ((!strcmp(ev->key, "Left")) || (!strcmp(ev->key, "KP_Left")))
     _e_menu_activate_previous();
   else if ((!strcmp(ev->key, "Right")) || (!strcmp(ev->key, "KP_Right")))
     _e_menu_activate_next();
   else if ((!strcmp(ev->key, "Home")) || (!strcmp(ev->key, "KP_Home")))
     _e_menu_item_activate_first();
   else if ((!strcmp(ev->key, "End")) || (!strcmp(ev->key, "KP_End")))
     _e_menu_item_activate_last();
   else if (!strcmp(ev->key, "space"))
     _e_menu_active_call();
   else if ((!strcmp(ev->key, "Return")) || (!strcmp(ev->key, "KP_Enter")))
     {
        _e_menu_active_call();
        _e_menu_deactivate_all();
     }
   else if (!strcmp(ev->key, "Escape"))
     _e_menu_deactivate_all();
   else if ((!strcmp(ev->key, "1")) || (!strcmp(ev->key, "KP_1")))
     _e_menu_item_activate_first();
   else if ((!strcmp(ev->key, "2")) || (!strcmp(ev->key, "KP_2")))
     _e_menu_item_activate_nth(1);
   else if ((!strcmp(ev->key, "3")) || (!strcmp(ev->key, "KP_3")))
     _e_menu_item_activate_nth(2);
   else if ((!strcmp(ev->key, "4")) || (!strcmp(ev->key, "KP_4")))
     _e_menu_item_activate_nth(3);
   else if ((!strcmp(ev->key, "5")) || (!strcmp(ev->key, "KP_5")))
     _e_menu_item_activate_nth(4);
   else if ((!strcmp(ev->key, "6")) || (!strcmp(ev->key, "KP_6")))
     _e_menu_item_activate_nth(5);
   else if ((!strcmp(ev->key, "7")) || (!strcmp(ev->key, "KP_7")))
     _e_menu_item_activate_nth(6);
   else if ((!strcmp(ev->key, "8")) || (!strcmp(ev->key, "KP_8")))
     _e_menu_item_activate_nth(7);
   else if ((!strcmp(ev->key, "9")) || (!strcmp(ev->key, "KP_9")))
     _e_menu_item_activate_nth(8);
   else if ((!strcmp(ev->key, "0")) || (!strcmp(ev->key, "KP_0")))
     _e_menu_item_activate_last();
   else if (ev->compose)
     _e_menu_item_activate_char(ev->compose);
   return ECORE_CALLBACK_PASS_ON;
}

/* we need all of these because menus are special and grab the mouse and
 * keyboard and thus the normal event mechanism doesn't work, so we feed
 * events directly to the canvases from our grab window
 */

static void
_e_menu_cb_mouse_evas_down(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Menu *m = data;

   while (m->parent_item)
     m = m->parent_item->menu;
   e_menu_deactivate(m);
}

static Eina_Bool
_e_menu_cb_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Button *ev;

   ev = event;
   _e_menu_nav_by = NAV_BY_MOUSE;
   if (ev->window != _e_menu_win)
     {
        if (_e_menu_active_get())
          _e_menu_deactivate_all();
        return ECORE_CALLBACK_PASS_ON;
     }

   _e_menu_lock = 1;
   e_comp_canvas_feed_mouse_up(0);
   _e_menu_lock = 0;

   /* Only allow dragging from floating menus for now.
    * The reason for this is that for non floating menus,
    * the mouse is already down and dragging, so the decision
    * to start a drag is much more complex.
    */
   if (_e_menu_activate_floating)
     _e_menu_activate_maybe_drag = 1;

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_menu_cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Button *ev;
   E_Menu *m;
   Eina_List *l;
   unsigned int t;
   int ret = 0;

   if (_e_menu_lock) return ECORE_CALLBACK_RENEW;
   ev = event;
   if (ev->window != _e_menu_win) return ECORE_CALLBACK_RENEW;

   _e_menu_nav_by = NAV_BY_MOUSE;
   if (!_e_menu_activate_floating)
     {
        EINA_LIST_FOREACH(_e_active_menus, l, m)
          {
             if (!m->hold_mode)
               {
                  _e_menu_activate_floating = 1;
                  return ECORE_CALLBACK_PASS_ON;
               }
          }
     }

   t = ev->timestamp - _e_menu_activate_time;
   if ((_e_menu_activate_time != 0) &&
       (t < (e_config->menus_click_drag_timeout * 1000)))
     {
        _e_menu_activate_floating = 1;
        return ECORE_CALLBACK_PASS_ON;
     }

   if (_e_menu_activate_dragging)
     {
        /* FIXME: This is a drop, which is not allowed for now.
         * Once dragging is working, this will be subject to some experimentation.
         */
     }
   else
     {
        E_Menu_Item *mi;

        mi = _e_menu_item_active_get();
        if ((!mi) ||
            (E_INSIDE(e_comp_canvas_x_root_adjust(ev->root.x),
                      e_comp_canvas_y_root_adjust(ev->root.y),
                      mi->x, mi->y, mi->w, mi->h))
           )
          ret = _e_menu_active_call();
     }
   _e_menu_activate_maybe_drag = 0;
   _e_menu_activate_dragging = 0;
   if (ret == 1)
     {
/* allow mouse to pop down menu if clicked elsewhere */
/*	if (_e_menu_activate_time != 0) */
        _e_menu_deactivate_all();
     }
   else if (ret == -1)
     _e_menu_deactivate_all();
   else if (!_e_menu_activate_floating)
     _e_menu_deactivate_all();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_menu_cb_mouse_move(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Move *ev;
   Eina_List *l, *tmp;
   E_Menu *m;
   int dx, dy, d;
   double dt;
   double fast_move_threshold;
   int is_fast = 0;

   ev = event;
   if (ev->window != _e_menu_win) return ECORE_CALLBACK_PASS_ON;
   _e_menu_nav_by = NAV_BY_MOUSE;
   fast_move_threshold = e_config->menus_fast_mouse_move_threshhold;
   dx = ev->x - _e_menu_x;
   dy = ev->y - _e_menu_y;
   d = (dx * dx) + (dy * dy);
   dt = (double)(ev->timestamp - _e_menu_time) / 1000.0;
   dt = dt * dt;
   if ((dt > 0.0) && ((d / dt) >= (fast_move_threshold * fast_move_threshold)))
     is_fast = 1;

   tmp = _e_active_menus_copy_ref();

   EINA_LIST_FOREACH(_e_active_menus, l, m)
     {
        if ((!m->realized) || (!m->cur.visible)) continue;
        if (is_fast)
          m->fast_mouse = 1;
        else if (dt > 0.0)
          {
             m->fast_mouse = 0;
             if (m->pending_new_submenu)
               {
                  E_Menu_Item *mi;

                  mi = _e_menu_item_active_get();
                  if (mi)
                    _e_menu_submenu_activate(mi);
               }
          }
       if (!_e_menu_activate_maybe_drag)
         /* this is useless while the mouse is down */
         evas_event_feed_mouse_move(m->evas, ev->x, ev->y, ev->timestamp, NULL);
     }
   if (_e_menu_activate_maybe_drag)
     {
        if (_e_active_menu_item)
          {
             if (!E_INSIDE(ev->x, ev->y, _e_active_menu_item->x, _e_active_menu_item->y, _e_active_menu_item->w, _e_active_menu_item->h))
               {
                  if (_e_active_menu_item->drag_cb.func)
                    {
                       /* User is dragging a draggable item elsewhere. */
                       _e_active_menu_item->drag.x = ev->x - (ev->x - _e_active_menu_item->x);
                       _e_active_menu_item->drag.y = ev->y - (ev->y - _e_active_menu_item->y);
                       _e_menu_deactivate_all();
                       _e_active_menu_item->drag_cb.func(_e_active_menu_item->drag_cb.data, _e_active_menu_item->menu, _e_active_menu_item);
                    }
                  /* Either way, the maybe drag stops here. */
                  _e_menu_activate_maybe_drag = 0;
               }
          }
     }

   _e_menu_list_free_unref(tmp);

   _e_menu_x = ev->x;
   _e_menu_y = ev->y;
   _e_menu_time = ev->timestamp;
   _e_menu_mouse_autoscroll_check();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_menu_cb_mouse_wheel(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Event_Mouse_Wheel *ev;

   ev = event;
   if (ev->window != _e_menu_win) return ECORE_CALLBACK_PASS_ON;
   _e_menu_nav_by = NAV_BY_WHEEL;
   if (ev->z < 0) /* up */
     {
        int i;

        for (i = ev->z; i < 0; i++)
          _e_menu_item_activate_previous();
     }
   else if (ev->z > 0) /* down */
     {
        int i;

        for (i = ev->z; i > 0; i--)
          _e_menu_item_activate_next();
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_menu_cb_scroll_animator(void *data EINA_UNUSED)
{
   double t, dt;
   double dx, dy;
   int out;
   double spd;

   t = ecore_loop_time_get();
   spd = e_config->menus_scroll_speed;
   dt = t - _e_menu_scroll_start;
   _e_menu_scroll_start = t;
   dx = 0;
   dy = 0;
   if (_e_menu_autoscroll_x)
     {
        out = _e_menu_outside_bounds_get(_e_menu_autoscroll_x, 0);
        dx = (-_e_menu_autoscroll_x) * spd * dt;
        if (_e_menu_autoscroll_x == -1)
          {
             if (dx > out) dx = out;
          }
        else
          {
             if (dx < -out) dx = -out;
          }
     }
   if (_e_menu_autoscroll_y)
     {
        out = _e_menu_outside_bounds_get(0, _e_menu_autoscroll_y);
        dy = (-_e_menu_autoscroll_y) * spd * dt;
        if (_e_menu_autoscroll_y == -1)
          {
             if (dy > out) dy = out;
          }
        else
          {
             if (dy < -out) dy = -out;
          }
     }
   _e_menu_scroll_by(dx, dy);
   _e_menu_mouse_autoscroll_check();
   if ((_e_menu_autoscroll_x == 0) && (_e_menu_autoscroll_y == 0))
     {
        _e_menu_scroll_animator = NULL;
        return 0;
     }
   return 1;
}

static void
_e_menu_cb_item_submenu_post_default(void *data EINA_UNUSED, E_Menu *m EINA_UNUSED, E_Menu_Item *mi)
{
   if (!mi->submenu) return;
   e_menu_item_submenu_set(mi, NULL); // unis unrefs previous submenu
}

static void
_e_menu_category_free_cb(E_Menu_Category *cat)
{
   E_FREE_LIST(cat->callbacks, free);
   free(cat);
}

#include "e.h"

/* TODO List:
 *
 * * add module types/classes
 * * add list of exclusions that a module can't work with Api
 *
 */

/* local subsystem functions */
static void      _e_module_free(E_Module *m);
static void      _e_module_dialog_disable_create(const char *title, const char *body, E_Module *m);
static void      _e_module_cb_dialog_disable(void *data, E_Dialog *dia);
static void      _e_module_event_update_free(void *data, void *event);
static int       _e_module_sort_name(const void *d1, const void *d2);
static void      _e_module_whitelist_check(void);

/* local subsystem globals */
static Eina_List *_e_modules = NULL;
static Eina_Hash *_e_modules_hash = NULL;
static Eina_Bool _e_modules_initting = EINA_FALSE;
static Eina_Bool _e_modules_init_end = EINA_FALSE;

static Eina_Hash *_e_module_path_hash = NULL;

E_API int E_EVENT_MODULE_UPDATE = 0;
E_API int E_EVENT_MODULE_INIT_END = 0;

static Eina_Bool
_module_is_nosave(const char *name)
{
   const char *blacklist[] =
   {
      "comp",
      "conf_comp",
      "xwayland",
   };
   unsigned int i;

   for (i = 0; i < EINA_C_ARRAY_LENGTH(blacklist); i++)
     if (eina_streq(name, blacklist[i])) return EINA_TRUE;
   return !strncmp(name, "wl_", 3); //block wl_* modules from being saved
}

static Eina_Bool
_module_is_important(const char *name)
{
   const char *list[] =
   {
      "xwayland",
      "wl_buffer",
      "wl_desktop_shell",
      "wl_drm",
      "wl_fb",
      "wl_text_input",
      "wl_weekeyboard",
      "wl_wl",
      "wl_x11",
   };
   unsigned int i;

   for (i = 0; i < EINA_C_ARRAY_LENGTH(list); i++)
     if (eina_streq(name, list[i])) return EINA_TRUE;
   return EINA_FALSE;
}

/* externally accessible functions */
EINTERN int
e_module_init(void)
{
   if (_e_modules_hash) return 1;
   E_EVENT_MODULE_UPDATE = ecore_event_type_new();
   E_EVENT_MODULE_INIT_END = ecore_event_type_new();
   _e_modules_hash = eina_hash_string_superfast_new(NULL);

   return 1;
}

EINTERN int
e_module_shutdown(void)
{
   E_Module *m;

#ifdef HAVE_VALGRIND
   /* do a leak check now before we dlclose() all those plugins, cause
    * that means we won't get a decent backtrace to leaks in there
    */
   VALGRIND_DO_LEAK_CHECK
#endif

   /* do not use EINA_LIST_FREE! e_object_del modifies list */
   if (x_fatal)
     e_module_save_all();
   else
     {
        while (_e_modules)
          {
             m = _e_modules->data;
             if ((m) && (m->enabled) && !(m->error))
               {
                  if (m->func.save) m->func.save(m);
                  if (m->func.shutdown) m->func.shutdown(m);
                  m->enabled = 0;
               }
             e_object_del(E_OBJECT(m));
          }
     }

   E_FREE_FUNC(_e_module_path_hash, eina_hash_free);
   E_FREE_FUNC(_e_modules_hash, eina_hash_free);

   return 1;
}

E_API void
e_module_all_load(void)
{
   Eina_List *l, *ll;
   E_Config_Module *em, *em2;
   char buf[128];

   _e_modules_initting = EINA_TRUE;

   // remove duplicate modules in load
   e_config->modules =
     eina_list_sort(e_config->modules, 0, _e_module_sort_name);
   EINA_LIST_FOREACH_SAFE(e_config->modules, l, ll, em)
     {
        if ((!em) || (!ll)) continue;
        em2 = ll->data;
        if (!em2) continue;

        if (!strcmp(em->name, em2->name))
          {
             eina_stringshare_del(em->name);
             e_config->modules = eina_list_remove_list(e_config->modules, l);
             free(em);
          }
     }

   EINA_LIST_FOREACH_SAFE(e_config->modules, l, ll, em)
     {
        if ((!em) || (!em->name)) continue;

        if (_module_is_nosave(em->name))
          {
             eina_stringshare_del(em->name);
             e_config->modules = eina_list_remove_list(e_config->modules, l);
             free(em);
             continue;
          }
        if (em->enabled)
          {
             E_Module *m;

             if (eina_hash_find(_e_modules_hash, em->name)) continue;

             e_util_env_set("E_MODULE_LOAD", em->name);
             snprintf(buf, sizeof(buf), _("Loading Module: %s"), em->name);

             m = e_module_new(em->name);
             if (m) e_module_enable(m);
          }
     }
   ecore_event_add(E_EVENT_MODULE_INIT_END, NULL, NULL, NULL);
   _e_modules_init_end = EINA_TRUE;
   _e_modules_initting = EINA_FALSE;
   _e_module_whitelist_check();

   unsetenv("E_MODULE_LOAD");
}

E_API Eina_Bool
e_module_loading_get(void)
{
   return !_e_modules_init_end;
}

E_API E_Module *
e_module_new(const char *name)
{
   E_Module *m;
   char buf[PATH_MAX];
   char body[PATH_MAX + 256], title[1024];
   const char *modpath = NULL;
   char *s;
   int in_list = 0;

   if (!name) return NULL;
   if (eina_hash_find(_e_modules_hash, name)) return NULL;

   m = E_OBJECT_ALLOC(E_Module, E_MODULE_TYPE, _e_module_free);
   if (name[0] != '/')
     {
        snprintf(buf, sizeof(buf), "%s/%s/module.so", name, MODULE_ARCH);
        modpath = e_path_find(path_modules, buf);
     }
   else if (eina_str_has_extension(name, ".so"))
     modpath = eina_stringshare_add(name);
   if (!modpath)
     {
        snprintf(body, sizeof(body),
                 _("There was an error loading the module named: %s<ps/>"
                   "No module named %s could be found in the<ps/>"
                   "module search directories.<ps/>"), name, buf);
        _e_module_dialog_disable_create(_("Error loading Module"), body, m);
        m->error = 1;
        goto init_done;
     }
   m->handle = dlopen(modpath, (RTLD_NOW | RTLD_LOCAL));
   if (!m->handle)
     {
        snprintf(body, sizeof(body),
                 _("There was an error loading the module named: %s<ps/>"
                   "The full path to this module is:<ps/>"
                   "%s<ps/>"
                   "The error reported was:<ps/>"
                   "%s<ps/>"), name, buf, dlerror());
        _e_module_dialog_disable_create(_("Error loading Module"), body, m);
        m->error = 1;
        goto init_done;
     }
   m->file = eina_stringshare_ref(modpath);
   m->api = dlsym(m->handle, "e_modapi");
   m->func.init = dlsym(m->handle, "e_modapi_init");
   m->func.shutdown = dlsym(m->handle, "e_modapi_shutdown");
   m->func.save = dlsym(m->handle, "e_modapi_save");

   if ((!m->func.init) || (!m->api))
     {
        snprintf(body, sizeof(body),
                 _("There was an error loading the module named: %s<ps/>"
                   "The full path to this module is:<ps/>"
                   "%s<ps/>"
                   "The error reported was:<ps/>"
                   "%s<ps/>"),
                 name, buf, _("Module does not contain all needed functions"));
        _e_module_dialog_disable_create(_("Error loading Module"), body, m);
        m->api = NULL;
        m->func.init = NULL;
        m->func.shutdown = NULL;
        m->func.save = NULL;

        dlclose(m->handle);
        m->handle = NULL;
        m->error = 1;
        goto init_done;
     }
   if (m->api->version != E_MODULE_API_VERSION)
     {
        snprintf(body, sizeof(body),
                 _("Module API Error<ps/>Error initializing Module: %s<ps/>"
                   "It requires a module API version of: %i.<ps/>"
                   "The module API advertized by Enlightenment is: %i.<ps/>"),
                 _(m->api->name), m->api->version, E_MODULE_API_VERSION);

        snprintf(title, sizeof(title), _("Enlightenment %s Module"),
                 _(m->api->name));

        _e_module_dialog_disable_create(title, body, m);
        m->api = NULL;
        m->func.init = NULL;
        m->func.shutdown = NULL;
        m->func.save = NULL;
        dlclose(m->handle);
        m->handle = NULL;
        m->error = 1;
        goto init_done;
     }

init_done:

   _e_modules = eina_list_append(_e_modules, m);
   if (!_e_modules_hash)
     {
        /* wayland module preloading */
        if (!e_module_init())
          CRI("WTFFFFF");
     }
   eina_hash_add(_e_modules_hash, name, m);
   m->name = eina_stringshare_add(name);
   if (modpath)
     {
        s = ecore_file_dir_get(modpath);
        if (s)
          {
             char *s2;

             s2 = ecore_file_dir_get(s);
             free(s);
             if (s2)
               {
                  m->dir = eina_stringshare_add(s2);
                  free(s2);
               }
          }
     }
   if (!in_list)
     {
        E_Config_Module *module;

        module = E_NEW(E_Config_Module, 1);
        module->name = eina_stringshare_add(m->name);
        module->enabled = 0;
        e_config->modules = eina_list_append(e_config->modules, module);
        e_config_save_queue();
     }
   if (modpath) eina_stringshare_del(modpath);
   return m;
}

E_API int
e_module_save(E_Module *m)
{
   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MODULE_TYPE, 0);
   if ((!m->enabled) || (m->error)) return 0;
   return m->func.save ? m->func.save(m) : 1;
}

E_API const char *
e_module_dir_get(E_Module *m)
{
   E_OBJECT_CHECK_RETURN(m, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MODULE_TYPE, 0);
   return m->dir;
}

E_API int
e_module_enable(E_Module *m)
{
   Eina_List *l;
   E_Event_Module_Update *ev;
   E_Config_Module *em;

   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MODULE_TYPE, 0);
   if ((m->enabled) || (m->error)) return 0;
   m->data = m->func.init(m);
   if (m->data)
     {
        m->enabled = 1;
        EINA_LIST_FOREACH(e_config->modules, l, em)
          {
             if (!em) continue;
             if (!e_util_strcmp(em->name, m->name))
               {
                  em->enabled = 1;
                  e_config_save_queue();

                  ev = E_NEW(E_Event_Module_Update, 1);
                  ev->name = eina_stringshare_ref(em->name);
                  ev->enabled = 1;
                  ecore_event_add(E_EVENT_MODULE_UPDATE, ev,
                                  _e_module_event_update_free, NULL);
                  break;
               }
          }
        if (_e_modules_hash && (!_e_modules_initting))
          _e_module_whitelist_check();
        return 1;
     }
   return 0;
}

E_API int
e_module_disable(E_Module *m)
{
   E_Event_Module_Update *ev;
   Eina_List *l;
   E_Config_Module *em;
   int ret;

   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MODULE_TYPE, 0);
   if ((!m->enabled) || (m->error)) return 0;
   if ((!stopping) && _module_is_important(m->name)) return 0;
   ret = m->func.shutdown ? m->func.shutdown(m) : 1;
   m->data = NULL;
   m->enabled = 0;
   EINA_LIST_FOREACH(e_config->modules, l, em)
     {
        if (!em) continue;
        if (!e_util_strcmp(em->name, m->name))
          {
             em->enabled = 0;
             e_config_save_queue();

             ev = E_NEW(E_Event_Module_Update, 1);
             ev->name = eina_stringshare_ref(em->name);
             ev->enabled = 0;
             ecore_event_add(E_EVENT_MODULE_UPDATE, ev,
                             _e_module_event_update_free, NULL);
             break;
          }
     }
   return ret;
}

E_API int
e_module_enabled_get(E_Module *m)
{
   E_OBJECT_CHECK_RETURN(m, 0);
   E_OBJECT_TYPE_CHECK_RETURN(m, E_MODULE_TYPE, 0);
   return m->enabled;
}

E_API int
e_module_save_all(void)
{
   Eina_List *l;
   E_Module *m;
   int ret = 1;

   EINA_LIST_FOREACH(_e_modules, l, m)
     {
        e_object_ref(E_OBJECT(m));
        if ((m->enabled) && (!m->error))
          {
             if (m->func.save && (!m->func.save(m))) ret = 0;
          }
        e_object_unref(E_OBJECT(m));
     }
   return ret;
}

E_API E_Module *
e_module_find(const char *name)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL);
   return eina_hash_find(_e_modules_hash, name);
}

E_API Eina_List *
e_module_list(void)
{
   return _e_modules;
}

E_API void
e_module_dialog_show(E_Module *m, const char *title, const char *body)
{
   E_Dialog *dia;
   char buf[PATH_MAX];
   const char *icon = NULL;

   dia = e_dialog_new(NULL,
                      "E", "_module_dialog");
   if (!dia) return;

   e_dialog_title_set(dia, title);
   if (m)
     {
        Efreet_Desktop *desktop;

        snprintf(buf, sizeof(buf), "%s/module.desktop", e_module_dir_get(m));

        desktop = efreet_desktop_new(buf);
        if ((desktop) && (desktop->icon))
          {
             icon = efreet_icon_path_find(e_config->icon_theme, desktop->icon, 64);
             if (!icon)
               {
                  snprintf(buf, sizeof(buf), "%s/%s.edj",
                           e_module_dir_get(m), desktop->icon);
                  dia->icon_object = e_util_icon_add(buf, evas_object_evas_get(dia->win));
               }
             else
               dia->icon_object = e_util_icon_add(icon, evas_object_evas_get(dia->win));
             evas_object_size_hint_min_set(dia->icon_object, 64, 64);
             edje_object_part_swallow(dia->bg_object, "e.swallow.icon", dia->icon_object);
             evas_object_show(dia->icon_object);
          }
        if (desktop) efreet_desktop_free(desktop);
     }
   else
     e_dialog_icon_set(dia, "preferences-plugin", 64);

   e_dialog_text_set(dia, body);
   e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
   e_dialog_button_focus_num(dia, 0);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   if (!m) return;
   e_win_client_icon_set(dia->win, icon);
}

static Eina_List *
_e_module_desktop_list(Eina_List *modules, const char *dir)
{
   Eina_List *l, *files;
   Efreet_Desktop *desktop;
   char buf[PATH_MAX], *f;
   E_Module_Desktop *md;

   files = ecore_file_ls(dir);
   EINA_LIST_FOREACH(files, l, f)
     {
        snprintf(buf, sizeof(buf), "%s/%s/module.desktop", dir, f);
        desktop = efreet_desktop_new(buf);
        if (desktop)
          {
             md = E_NEW(E_Module_Desktop, 1);
             md->desktop = desktop;
             snprintf(buf, sizeof(buf), "%s/%s", dir, f);
             md->dir = eina_stringshare_add(buf);
             modules = eina_list_append(modules, md);
          }
     }
   return modules;
}

E_API Eina_List *
e_module_desktop_list(void)
{
   Eina_List *modules = NULL, *l;
   E_Path_Dir *epd;

   EINA_LIST_FOREACH(path_modules->default_dir_list, l, epd)
     {
        modules = _e_module_desktop_list(modules, epd->dir);
     }
   EINA_LIST_FOREACH(*(path_modules->user_dir_list), l, epd)
     {
        modules = _e_module_desktop_list(modules, epd->dir);
     }
   return modules;
}

E_API void
e_module_desktop_free(E_Module_Desktop *md)
{
   if (!md) return;
   eina_stringshare_del(md->dir);
   efreet_desktop_free(md->desktop);
   free(md);
}

/* local subsystem functions */

static void
_e_module_free(E_Module *m)
{
   E_Config_Module *em;
   Eina_List *l;

   EINA_LIST_FOREACH(e_config->modules, l, em)
     {
        if (!em) continue;
        if (!e_util_strcmp(em->name, m->name))
          {
             e_config->modules = eina_list_remove(e_config->modules, em);
             if (em->name) eina_stringshare_del(em->name);
             E_FREE(em);
             break;
          }
     }

   if ((m->enabled) && (!m->error))
     {
        if (m->func.save) m->func.save(m);
        if (m->func.shutdown) m->func.shutdown(m);
     }
   if (m->name) eina_stringshare_del(m->name);
   if (m->dir) eina_stringshare_del(m->dir);
//   if (m->handle) dlclose(m->handle); DONT dlclose! causes problems with deferred callbacks for free etc. - when their code goes away!
   _e_modules = eina_list_remove(_e_modules, m);
   free(m);
}

typedef struct Disable_Dialog
{
   char *title;
   char *body;
   E_Module *m;
} Disable_Dialog;

static void
_e_module_dialog_disable_show(const char *title, const char *body, E_Module *m)
{
   E_Dialog *dia;
   char buf[4096];

   dia = e_dialog_new(NULL, "E", "_module_unload_dialog");

   snprintf(buf, sizeof(buf), "%s<ps/>%s", body,
            _("What action should be taken with this module?<ps/>"));

   e_dialog_title_set(dia, title);
   e_dialog_icon_set(dia, "enlightenment", 64);
   e_dialog_text_set(dia, buf);
   e_dialog_button_add(dia, _("Unload"), NULL, _e_module_cb_dialog_disable, m);
   e_dialog_button_add(dia, _("Keep"), NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_win_no_remember_set(dia->win, 1);
   e_dialog_show(dia);
}

static Eina_Bool
_e_module_dialog_disable_timer(Disable_Dialog *dd)
{
   _e_module_dialog_disable_show(dd->title, dd->body, dd->m);
   free(dd->title);
   free(dd->body);
   free(dd);
   return EINA_FALSE;
}

static void
_e_module_dialog_disable_create(const char *title, const char *body, E_Module *m)
{
   Disable_Dialog *dd;

   dd = E_NEW(Disable_Dialog, 1);
   dd->title = strdup(title);
   dd->body = strdup(body);
   dd->m = m;
   ecore_timer_loop_add(1.5, (Ecore_Task_Cb)_e_module_dialog_disable_timer, dd);
   fprintf(stderr, "MODULE ERR: [%s]\n%s\n", title, body);
}

static void
_e_module_cb_dialog_disable(void *data, E_Dialog *dia)
{
   E_Module *m;

   m = data;
   e_module_disable(m);
   e_object_del(E_OBJECT(m));
   e_object_del(E_OBJECT(dia));
   e_config_save_queue();
}

static int
_e_module_sort_name(const void *d1, const void *d2)
{
   const E_Config_Module *m1, *m2;

   m1 = d1;
   if (!m1->name) return -1;
   m2 = d2;
   if (!m2->name) return 1;
   return strcmp(m1->name, m2->name);
}

static void
_e_module_event_update_free(void *data EINA_UNUSED, void *event)
{
   E_Event_Module_Update *ev;

   if (!(ev = event)) return;
   eina_stringshare_del(ev->name);
   E_FREE(ev);
}

static void
_cleanup_cb(void *data, E_Dialog *dialog)
{
   Eina_List *badl = data;
   const char *s;

   e_object_del(E_OBJECT(dialog));
   EINA_LIST_FREE(badl, s)
     eina_stringshare_del(s);
}

static void
_ignore_cb(void *data, E_Dialog *dialog)
{
   const char *s;

   e_object_del(E_OBJECT(dialog));

   EINA_LIST_FREE(e_config->bad_modules, s)
     eina_stringshare_del(s);
   e_config->bad_modules = data;
   e_config_save_queue();
}

static Eina_Bool
_e_module_whitelist_dialog_timer(void *badl)
{
   E_Dialog *dia;
   Eina_Strbuf *sbuf;
   Eina_List *l;
   const char *s;

   dia = e_dialog_new(NULL,
                      "E", "_module_whitelist_dialog");
   sbuf = eina_strbuf_new();
   eina_strbuf_append
     (sbuf, _("The following modules are not standard ones for<ps/>"
              "Enlightenment and may cause bugs and crashes.<ps/>"
              "Please remove them before reporting any bugs.<ps/>"
              "<ps/>"
              "The module list is as follows:<ps/>"
              "<ps/>"));
   EINA_LIST_FOREACH(badl, l, s)
     {
        eina_strbuf_append(sbuf, s);
        eina_strbuf_append(sbuf, "<ps/>");
     }

   e_dialog_title_set(dia, _("Unstable module tainting"));
   e_dialog_icon_set(dia, "enlightenment", 64);
   e_dialog_text_set(dia, eina_strbuf_string_get(sbuf));
   e_dialog_button_add(dia, _("OK"), NULL, _cleanup_cb, badl);
   e_dialog_button_add(dia, _("I know"), NULL, _ignore_cb, badl);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   eina_strbuf_free(sbuf);
   return EINA_FALSE;
}

static void
_e_module_whitelist_check(void)
{
   Eina_List *l, *badl = NULL;
   E_Module *m;
   unsigned int known = 0;
   int i;
   const char *s;
   const char *goodmods[] =
   {
      "backlight",
      "battery",
      "bluez5",
      "clock",
      "conf",
      "conf_applications",
      "conf_comp",
      "conf_dialogs",
      "conf_display",
      "conf_interaction",
      "conf_intl",
      "conf_bindings",
      "conf_menus",
      "conf_paths",
      "conf_performance",
      "conf_randr",
      "conf_shelves",
      "conf_theme",
      "conf_window_manipulation",
      "conf_window_remembers",
      "connman",
      "cpufreq",
      "everything",
      "fileman",
      "fileman_opinfo",
      "gadman",
      "ibar",
      "ibox",
      "layout",
      "lokker",
      "polkit",
      "luncher",
      "mixer",
      "msgbus",
      "notification",
      "ofono",
      "pager",
      "quickaccess",
      "shot",
      "start",
      "syscon",
      "sysinfo",
      "systray",
      "tasks",
      "temperature",
      "tiling",
      "time",
      "winlist",
      "wireless",
      "wizard",
      "wl_buffer",
      "wl_desktop_shell",
      "wl_x11",
      "wl_wl",
      "wl_drm",
      "wl_shell",
      "wl_desktop_shell",
      "xkbswitch",
      "music-control",
      "appmenu",
      "packagekit",
      "policy_mobile",
      "geolocation",
      "xwayland",
      "procstats",
      NULL   // end marker
   };

   EINA_LIST_FOREACH(_e_modules, l, m)
     {
        Eina_Bool ok;

        if (!m->name) continue;
        ok = EINA_FALSE;
        for (i = 0; goodmods[i]; i++)
          {
             if (!strcmp(m->name, goodmods[i]))
               {
                  ok = EINA_TRUE;
                  break;
               }
          }
        if (!ok) badl = eina_list_append(badl, eina_stringshare_add(m->name));
     }

   EINA_LIST_FOREACH(badl, l, s)
     {
        const char *tmp;
        Eina_List *ll;
        Eina_Bool found = EINA_FALSE;

        EINA_LIST_FOREACH(e_config->bad_modules, ll, tmp)
          {
             if (!strcmp(s, tmp))
               {
                  found = EINA_TRUE;
                  break;
               }
          }

        if (!found) break;
        known++;
     }

#ifndef HAVE_WAYLAND_ONLY
   {
      const char *state;

      state = badl ? "YES" : "NO";

      if (e_comp->comp_type == E_PIXMAP_TYPE_X)
        {
           Ecore_X_Atom _x_tainted;
           unsigned int _e_tainted = badl ? 1 : 0;

           _x_tainted = ecore_x_atom_get("_E_TAINTED");
           ecore_x_window_prop_card32_set(ecore_x_window_root_first_get(),
                                          _x_tainted, &_e_tainted, 1);
        }

      e_env_set("E_TAINTED", state);
   }
#endif

   if (eina_list_count(badl) != known)
     ecore_timer_loop_add(1.5, _e_module_whitelist_dialog_timer, badl);
   else
     {
        EINA_LIST_FREE(badl, s)
          eina_stringshare_del(s);
     }
}

#include "e.h"
#ifdef USE_MODULE_WL_DRM
# include <Ecore_Drm2.h>
#endif

E_API int
e_mouse_update(void)
{
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     e_comp_x_devices_config_apply(EINA_TRUE);
#endif

#ifdef USE_MODULE_WL_DRM
   if (strstr(ecore_evas_engine_name_get(e_comp->ee), "drm"))
     {
        Ecore_Drm2_Device *dev;

        dev = ecore_evas_data_get(e_comp->ee, "device");
        if (dev)
          {
             ecore_drm2_device_pointer_left_handed_set(dev, (Eina_Bool)!e_config->mouse_hand);
             ecore_drm2_device_pointer_accel_speed_set(dev, e_config->mouse_accel);
             ecore_drm2_device_touch_tap_to_click_enabled_set(dev, e_config->touch_tap_to_click);
          }
     }
#endif
   return 1;
}
#include "e.h"

static void _e_resize_begin(void *data, E_Client *ec);
static void _e_resize_update(void *data, E_Client *ec);
static void _e_resize_end(void *data, E_Client *ec);
static void _e_move_begin(void *data, E_Client *ec);
static void _e_move_update(void *data, E_Client *ec);
static void _e_move_end(void *data, E_Client *ec);

static Evas_Object *_disp_obj = NULL;
static Evas_Object *_disp_content = NULL;
static Eina_List *hooks = NULL;

static Eina_Bool _e_moveresize_enabled = EINA_TRUE;

EINTERN int
e_moveresize_init(void)
{
   E_Client_Hook *h;

   h = e_client_hook_add(E_CLIENT_HOOK_RESIZE_BEGIN, _e_resize_begin, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_RESIZE_UPDATE, _e_resize_update, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_RESIZE_END, _e_resize_end, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_MOVE_BEGIN, _e_move_begin, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_MOVE_UPDATE, _e_move_update, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_MOVE_END, _e_move_end, NULL);
   if (h) hooks = eina_list_append(hooks, h);

   return 1;
}

EINTERN int
e_moveresize_shutdown(void)
{
   E_Client_Hook *h;

   EINA_LIST_FREE(hooks, h)
     e_client_hook_del(h);

   _e_moveresize_enabled = EINA_TRUE;

   return 1;
}

E_API void
e_moveresize_replace(Eina_Bool enable)
{
   _e_moveresize_enabled = !enable;
}

E_API void
e_moveresize_client_extents(const E_Client *ec, int *w, int *h)
{
   int l, r, t, b, bw, bh;

   e_comp_object_frame_geometry_get(ec->frame, &l, &r, &t, &b);
   bw = ec->icccm.base_w;
   bh = ec->icccm.base_h;
   if (e_comp_object_frame_allowed(ec->frame))
     *w = ec->client.w, *h = ec->client.h;
   else
     {
        *w = ec->w;
        *h = ec->h;
        bw -= -l + -r;
        bh -= -t + -b;
     }
   if ((bw >= 0) &&
       (bh >= 0))
     {
        if (ec->icccm.step_w > 0)
          *w = (*w - bw) / ec->icccm.step_w;
        if (ec->icccm.step_h > 0)
          *h = (*h - bh) / ec->icccm.step_h;
     }
}

static void
_e_resize_begin(void *data EINA_UNUSED, E_Client *ec)
{
   Evas_Object *o;
   Evas_Coord ew, eh;
   char buf[40];
   int w, h;

   if (_disp_obj) evas_object_hide(_disp_obj);
   E_FREE_FUNC(_disp_obj, evas_object_del);

   if ((!e_config->resize_info_visible) || (!_e_moveresize_enabled))
     return;

   e_moveresize_client_extents(ec, &w, &h);

   _disp_content = o = edje_object_add(e_comp->evas);
   evas_object_name_set(o, "resizeinfo->_disp_content");
   e_theme_edje_object_set(o, "base/theme/borders",
                           "e/widgets/border/default/resize");
   snprintf(buf, sizeof(buf), "9999x9999");
   edje_object_part_text_set(o, "e.text.label", buf);

   edje_object_size_min_calc(o, &ew, &eh);
   snprintf(buf, sizeof(buf), _("%i×%i"), w, h);
   edje_object_part_text_set(o, "e.text.label", buf);

   _disp_obj = e_comp_object_util_add(o, E_COMP_OBJECT_TYPE_POPUP);
   evas_object_name_set(o, "resizeinfo->_disp_obj");
   evas_object_layer_set(_disp_obj, E_LAYER_POPUP);
   evas_object_pass_events_set(_disp_obj, 1);

   evas_object_resize(_disp_obj, ew, eh);
   if (e_config->resize_info_follows)
     e_comp_object_util_center_on(_disp_obj, ec->frame);
   else
     e_comp_object_util_center(_disp_obj);
   evas_object_show(_disp_obj);
}

static void
_e_resize_end(void *data EINA_UNUSED, E_Client *ec EINA_UNUSED)
{
   if (e_config->resize_info_visible)
     {
        if (_disp_obj)
          {
             evas_object_hide(_disp_obj);
             evas_object_del(_disp_obj);
             _disp_obj = NULL;
             _disp_content = NULL;
          }
     }

}

static void
_e_resize_update(void *data EINA_UNUSED, E_Client *ec)
{
   char buf[40];
   int w, h;

   if (!_disp_obj) return;

   if (e_config->resize_info_follows)
     e_comp_object_util_center_on(_disp_obj, ec->frame);

   e_moveresize_client_extents(ec, &w, &h);

   snprintf(buf, sizeof(buf), _("%i×%i"), w, h);
   edje_object_part_text_set(_disp_content, "e.text.label", buf);
}

static void
_e_move_begin(void *data EINA_UNUSED, E_Client *ec)
{
   Evas_Object *o;
   Evas_Coord ew, eh;
   char buf[40];

   if (_disp_obj)
     {
        evas_object_hide(_disp_obj);
        evas_object_del(_disp_obj);
     }
   _disp_obj = NULL;

   if ((!e_config->move_info_visible) || (!_e_moveresize_enabled))
     return;

   _disp_content = o = edje_object_add(e_comp->evas);
   evas_object_name_set(o, "moveinfo->_disp_content");
   e_theme_edje_object_set(o, "base/theme/borders",
                           "e/widgets/border/default/move");
   snprintf(buf, sizeof(buf), "9999 9999");
   edje_object_part_text_set(o, "e.text.label", buf);

   edje_object_size_min_calc(o, &ew, &eh);
   snprintf(buf, sizeof(buf), "%i %i", ec->x, ec->y);
   edje_object_part_text_set(o, "e.text.label", buf);

   _disp_obj = e_comp_object_util_add(o, E_COMP_OBJECT_TYPE_POPUP);
   evas_object_name_set(o, "moveinfo->_disp_obj");
   evas_object_layer_set(_disp_obj, E_LAYER_POPUP);
   evas_object_pass_events_set(_disp_obj, 1);

   evas_object_resize(_disp_obj, ew, eh);
   if (e_config->move_info_follows)
     e_comp_object_util_center_on(_disp_obj, ec->frame);
   else
     e_comp_object_util_center(_disp_obj);
   evas_object_show(_disp_obj);
}

static void
_e_move_end(void *data EINA_UNUSED, E_Client *ec EINA_UNUSED)
{
   if (e_config->move_info_visible)
     {
        if (_disp_obj)
          {
             evas_object_hide(_disp_obj);
             E_FREE_FUNC(_disp_obj, evas_object_del);
             _disp_content = NULL;
          }
     }
}

static void
_e_move_update(void *data EINA_UNUSED, E_Client *ec)
{
   char buf[40];

   if (!_disp_obj) return;

   if (e_config->move_info_follows)
     e_comp_object_util_center_on(_disp_obj, ec->frame);

   snprintf(buf, sizeof(buf), "%i %i", ec->x, ec->y);
   edje_object_part_text_set(_disp_content, "e.text.label", buf);
}
#include "e.h"

#define PATH "/org/enlightenment/wm/RemoteObject"

/* local subsystem functions */
static void            _e_msgbus_request_name_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending);

static Eldbus_Message *_e_msgbus_core_version_cb(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
static Eldbus_Message *_e_msgbus_core_restart_cb(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);
static Eldbus_Message *_e_msgbus_core_shutdown_cb(const Eldbus_Service_Interface *iface, const Eldbus_Message *msg);

static const Eldbus_Method core_methods[] =
{
   { "Version", NULL, ELDBUS_ARGS({"s", "version"}), _e_msgbus_core_version_cb, 0 },
   { "Restart", NULL, NULL, _e_msgbus_core_restart_cb, 0 },
   { "Shutdown", NULL, NULL, _e_msgbus_core_shutdown_cb, 0 },
   { NULL, NULL, NULL, NULL, 0}
};

static const Eldbus_Service_Interface_Desc core_desc = {
   "org.enlightenment.wm.Core", core_methods, NULL, NULL, NULL, NULL
};

/* local subsystem globals */
static E_Msgbus_Data *_e_msgbus_data = NULL;

/* externally accessible functions */
EINTERN int
e_msgbus_init(void)
{
   _e_msgbus_data = E_NEW(E_Msgbus_Data, 1);

   eldbus_init();

   _e_msgbus_data->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION);
   if (!_e_msgbus_data->conn)
     {
        WRN("Cannot get ELDBUS_CONNECTION_TYPE_SESSION");
        return 0;
     }

   _e_msgbus_data->iface = eldbus_service_interface_register
     (_e_msgbus_data->conn, PATH, &core_desc);
   eldbus_name_request(_e_msgbus_data->conn,
                       "org.enlightenment.wm.service",
                       0, _e_msgbus_request_name_cb, NULL);
   return 1;
}

EINTERN int
e_msgbus_shutdown(void)
{
   if (_e_msgbus_data->iface)
     eldbus_service_object_unregister(_e_msgbus_data->iface);
   if (_e_msgbus_data->conn)
     {
        eldbus_name_release(_e_msgbus_data->conn,
                            "org.enlightenment.wm.service",
                            NULL, NULL);
        eldbus_connection_unref(_e_msgbus_data->conn);
     }
   eldbus_shutdown();

   E_FREE(_e_msgbus_data);
   _e_msgbus_data = NULL;
   return 1;
}

E_API Eldbus_Service_Interface *
e_msgbus_interface_attach(const Eldbus_Service_Interface_Desc *desc)
{
   if (!_e_msgbus_data->iface) return NULL;
   return eldbus_service_interface_register(_e_msgbus_data->conn, PATH, desc);
}

static void
_e_msgbus_request_name_cb(void *data EINA_UNUSED, const Eldbus_Message *msg,
                          Eldbus_Pending *pending EINA_UNUSED)
{
   unsigned int flag;

   if (eldbus_message_error_get(msg, NULL, NULL))
     {
        ERR("Could not request bus name");
        return;
     }

   if (!eldbus_message_arguments_get(msg, "u", &flag))
     {
        ERR("Could not get arguments on on_name_request");
        return;
     }

   if (!(flag & ELDBUS_NAME_REQUEST_REPLY_PRIMARY_OWNER))
     WRN("Name already in use\n");
}

/* Core Handlers */
static Eldbus_Message *
_e_msgbus_core_version_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
                          const Eldbus_Message *msg)
{
   Eldbus_Message *reply = eldbus_message_method_return_new(msg);
   EINA_SAFETY_ON_NULL_RETURN_VAL(reply, NULL);
   eldbus_message_arguments_append(reply, "s", VERSION);
   return reply;
}

static Eldbus_Message *
_e_msgbus_core_restart_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
                          const Eldbus_Message *msg)
{
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     ERR("DBus restart API disabled for security reasons");
   else
     e_sys_action_do(E_SYS_RESTART, NULL);
   return eldbus_message_method_return_new(msg);
}

static Eldbus_Message *
_e_msgbus_core_shutdown_cb(const Eldbus_Service_Interface *iface EINA_UNUSED,
                           const Eldbus_Message *msg)
{
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     ERR("DBus shutdown API disabled for security reasons");
   else
     e_sys_action_do(E_SYS_EXIT, NULL);
   return eldbus_message_method_return_new(msg);
}
#include "e.h"

typedef struct _E_Msg_Event E_Msg_Event;

struct _E_Msg_Handler
{
   void          (*func)(void *data, const char *name, const char *info, int val, E_Object *obj, void *msgdata);
   void         *data;
   unsigned char delete_me E_BITFIELD;
};

struct _E_Msg_Event
{
   char     *name;
   char     *info;
   int       val;
   E_Object *obj;
   void     *msgdata;
   void      (*afterfunc)(void *data, E_Object *obj, void *msgdata);
   void     *afterdata;
};

/* local subsystem functions */
static Eina_Bool _e_msg_event_cb(void *data, int ev_type, void *ev);
static void      _e_msg_event_free(void *data, void *ev);

/* local subsystem globals */
static Eina_List *handlers = NULL;
static Eina_List *del_handlers = NULL;
static int processing_handlers = 0;
static int E_EVENT_MSG = 0;
static Ecore_Event_Handler *hand = NULL;

/* externally accessible functions */
EINTERN int
e_msg_init(void)
{
   E_EVENT_MSG = ecore_event_type_new();
   hand = ecore_event_handler_add(E_EVENT_MSG, _e_msg_event_cb, NULL);
   return 1;
}

EINTERN int
e_msg_shutdown(void)
{
   while (handlers)
     e_msg_handler_del(eina_list_data_get(handlers));
   E_EVENT_MSG = 0;
   if (hand) ecore_event_handler_del(hand);
   hand = NULL;
   return 1;
}

E_API void
e_msg_send(const char *name, const char *info, int val, E_Object *obj, void *msgdata, void (*afterfunc)(void *data, E_Object *obj, void *msgdata), void *afterdata)
{
   unsigned int size, pos, name_len, info_len;
   E_Msg_Event *ev;

   name_len = info_len = 0;
   size = sizeof(E_Msg_Event);
   if (name) name_len = strlen(name) + 1;
   if (info) info_len = strlen(info) + 1;
   ev = malloc(size + name_len + info_len);
   if (!ev) return;
   pos = size;
   if (name)
     {
        ev->name = ((char *)ev) + pos;
        pos += name_len;
        strcpy(ev->name, name);
     }
   if (info)
     {
        ev->info = ((char *)ev) + pos;
        strcpy(ev->info, info);
     }
   ev->val = val;
   ev->obj = obj;
   ev->msgdata = msgdata;
   ev->afterfunc = afterfunc;
   ev->afterdata = afterdata;
   if (ev->obj) e_object_ref(ev->obj);
   ecore_event_add(E_EVENT_MSG, ev, _e_msg_event_free, NULL);
}

E_API E_Msg_Handler *
e_msg_handler_add(void (*func)(void *data, const char *name, const char *info, int val, E_Object *obj, void *msgdata), void *data)
{
   E_Msg_Handler *emsgh;

   emsgh = calloc(1, sizeof(E_Msg_Handler));
   if (!emsgh) return NULL;
   emsgh->func = func;
   emsgh->data = data;
   handlers = eina_list_append(handlers, emsgh);
   return emsgh;
}

E_API void
e_msg_handler_del(E_Msg_Handler *emsgh)
{
   if (processing_handlers > 0)
     {
        emsgh->delete_me = 1;
        del_handlers = eina_list_append(del_handlers, emsgh);
     }
   else
     {
        handlers = eina_list_remove(handlers, emsgh);
        free(emsgh);
     }
}

/* local subsystem functions */

static Eina_Bool
_e_msg_event_cb(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev)
{
   E_Msg_Event *e;
   Eina_List *l;
   E_Msg_Handler *emsgh;

   processing_handlers++;
   e = ev;
   EINA_LIST_FOREACH(handlers, l, emsgh)
     {
        if (!emsgh->delete_me)
          emsgh->func(emsgh->data, e->name, e->info, e->val, e->obj, e->msgdata);
     }
   if (e->afterfunc) e->afterfunc(e->afterdata, e->obj, e->msgdata);
   processing_handlers--;
   if ((processing_handlers == 0) && (del_handlers))
     {
        E_FREE_LIST(del_handlers, e_msg_handler_del);
     }
   return 1;
}

static void
_e_msg_event_free(void *data EINA_UNUSED, void *ev)
{
   E_Msg_Event *e;

   e = ev;
   if (e->obj) e_object_unref(e->obj);

   E_FREE(ev);
}

#include "e.h"

typedef struct _Notification_Data
{
   Eldbus_Connection                 *conn;
   Eldbus_Service_Interface          *iface;
   E_Notification_Notify_Cb          notify_cb;
   E_Notification_Close_Cb           close_cb;
   void                             *data;
   const E_Notification_Server_Info *server_info;
} Notification_Data;

static Notification_Data *n_data = NULL;

static void
_notification_free(E_Notification_Notify *notify)
{
   EINA_SAFETY_ON_NULL_RETURN(notify);
   eina_stringshare_del(notify->app_name);
   eina_stringshare_del(notify->body);
   eina_stringshare_del(notify->icon.icon);
   if (notify->icon.icon_path)
     eina_stringshare_del(notify->icon.icon_path);
   eina_stringshare_del(notify->summary);
   if (notify->icon.raw.data)
     free(notify->icon.raw.data);
   free(notify);
}

static void
hints_dict_iter(void *data, const void *key, Eldbus_Message_Iter *var)
{
   E_Notification_Notify *n = data;
   if (!strcmp(key, "image-data") || !strcmp(key, "image_data"))
     {
        Eldbus_Message_Iter *st, *data_iter;
        int w, h, r, bits, channels;
        Eina_Bool alpha;
        unsigned char *raw_data;
        if (!eldbus_message_iter_arguments_get(var, "(iiibiiay)", &st))
          return;
        if (!eldbus_message_iter_arguments_get(st, "iiibiiay", &w, &h, &r,
                                              &alpha, &bits, &channels,
                                              &data_iter))
          return;
        eldbus_message_iter_fixed_array_get(data_iter, 'y', &raw_data,
                                           &n->icon.raw.data_size);
        n->icon.raw.width = w;
        n->icon.raw.height = h;
        n->icon.raw.has_alpha = alpha;
        n->icon.raw.rowstride = r;
        n->icon.raw.bits_per_sample = bits;
        n->icon.raw.channels = channels;
        n->icon.raw.data = malloc(sizeof(char) * n->icon.raw.data_size);
        EINA_SAFETY_ON_NULL_RETURN(n->icon.raw.data);
        memcpy(n->icon.raw.data, raw_data, sizeof(char) * n->icon.raw.data_size);
     }
   else if (!strcmp(key, "urgency"))
     {
        unsigned char urgency;
        eldbus_message_iter_arguments_get(var, "y", &urgency);
        if (urgency < 3)
          n->urgency = urgency;
     }
   else if (!strcmp(key, "image-path") || !strcmp(key, "image_path"))
     {
        eldbus_message_iter_arguments_get(var, "s", &n->icon.icon_path);
        n->icon.icon_path = eina_stringshare_add(n->icon.icon_path);
     }
}


/* this function should be external in edje for use in cases such as this module.
 *
 * happily, it was decided that the function would not be external so that it could
 * be duplicated into the module in full.
 */

static int
_text_escape(Eina_Strbuf *txt, const char *text)
{
   const char *escaped;
   int advance;

   escaped = evas_textblock_string_escape_get(text, &advance);
   if (!escaped)
     {
        eina_strbuf_append_char(txt, text[0]);
        advance = 1;
     }
   else
     eina_strbuf_append(txt, escaped);
   return advance;
}

/* hardcoded list of allowed tags based on
 * https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html#markup
 */
static const char *tags[] =
{
   "<b",
   "<i",
   "<u",
   //"<a", FIXME: we can't actually display these right now
   //"<img",
};

static const char *
_get_tag(const char *c)
{
   unsigned int i;

   if (c[1] != '>') return NULL;
   for (i = 0; i < EINA_C_ARRAY_LENGTH(tags); i++)
     if (tags[i][1] == c[0]) return tags[i];
   return NULL;
}

char *
_nedje_text_escape(const char *text)
{
   Eina_Strbuf *txt;
   char *ret;
   const char *text_end;
   size_t text_len;
   Eina_Array *arr;
   const char *cur_tag = NULL;

   if (!text) return NULL;

   txt = eina_strbuf_new();
   text_len = strlen(text);
   arr = eina_array_new(3);

   text_end = text + text_len;
   while (text < text_end)
     {
        int advance;

        if ((text[0] == '<') && text[1])
          {
             const char *tag, *popped;
             Eina_Bool closing = EINA_FALSE;

             if (text[1] == '/') //closing tag
               {
                  closing = EINA_TRUE;
                  tag = _get_tag(text + 2);
               }
             else
               tag = _get_tag(text + 1);
             if (closing)
               {
                  if (cur_tag && (tag != cur_tag))
                    {
                       /* tag mismatch: autoclose all failure tags
                        * not technically required by the spec,
                        * but it makes me feel better about myself
                        */
                       do
                         {
                            popped = eina_array_pop(arr);
                            if (eina_array_count(arr))
                              cur_tag = eina_array_data_get(arr, eina_array_count(arr) - 1);
                            else
                              cur_tag = NULL;
                            eina_strbuf_append_printf(txt, "</%c>", popped[1]);
                         } while (cur_tag && (popped != tag));
                       advance = 4;
                    }
                  else if (cur_tag)
                    {
                       /* tag match: just pop */
                       popped = eina_array_pop(arr);
                       if (eina_array_count(arr))
                         cur_tag = eina_array_data_get(arr, eina_array_count(arr) - 1);
                       else
                         cur_tag = NULL;
                       eina_strbuf_append_printf(txt, "</%c>", popped[1]);
                       advance = 4;
                    }
                  else
                    {
                       /* no current tag: escape */
                       advance = _text_escape(txt, text);
                    }
               }
             else
               {
                  if (tag)
                    {
                       cur_tag = tag;
                       eina_array_push(arr, tag);
                       eina_strbuf_append_printf(txt, "<%c>", tag[1]);
                       advance = 3;
                    }
                  else
                    advance = _text_escape(txt, text);
               }
          }
        else if (text[0] == '&')
          {
             const char *s;

             s = strchr(text, ';');
             if (s)
               s = evas_textblock_escape_string_range_get(text, s + 1);
             if (s)
               {
                  eina_strbuf_append_char(txt, text[0]);
                  advance = 1;
               }
             else
               advance = _text_escape(txt, text);
          }
        else
          advance = _text_escape(txt, text);

        text += advance;
     }

   eina_array_free(arr);
   ret = eina_strbuf_string_steal(txt);
   eina_strbuf_free(txt);
   return ret;
}

static Eldbus_Message *
notify_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
{
   E_Notification_Notify *n;
   Eldbus_Message_Iter *actions_iter, *hints_iter;
   Eldbus_Message *reply;
   char *txt;

   if (!n_data->notify_cb)
     return NULL;

   n = E_OBJECT_ALLOC(E_Notification_Notify, E_NOTIFICATION_TYPE, _notification_free);
   n->urgency = E_NOTIFICATION_NOTIFY_URGENCY_NORMAL;
   if (!eldbus_message_arguments_get(msg, "susssasa{sv}i", &n->app_name,
                                    &n->replaces_id, &n->icon.icon, &n->summary,
                                    &n->body, &actions_iter, &hints_iter,
                                    &n->timeout))
     {
        ERR("Reading message.");
        e_object_del(E_OBJECT(n));
        return NULL;
     }
   if (e_screensaver_on_get() && e_config->screensaver_wake_on_notify)
     {
        int x, y;
        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        ecore_evas_pointer_warp(e_comp->ee, x, y);
     }
   eldbus_message_iter_dict_iterate(hints_iter, "sv", hints_dict_iter, n);
   n->app_name = eina_stringshare_add(n->app_name);
   n->icon.icon = eina_stringshare_add(n->icon.icon);
   n->summary = eina_stringshare_add(n->summary);
   txt = _nedje_text_escape(n->body);
   n->body = eina_stringshare_add(txt);
   free(txt);

   e_object_ref(E_OBJECT(n));
   n->id = n_data->notify_cb(n_data->data, n);
   reply = eldbus_message_method_return_new(msg);
   eldbus_message_arguments_append(reply, "u", n->id);
   e_object_unref(E_OBJECT(n));
   return reply;
}

static Eldbus_Message *
close_notification_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
{
   unsigned id;
   if (!eldbus_message_arguments_get(msg, "u", &id))
     return NULL;
   if (n_data->close_cb)
     n_data->close_cb(n_data->data, id);
   return eldbus_message_method_return_new(msg);
}

static Eldbus_Message *
capabilities_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
{
   Eldbus_Message *reply = eldbus_message_method_return_new(msg);
   int i;
   Eldbus_Message_Iter *main_iter, *array;

   main_iter = eldbus_message_iter_get(reply);
   eldbus_message_iter_arguments_append(main_iter, "as", &array);

   for (i = 0; n_data->server_info->capabilities[i]; i++)
     eldbus_message_iter_arguments_append(array, "s",
                                         n_data->server_info->capabilities[i]);
   eldbus_message_iter_container_close(main_iter, array);
   return reply;
}

static Eldbus_Message *
server_info_cb(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message *msg)
{
   Eldbus_Message *reply = eldbus_message_method_return_new(msg);
   eldbus_message_arguments_append(reply, "ssss", n_data->server_info->name,
                                  n_data->server_info->vendor,
                                  n_data->server_info->version,
                                  n_data->server_info->spec_version);
   return reply;
}

static const Eldbus_Method methods[] = {
   { "Notify",
     ELDBUS_ARGS({"s", "app_name"}, {"u", "replaces_id"}, {"s", "app_icon"}, {"s", "summary"}, {"s", "body"}, {"as", "actions"}, {"a{sv}", "hints"}, {"i", "expire_timeout"}),
     ELDBUS_ARGS({"u", "id"}), notify_cb, 0 },
   { "CloseNotification", ELDBUS_ARGS({"u", "id"}), NULL, close_notification_cb, 0 },
   { "GetCapabilities", NULL, ELDBUS_ARGS({"as", "capabilities"}),
     capabilities_cb, 0 },
   { "GetServerInformation", NULL,
     ELDBUS_ARGS({"s", "name"}, {"s", "vendor"}, {"s", "version"}, {"s", "spec_version"}),
     server_info_cb, 0 },
   { NULL, NULL, NULL, NULL, 0 }
};

enum
{
   SIGNAL_NOTIFICATION_CLOSED = 0,
   SIGNAL_ACTION_INVOKED,
};

static const Eldbus_Signal signals[] = {
   [SIGNAL_NOTIFICATION_CLOSED] =
   { "NotificationClosed", ELDBUS_ARGS({"u", "id"}, {"u", "reason"}), 0 },
   [SIGNAL_ACTION_INVOKED] =
   { "ActionInvoked", ELDBUS_ARGS({"u", "id"}, {"s", "action_key"}), 0 },
   { NULL, NULL, 0}
};

#define PATH      "/org/freedesktop/Notifications"
#define BUS       "org.freedesktop.Notifications"
#define INTERFACE "org.freedesktop.Notifications"

static const Eldbus_Service_Interface_Desc desc = {
   INTERFACE, methods, signals, NULL, NULL, NULL
};

E_API Eina_Bool
e_notification_server_register(const E_Notification_Server_Info *server_info, E_Notification_Notify_Cb n_cb, E_Notification_Close_Cb close_cb, const void *data)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(server_info, EINA_FALSE);
   if (n_data)
     return EINA_FALSE;
   n_data = calloc(1, sizeof(Notification_Data));
   EINA_SAFETY_ON_NULL_RETURN_VAL(n_data, EINA_FALSE);

   n_data->conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION);
   n_data->iface = eldbus_service_interface_register(n_data->conn, PATH, &desc);
   n_data->notify_cb = n_cb;
   n_data->close_cb = close_cb;
   n_data->data = (void *)data;
   n_data->server_info = server_info;
   eldbus_name_request(n_data->conn, BUS,
                      ELDBUS_NAME_REQUEST_FLAG_REPLACE_EXISTING, NULL, NULL);

   return EINA_TRUE;
}

E_API void
e_notification_server_unregister(void)
{
   EINA_SAFETY_ON_NULL_RETURN(n_data);
   eldbus_service_interface_unregister(n_data->iface);
   eldbus_connection_unref(n_data->conn);
   free(n_data);
   n_data = NULL;
}

E_API void
e_notification_notify_close(E_Notification_Notify *notify, E_Notification_Notify_Closed_Reason reason)
{
   EINA_SAFETY_ON_NULL_RETURN(n_data);
   EINA_SAFETY_ON_NULL_RETURN(notify);
   EINA_SAFETY_ON_FALSE_RETURN(reason <= E_NOTIFICATION_NOTIFY_CLOSED_REASON_UNDEFINED);
   eldbus_service_signal_emit(n_data->iface, SIGNAL_NOTIFICATION_CLOSED,
                             notify->id, reason);
}

E_API Evas_Object *
e_notification_notify_raw_image_get(E_Notification_Notify *notify, Evas *evas)
{
   Evas_Object *o;
   unsigned char *imgdata;

   EINA_SAFETY_ON_NULL_RETURN_VAL(notify, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(evas, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(notify->icon.raw.data, NULL);

   o = evas_object_image_filled_add(evas);
   evas_object_resize(o, notify->icon.raw.width, notify->icon.raw.height);
   evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
   evas_object_image_alpha_set(o, notify->icon.raw.has_alpha);
   evas_object_image_size_set(o, notify->icon.raw.width, notify->icon.raw.height);

   imgdata = evas_object_image_data_get(o, EINA_TRUE);
   EINA_SAFETY_ON_NULL_GOTO(imgdata, error);

   if (notify->icon.raw.bits_per_sample == 8)
     {
        /* Although not specified.
         * The data are very likely to come from a GdkPixbuf
         * which align each row on a 4-bytes boundary when using RGB.
         * And is RGBA otherwise. */
        int x, y, *d;
        unsigned char *s;
        int rowstride = evas_object_image_stride_get(o);

        for (y = 0; y < notify->icon.raw.height; y++)
          {
             s = notify->icon.raw.data + (y * notify->icon.raw.rowstride);
             d = (int *)(void *)(imgdata + (y * rowstride));

             for (x = 0; x < notify->icon.raw.width;
                  x++, s += notify->icon.raw.channels, d++)
               {
                  if (notify->icon.raw.has_alpha)
                    *d = (s[3] << 24) | (s[0] << 16) | (s[1] << 8) | (s[2]);
                  else
                    *d = (0xff << 24) | (s[0] << 16) | (s[1] << 8) | (s[2]);
               }
          }
     }
   evas_object_image_data_update_add(o, 0, 0, notify->icon.raw.width,
                                     notify->icon.raw.height);
   evas_object_image_data_set(o, imgdata);

   return o;
error:
   evas_object_del(o);
   return NULL;
}

/* client API */

static void
client_notify_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending)
{
   unsigned id = 0;
   E_Notification_Client_Send_Cb cb = eldbus_pending_data_del(pending, "cb");
   Eldbus_Connection *conn = eldbus_pending_data_del(pending, "conn");
   if (eldbus_message_error_get(msg, NULL, NULL))
     goto end;
   if (!eldbus_message_arguments_get(msg, "u", &id))
     goto end;
end:
   if (cb)
     cb(data, id);
   eldbus_connection_unref(conn);
   eldbus_shutdown();
}

static Eina_Bool
notification_client_dbus_send(E_Notification_Notify *notify, E_Notification_Client_Send_Cb cb, const void *data)
{
   Eldbus_Connection *conn;
   Eldbus_Message *msg;
   Eldbus_Message_Iter *main_iter, *actions, *hints;
   Eldbus_Message_Iter *entry, *var;
   Eldbus_Pending *p;
   EINA_SAFETY_ON_NULL_RETURN_VAL(notify, EINA_FALSE);
   eldbus_init();
   conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION);
   EINA_SAFETY_ON_NULL_RETURN_VAL(conn, EINA_FALSE);

   msg = eldbus_message_method_call_new(BUS, PATH, INTERFACE, "Notify");
   EINA_SAFETY_ON_NULL_RETURN_VAL(msg, EINA_FALSE);

   //build message
   main_iter = eldbus_message_iter_get(msg);
   if (!eldbus_message_iter_arguments_append(main_iter, "susssas",
                                            notify->app_name ? : "",
                                            notify->replaces_id,
                                            notify->icon.icon ? : "",
                                            notify->summary ? : "",
                                            notify->body ? : "",
                                            &actions))
     goto error;
   eldbus_message_iter_container_close(main_iter, actions);
   if (!eldbus_message_iter_arguments_append(main_iter, "a{sv}", &hints))
     goto error;

   if (notify->icon.raw.data)
     {
        Eldbus_Message_Iter *st, *data_iter;
        int i;
        eldbus_message_iter_arguments_append(hints, "{sv}", &entry);
        eldbus_message_iter_arguments_append(entry, "s", "image-data");
        var = eldbus_message_iter_container_new(entry, 'v', "(iiibiiay)");
        eldbus_message_iter_arguments_append(var, "(iiibiiay)", &st);
        eldbus_message_iter_arguments_append(st, "iiibiiay",
                                            notify->icon.raw.width,
                                            notify->icon.raw.height,
                                            notify->icon.raw.rowstride,
                                            notify->icon.raw.has_alpha,
                                            notify->icon.raw.bits_per_sample,
                                            notify->icon.raw.channels,
                                            &data_iter);
        for (i = 0; i < notify->icon.raw.data_size; i++)
          eldbus_message_iter_basic_append(data_iter, 'y', notify->icon.raw.data[i]);
        eldbus_message_iter_container_close(st, data_iter);
        eldbus_message_iter_container_close(var, st);
        eldbus_message_iter_container_close(entry, var);
        eldbus_message_iter_container_close(hints, entry);
     }
   if (notify->icon.icon_path)
     {
        eldbus_message_iter_arguments_append(hints, "{sv}", &entry);
        eldbus_message_iter_arguments_append(entry, "s", "image-path");
        var = eldbus_message_iter_container_new(entry, 'v', "s");
        eldbus_message_iter_arguments_append(var, "s", notify->icon.icon_path);
        eldbus_message_iter_container_close(entry, var);
        eldbus_message_iter_container_close(hints, entry);
     }

   eldbus_message_iter_arguments_append(hints, "{sv}", &entry);
   eldbus_message_iter_arguments_append(entry, "s", "urgency");
   var = eldbus_message_iter_container_new(entry, 'v', "y");
   eldbus_message_iter_arguments_append(var, "y", notify->urgency);
   eldbus_message_iter_container_close(entry, var);
   eldbus_message_iter_container_close(hints, entry);

   eldbus_message_iter_container_close(main_iter, hints);

   eldbus_message_iter_arguments_append(main_iter, "i", notify->timeout);

   p = eldbus_connection_send(conn, msg, client_notify_cb, data, 5000);
   EINA_SAFETY_ON_NULL_GOTO(p, error);
   if (cb)
     eldbus_pending_data_set(p, "cb", cb);
   eldbus_pending_data_set(p, "conn", conn);

   return EINA_TRUE;

error:
   eldbus_message_unref(msg);
   return EINA_FALSE;
}

static void
normalize_notify(E_Notification_Notify *notify)
{
   if (!notify->timeout)
     notify->timeout = -1;
}

E_API Eina_Bool
e_notification_client_send(E_Notification_Notify *notify, E_Notification_Client_Send_Cb cb, const void *data)
{
   unsigned id;
   E_Notification_Notify *copy;

   EINA_SAFETY_ON_NULL_RETURN_VAL(notify, EINA_FALSE);
   normalize_notify(notify);

   if (!n_data)
     {
        fprintf(stderr, "UNHANDLED NOTIFICATION:\nSummary: %s\nBody: %s\n", notify->summary, notify->body);
        return notification_client_dbus_send(notify, cb, data);
     }

   //local
   copy = malloc(sizeof(E_Notification_Notify));
   EINA_SAFETY_ON_NULL_RETURN_VAL(copy, EINA_FALSE);
   memcpy(copy, notify, sizeof(E_Notification_Notify));

   copy->app_name = eina_stringshare_add(notify->app_name);
   copy->body = eina_stringshare_add(notify->body);
   copy->summary = eina_stringshare_add(notify->summary);
   copy->icon.icon = eina_stringshare_add(notify->icon.icon);
   if (notify->icon.icon_path)
     copy->icon.icon_path = eina_stringshare_add(notify->icon.icon_path);

   id = n_data->notify_cb(n_data->data, copy);
   if (cb)
     cb((void *)data, id);
   return EINA_TRUE;
}

E_API Eina_Bool
e_notification_util_send(const char *summary, const char *body)
{
   E_Notification_Notify n;

   memset(&n, 0, sizeof(E_Notification_Notify));
   n.timeout = 3000;
   n.summary = summary;
   n.body = body;
   n.urgency = E_NOTIFICATION_NOTIFY_URGENCY_NORMAL;
   return e_notification_client_send(&n, NULL, NULL);
}
#include "e.h"

/* local subsystem functions */
static void _e_obj_dialog_free(E_Obj_Dialog *od);
static void _e_obj_dialog_cb_delete(E_Obj_Dialog *od, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _e_obj_dialog_cb_close(void *data, Evas_Object *obj, const char *emission, const char *source);

/* local subsystem globals */

/* externally accessible functions */

static void
_key_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Evas_Event_Key_Down *ev = event;

   if (!strcmp(ev->key, "Escape") && data)
     _e_obj_dialog_cb_delete(data, NULL, NULL, NULL);
}

static void
_focus_in_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   edje_object_signal_emit(data, "e,state,focused", "e");
}

static void
_focus_out_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   edje_object_signal_emit(data, "e,state,unfocused", "e");
}

E_API E_Obj_Dialog *
e_obj_dialog_new(char *title, char *class_name, char *class_class)
{
   E_Obj_Dialog *od;
   Evas_Object *o;
   Eina_Bool kg;
   Evas_Modifier_Mask mask;

   od = E_OBJECT_ALLOC(E_Obj_Dialog, E_OBJ_DIALOG_TYPE, _e_obj_dialog_free);
   if (!od) return NULL;
   od->win = elm_win_add(NULL, NULL, ELM_WIN_DIALOG_BASIC);
   if (!od->win)
     {
        free(od);
        return NULL;
     }
   evas_object_event_callback_add(od->win, EVAS_CALLBACK_DEL, (Evas_Object_Event_Cb)_e_obj_dialog_cb_delete, od);
   ecore_evas_name_class_set(e_win_ee_get(od->win), class_name, class_class);
   elm_win_title_set(od->win, title);

   o = edje_object_add(evas_object_evas_get(od->win));
   elm_win_resize_object_add(od->win, o);
   od->bg_object = o;

   evas_object_smart_callback_add(od->win, "focus,in",
                                  _focus_in_cb, od->bg_object);
   evas_object_smart_callback_add(od->win, "focus,out",
                                  _focus_out_cb, od->bg_object);

   elm_win_center(od->win, 1, 1);
   od->cb_delete = NULL;

   mask = 0;
   kg = evas_object_key_grab(o, "Escape", mask, ~mask, 0);
   if (!kg)
     fprintf(stderr, "ERROR: unable to redirect \"Escape\" key events to object %p.\n", o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_KEY_DOWN,
                                  _key_down_cb, od);

   return od;
}

E_API void
e_obj_dialog_cb_delete_set(E_Obj_Dialog *od, void (*func)(E_Obj_Dialog *od))
{
   od->cb_delete = func;
}

E_API void
e_obj_dialog_icon_set(E_Obj_Dialog *od, char *icon)
{
   E_OBJECT_CHECK(od);
   E_OBJECT_TYPE_CHECK(od, E_OBJ_DIALOG_TYPE);
   e_win_client_icon_set(od->win, icon);
 }

E_API void
e_obj_dialog_show(E_Obj_Dialog *od)
{
   Evas_Coord w, h, mw, mh;
   const char *s;

   E_OBJECT_CHECK(od);
   E_OBJECT_TYPE_CHECK(od, E_OBJ_DIALOG_TYPE);

   edje_object_size_min_get(od->bg_object, &mw, &mh);
   edje_object_size_min_restricted_calc(od->bg_object, &mw, &mh, mw, mh);
   evas_object_resize(od->bg_object, mw, mh);
   evas_object_resize(od->win, mw, mh);
   evas_object_size_hint_min_set(od->win, mw, mh);
   edje_object_size_max_get(od->bg_object, &w, &h);
   if ((w > 0) && (h > 0))
     {
        if (w < mw) w = mw;
        if (h < mh) h = mh;
        evas_object_size_hint_max_set(od->win, w, h);
     }
   s = edje_object_data_get(od->bg_object, "borderless");
   if (s && (!strcmp(s, "1")))
     elm_win_borderless_set(od->win, 1);
   evas_object_show(od->win);
}

E_API void
e_obj_dialog_obj_part_text_set(E_Obj_Dialog *od, const char *part, const char *text)
{
   E_OBJECT_CHECK(od);
   E_OBJECT_TYPE_CHECK(od, E_OBJ_DIALOG_TYPE);
   edje_object_part_text_set(od->bg_object, part, text);
}

E_API void
e_obj_dialog_obj_theme_set(E_Obj_Dialog *od, char *theme_cat, char *theme_obj)
{
   E_OBJECT_CHECK(od);
   E_OBJECT_TYPE_CHECK(od, E_OBJ_DIALOG_TYPE);

   e_theme_edje_object_set(od->bg_object, theme_cat, theme_obj);
   evas_object_move(od->bg_object, 0, 0);
   evas_object_show(od->bg_object);
   edje_object_signal_callback_add(od->bg_object, "e,action,close", "",
                                   _e_obj_dialog_cb_close, od);
}

/* local subsystem functions */
static void
_e_obj_dialog_free(E_Obj_Dialog *od)
{
   if (od->bg_object) evas_object_del(od->bg_object);
   evas_object_del(od->win);
   free(od);
}

static void
_e_obj_dialog_cb_delete(E_Obj_Dialog *od, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   if (od->cb_delete)
     od->cb_delete(od);
   e_object_del(E_OBJECT(od));
}

static void
_e_obj_dialog_cb_close(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Obj_Dialog *od;

   od = data;
   if (od->cb_delete)
     od->cb_delete(od);
   e_util_defer_object_del(E_OBJECT(od));
}
#include "e.h"

/* yes - i know. glibc specific... but i like being able to do my own */
/* backtraces! NB: you need CFLAGS="-rdynamic -g" LDFLAGS="-rdynamic -g" */
#ifdef OBJECT_PARANOIA_CHECK

/* local subsystem functions */
static void _e_object_segv(int sig);

/* local subsystem globals */
static sigjmp_buf _e_object_segv_buf;
#endif

/* externally accessible functions */
E_API void *
e_object_alloc(int size, int type, E_Object_Cleanup_Func cleanup_func)
{
   E_Object *obj;

   obj = calloc(1, size);
   if (!obj) return NULL;
   obj->magic = E_OBJECT_MAGIC;
   obj->type = type;
   obj->references = 1;
   obj->cleanup_func = cleanup_func;
   return obj;
}

static void
_delay_del(void *data)
{
   E_Object *obj = data;

   obj->delay_del_job = NULL;
   if (obj->del_att_func) obj->del_att_func(obj);
   if (obj->del_func) obj->del_func(obj);
   e_object_unref(obj);
}

E_API void
e_object_del(E_Object *obj)
{
   E_OBJECT_CHECK(obj);
   if (obj->deleted) return;
   if (obj->del_delay_func)
     {
        if (obj->ref_debug)
          INF("[%p] DELAY DEL (REF: %d)", obj, obj->references);
        obj->del_delay_func(obj);
        if (!obj->delay_del_job)
          obj->delay_del_job = ecore_job_add(_delay_del, obj);
        obj->deleted = 1;
        return;
     }
   if (obj->ref_debug)
     INF("[%p] DEL (REF: %d)", obj, obj->references);
   obj->deleted = 1;
   if (obj->del_att_func) obj->del_att_func(obj);
   if (obj->del_func) obj->del_func(obj);
   e_object_unref(obj);
}

E_API void
e_object_delay_del_set(E_Object *obj, void *func)
{
   E_OBJECT_CHECK(obj);
   obj->del_delay_func = func;
}

E_API int
e_object_is_del(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, 1);
   return obj->deleted;
}

E_API void
e_object_del_func_set(E_Object *obj, E_Object_Cleanup_Func del_func)
{
   E_OBJECT_CHECK(obj);
   obj->del_func = del_func;
}

E_API void
e_object_type_set(E_Object *obj, int type)
{
   E_OBJECT_CHECK(obj);
   obj->type = type;
}

E_API void
e_object_free(E_Object *obj)
{
   E_OBJECT_CHECK(obj);
   if (obj->free_att_func) obj->free_att_func(obj);
   obj->free_att_func = NULL;
   obj->walking_list++;
   while (obj->del_fn_list)
     {
        E_Object_Delfn *dfn = (E_Object_Delfn *)obj->del_fn_list;
        if (!dfn->delete_me) dfn->func(dfn->data, obj);
        obj->del_fn_list = eina_inlist_remove(obj->del_fn_list,
                                              EINA_INLIST_GET(dfn));
        free(dfn);
     }
   obj->walking_list--;
   if (obj->references) return;
   /*
    * FIXME:
    * although this is good - if during cleanup the cleanup func calls
    * other generic funcs to do cleanups on the same object... we get bitching.
    * disable for now (the final free of the struct should probably happen after
    * the cleanup func and be done by the object system - set the magic after
    * cleanup :)
    */
#if 0
   obj->magic = E_OBJECT_MAGIC_FREED;
#endif
   obj->cleanup_func(obj);
   obj = NULL;
}

E_API int
e_object_ref(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, -1);
   obj->references++;
   if (obj->ref_debug)
     INF("[%p] REF (REF: %d)", obj, obj->references);
   return obj->references;
}

E_API int
e_object_unref(E_Object *obj)
{
   int ref;

   E_OBJECT_CHECK_RETURN(obj, -1);
   if (!obj->references) return 0;
   obj->references--;
   ref = obj->references;
   if (obj->ref_debug)
     {
        if ((ref > 1) && (ref < 1000))
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else if (ref == 1)
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else if (!ref)
          INF("[%p] UNREF (REF: %d)", obj, ref);
        else
          CRI("[%p] UNREF (REF: %d)", obj, ref);
     }
   if (obj->references == 0) e_object_free(obj);
   return ref;
}

E_API int
e_object_ref_get(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, 0);
   return obj->references;
}

#if 0
E_API void
e_bt(void)
{
   int i, trace_num;
   void *trace[128];
   char **messages = NULL;
   trace_num = backtrace(trace, 128);
   messages = backtrace_symbols(trace, trace_num);
   if (messages)
     {
        for (i = 1; i < trace_num; i++)
          {
             int j;

             for (j = 1; j < i; j++)
               putchar(' ');
             printf("%s\n", messages[i]);
          }
        free(messages);
     }
}

#endif

E_API int
e_object_error(E_Object *obj)
{
#ifdef OBJECT_PARANOIA_CHECK
   char buf[4096];
   char bt[8192];
   void *trace[128];
   char **messages = NULL;
   int i, trace_num;

   /* fetch stacktrace */
   trace_num = backtrace(trace, 128);
   messages = backtrace_symbols(trace, trace_num);

   /* build stacktrace */
   bt[0] = 0;
   if (messages)
     {
        for (i = 1; i < trace_num; i++)
          {
             strcat(bt, messages[i]);
             strcat(bt, "\n");
          }
        free(messages);
     }
   /* if NULL obj then dump stacktrace */
   if (!obj)
     {
        snprintf(buf, sizeof(buf),
                 "Object is NULL.\n"
                 "%s",
                 bt);
     }
   /* if obj pointer is non NULL, actually try an access and see if we segv */
   else if (obj)
     {
        struct sigaction act, oact;
        int magic = 0, segv = 0;

        /* setup segv handler */
        act.sa_handler = _e_object_segv;
        act.sa_flags = SA_RESETHAND;
        sigemptyset(&act.sa_mask);
        sigaction(SIGSEGV, &act, &oact);
        /* set a longjump to be within this if statement. only called if we */
        /* segfault */
        if (sigsetjmp(_e_object_segv_buf, 1))
          {
             sigaction(SIGSEGV, &oact, NULL);
             segv = 1;
          }
        else
          {
             /* try access magic value */
             magic = obj->magic;
             /* if pointer is bogus we'd segv and so jump to the if () above */
             /* contents, and thus reset segv handler and set segv flag. */
             /* if not we just continue moving along here and reset handler */
             sigaction(SIGSEGV, &oact, NULL);
          }
        /* if we segfaulted above... */
        if (segv)
          snprintf(buf, sizeof(buf),
                   "Object [%p] is an invalid/garbage pointer.\n"
                   "Segfault successfully avoided.\n"
                   "%s",
                   obj,
                   bt);
        else
          {
             /* valid ram then... if we freed this object before */
             if (magic == E_OBJECT_MAGIC_FREED)
               snprintf(buf, sizeof(buf),
                        "Object [%p] is already freed.\n"
                        "%s",
                        obj,
                        bt);
             /* garbage magic value - pointer to non object */
             else if (magic != E_OBJECT_MAGIC)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has garbage magic (%x).\n"
                        "%s",
                        obj, magic,
                        bt);
             else if (obj->references < 0)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has negative references (%i).\n"
                        "%s",
                        obj, obj->references,
                        bt);
             else if (obj->references > 100)
               snprintf(buf, sizeof(buf),
                        "Object [%p] has unusually high reference count (%i).\n"
                        "%s",
                        obj, obj->references,
                        bt);
             /* it's all ok! */
             else
               {
                  return 0;
               }
          }
     }
   /* display actual error message */
   e_error_message_show("%s", buf);
   return 1;
#else
   if (!obj)
     {
        fprintf(stderr, "ERROR: Object is NULL. Add break point at %s:%d\n",
                __FILE__, __LINE__);
        return 1;
     }
   return 0;
#endif
}

E_API void
e_object_data_set(E_Object *obj, const void *data)
{
   E_OBJECT_CHECK(obj);
   obj->data = (void *)data;
}

E_API void *
e_object_data_get(E_Object *obj)
{
   E_OBJECT_CHECK_RETURN(obj, NULL);
   return obj->data;
}

E_API void
e_object_free_attach_func_set(E_Object *obj, E_Object_Cleanup_Func func)
{
   E_OBJECT_CHECK(obj);
   obj->free_att_func = func;
}

E_API void
e_object_del_attach_func_set(E_Object *obj, E_Object_Cleanup_Func func)
{
   E_OBJECT_CHECK(obj);
   obj->del_att_func = func;
}

E_API void
e_object_ref_debug_set(E_Object *obj, Eina_Bool set)
{
   E_OBJECT_CHECK(obj);
   obj->ref_debug = !!set;
}

E_API void
e_object_delfn_clear(E_Object *obj)
{
   E_Object_Delfn *dfn;

   E_OBJECT_CHECK(obj);
   if (obj->walking_list)
     {
        EINA_INLIST_FOREACH(obj->del_fn_list, dfn)
          {
             dfn->delete_me = 1;
          }
        return;
     }
   while (obj->del_fn_list)
     {
        dfn = (E_Object_Delfn *)obj->del_fn_list;
        obj->del_fn_list = eina_inlist_remove(obj->del_fn_list,
                                              EINA_INLIST_GET(dfn));
        free(dfn);
     }
}

E_API E_Object_Delfn *
e_object_delfn_add(E_Object *obj, void (*func)(void *data, void *obj), void *data)
{
   E_Object_Delfn *dfn;
   E_OBJECT_CHECK_RETURN(obj, NULL);
   dfn = calloc(1, sizeof(E_Object_Delfn));
   if (!dfn) return NULL;
   dfn->func = func;
   dfn->data = data;
   obj->del_fn_list = eina_inlist_append(obj->del_fn_list, EINA_INLIST_GET(dfn));
   return dfn;
}

E_API void
e_object_delfn_del(E_Object *obj, E_Object_Delfn *dfn)
{
   E_OBJECT_CHECK(obj);
   if (obj->walking_list)
     {
        dfn->delete_me = 1;
        return;
     }
   obj->del_fn_list = eina_inlist_remove(obj->del_fn_list, EINA_INLIST_GET(dfn));
   free(dfn);
}

/*
   void
   e_object_breadcrumb_add(E_Object *obj, char *crumb)
   {
   E_OBJECT_CHECK(obj);
   obj->crumbs = eina_list_append(obj->crumbs, strdup(crumb));
   }

   void
   e_object_breadcrumb_del(E_Object *obj, char *crumb)
   {
   Eina_List *l;
   char *key;

   E_OBJECT_CHECK(obj);
   EINA_LIST_FOREACH(obj->crumbs, l, key)
     {
        if (!strcmp(crumb, key))
          {
             free(key);
             obj->crumbs = eina_list_remove_list(obj->crumbs, l);
             return;
          }
     }
   }

   void
   e_object_breadcrumb_debug(E_Object *obj)
   {
   Eina_List *l;
   char *key;

   E_OBJECT_CHECK(obj);
   EINA_LISt_FOREACH(obj->crumbs, l, key)
     printf("CRUMB: %s\n", key);
   }
 */

#ifdef OBJECT_PARANOIA_CHECK
/* local subsystem functions */
static void
_e_object_segv(int sig)
{
   siglongjmp(_e_object_segv_buf, 1);
}

#endif
#include "e.h"

/* local subsystem functions */
static void      _e_order_free(E_Order *eo);
static void      _e_order_cb_monitor(void *data, Ecore_File_Monitor *em, Ecore_File_Event event, const char *path);
static void      _e_order_read(E_Order *eo);
static void      _e_order_save(E_Order *eo);
static void      _e_order_remove_if_exists(E_Order *eo, Efreet_Desktop *desktop);
static Eina_Bool _e_order_cb_efreet_cache_update(void *data, int ev_type, void *ev);

static Eina_List *orders = NULL;
static Eina_List *handlers = NULL;

/* externally accessible functions */
EINTERN int
e_order_init(void)
{
   char *menu_file = NULL;

   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_UPDATE,
                         _e_order_cb_efreet_cache_update, NULL);
   E_LIST_HANDLER_APPEND(handlers, EFREET_EVENT_DESKTOP_CACHE_BUILD,
                         _e_order_cb_efreet_cache_update, NULL);
   if (e_config->default_system_menu)
     menu_file = strdup(e_config->default_system_menu);
   if (!menu_file)
     menu_file = strdup("/etc/xdg/menus/applications.menu");
   if (menu_file)
     {
        if (!ecore_file_exists(menu_file))
          {
             char buf[PATH_MAX];

             E_FREE(menu_file);
             snprintf(buf, sizeof(buf), "/etc/xdg/menus/e-applications.menu");
             if (ecore_file_exists(buf)) menu_file = strdup(buf);
             else
               {
                  snprintf(buf, sizeof(buf),
                           "%s/etc/xdg/menus/e-applications.menu",
                           e_prefix_get());
                  if (ecore_file_exists(buf)) menu_file = strdup(buf);
               }
          }
     }
   efreet_menu_file_set(menu_file);
   free(menu_file);
   return 1;
}

EINTERN int
e_order_shutdown(void)
{
   orders = eina_list_free(orders);

   E_FREE_LIST(handlers, ecore_event_handler_del);
   return 1;
}

E_API E_Order *
e_order_new(const char *path)
{
   E_Order *eo;

   if (!path)
     {
        ERR("null order");
        return NULL;
     }

   eo = E_OBJECT_ALLOC(E_Order, E_ORDER_TYPE, _e_order_free);
   if (!eo) return NULL;

   if (path[0] != '/')
     {
        ERR("not full");
     }
   if (path) eo->path = eina_stringshare_add(path);
   _e_order_read(eo);
   eo->monitor = ecore_file_monitor_add(path, _e_order_cb_monitor, eo);

   orders = eina_list_append(orders, eo);

   return eo;
}

E_API E_Order *
e_order_clone(const E_Order *eo)
{
   E_Order *eoc;
   Eina_List *l;
   Efreet_Desktop *desktop;

   E_OBJECT_CHECK_RETURN(eo, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(eo, E_ORDER_TYPE, NULL);

   eoc = E_OBJECT_ALLOC(E_Order, E_ORDER_TYPE, _e_order_free);
   if (!eoc) return NULL;

   eoc->path = eina_stringshare_ref(eo->path);
   EINA_LIST_FOREACH(eo->desktops, l, desktop)
     {
        efreet_desktop_ref(desktop);
        eoc->desktops = eina_list_append(eoc->desktops, desktop);
     }
   eoc->monitor = ecore_file_monitor_add(eoc->path, _e_order_cb_monitor, eoc);

   orders = eina_list_append(orders, eoc);
   return eoc;
}

E_API void
e_order_update_callback_set(E_Order *eo, void (*cb)(void *data, E_Order *eo), void *data)
{
   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   eo->cb.update = cb;
   eo->cb.data = data;
}

E_API void
e_order_remove(E_Order *eo, Efreet_Desktop *desktop)
{
   Eina_List *tmp;

   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   tmp = eina_list_data_find_list(eo->desktops, desktop);
   if (!tmp) return;
   efreet_desktop_free(eina_list_data_get(tmp));
   eo->desktops = eina_list_remove_list(eo->desktops, tmp);
   _e_order_save(eo);
}

E_API void
e_order_append(E_Order *eo, Efreet_Desktop *desktop)
{
   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   _e_order_remove_if_exists(eo, desktop);
   efreet_desktop_ref(desktop);
   eo->desktops = eina_list_append(eo->desktops, desktop);
   _e_order_save(eo);
}

E_API void
e_order_prepend_relative(E_Order *eo, Efreet_Desktop *desktop, Efreet_Desktop *before)
{
   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   _e_order_remove_if_exists(eo, desktop);
   efreet_desktop_ref(desktop);
   eo->desktops = eina_list_prepend_relative(eo->desktops, desktop, before);
   _e_order_save(eo);
}

E_API void
e_order_files_append(E_Order *eo, Eina_List *files)
{
   Eina_List *l;
   const char *file;

   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   EINA_LIST_FOREACH(files, l, file)
     {
        Efreet_Desktop *desktop;

        if (!strncmp(file, "file://", 7))
          file += 7;
        desktop = efreet_desktop_get(file);
        if (!desktop) continue;
        _e_order_remove_if_exists(eo, desktop);
        eo->desktops = eina_list_append(eo->desktops, desktop);
     }
   _e_order_save(eo);
}

E_API void
e_order_files_prepend_relative(E_Order *eo, Eina_List *files, Efreet_Desktop *before)
{
   Eina_List *l;
   const char *file;

   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   EINA_LIST_FOREACH(files, l, file)
     {
        Efreet_Desktop *desktop;

        if (!strncmp(file, "file://", 7))
          file += 7;
        desktop = efreet_desktop_get(file);
        if (!desktop) continue;
        _e_order_remove_if_exists(eo, desktop);
        eo->desktops = eina_list_prepend_relative(eo->desktops, desktop, before);
     }
   _e_order_save(eo);
}

E_API void
e_order_clear(E_Order *eo)
{
   E_OBJECT_CHECK(eo);
   E_OBJECT_TYPE_CHECK(eo, E_ORDER_TYPE);

   E_FREE_LIST(eo->desktops, efreet_desktop_free);
   _e_order_save(eo);
}

/* local subsystem functions */
static void
_e_order_free(E_Order *eo)
{
   if (eo->delay) ecore_timer_del(eo->delay);
   E_FREE_LIST(eo->desktops, efreet_desktop_free);
   if (eo->path) eina_stringshare_del(eo->path);
   if (eo->monitor) ecore_file_monitor_del(eo->monitor);
   orders = eina_list_remove(orders, eo);
   free(eo);
}

static void
_e_order_remove_if_exists(E_Order *eo, Efreet_Desktop *desktop)
{
   Efreet_Desktop *desk;
   Eina_List *node, *nnode;

   EINA_LIST_FOREACH_SAFE(eo->desktops, node, nnode, desk)
     {
        if (!strcmp(desk->orig_path, desktop->orig_path))
          {
             efreet_desktop_free(desktop);
             eo->desktops = eina_list_remove(eo->desktops, desktop);
             return;
          }
     }
}

static Eina_Bool
_e_order_cb_monitor_delay(void *data)
{
   E_Order *eo = data;

   /* It doesn't really matter what the change is, just re-read the file */
   _e_order_read(eo);
   if (eo->cb.update) eo->cb.update(eo->cb.data, eo);
   eo->delay = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_order_cb_monitor(void *data, Ecore_File_Monitor *em EINA_UNUSED, Ecore_File_Event event EINA_UNUSED, const char *path EINA_UNUSED)
{
   E_Order *eo = data;

   if (eo->delay) ecore_timer_del(eo->delay);
   eo->delay = ecore_timer_loop_add(0.2, _e_order_cb_monitor_delay, eo);
}

static void
_e_order_read(E_Order *eo)
{
   char *dir, *s, *s2, buf[4096], buf2[8192];
   Efreet_Desktop *desktop;
   Eina_List *files, *l;
   size_t len;
   FILE *f;

   E_FREE_LIST(eo->desktops, efreet_desktop_free);
   if (!eo->path) return;

   // eo->path = /path/to/dir/.order ...
   dir = ecore_file_dir_get(eo->path);
   if (!dir) return;

   files = ecore_file_ls(dir);

   f = fopen(eo->path, "rb");
   if (!f) goto err;

   while (fgets(buf, sizeof(buf), f))
     {
        len = strlen(buf);
        if (len == 0) continue;
        if (buf[len - 1] == '\n')
          {
             buf[len - 1] = 0;
             len--;
          }
        if (len == 0) continue;
        // if full path - use that first
        desktop = NULL;
        if (buf[0] == '/')
          desktop = efreet_desktop_get(buf);
        else
          {
             // /path/to/dir/filename.desktop (buf)
             snprintf(buf2, sizeof(buf2), "%s/%s", dir, buf);
             // remove file if its in the dir already
             EINA_LIST_FOREACH(files, l, s)
               {
                  if ((!strcmp(s, buf)) || (!strcmp(s, buf2)))
                    {
                       files = eina_list_remove_list(files, l);
                       free(s);
                       break;
                    }
               }
             // desktop file in the order dir first
             if (!desktop)
               desktop = efreet_desktop_get(buf2);
             // ignore any path elements and look up just filename
             if (!desktop)
               desktop = efreet_desktop_get(ecore_file_file_get(buf));
             // look uop by id
             if (!desktop)
               desktop = efreet_util_desktop_file_id_find(ecore_file_file_get(buf));
          }
        // if we found it - append.
        if (desktop)
          eo->desktops = eina_list_append(eo->desktops, desktop);
     }
   fclose(f);
err:
   EINA_LIST_FOREACH(files, l, s)
     {
        if (s[0] == '.') continue;
        s2 = strchr(s, '.');
        if (!s2) continue;
        if (!(!strcasecmp(s2, ".desktop"))) continue;
        snprintf(buf2, sizeof(buf2), "%s/%s", dir, s);
        desktop = efreet_desktop_get(buf2);
        if (desktop)
          eo->desktops = eina_list_append(eo->desktops, desktop);
     }
   free(dir);
   EINA_LIST_FREE(files, s) free(s);
}

static void
_e_order_save(E_Order *eo)
{
   FILE *f;
   Eina_List *l;
   Efreet_Desktop *desktop;

   if (!eo->path) return;
   f = fopen(eo->path, "wb");
   if (!f) return;

   EINA_LIST_FOREACH(eo->desktops, l, desktop)
     {
        const char *id;

        id = efreet_util_path_to_file_id(desktop->orig_path);
        if (id)
          fprintf(f, "%s\n", id);
        else
          fprintf(f, "%s\n", desktop->orig_path);
     }

   fclose(f);
}

static Eina_Bool
_e_order_cb_efreet_cache_update(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev EINA_UNUSED)
{
   Eina_List *l;
   E_Order *eo;

   /* reread all .order files */
   EINA_LIST_FOREACH(orders, l, eo)
     {
        _e_order_read(eo);
        if (eo->cb.update) eo->cb.update(eo->cb.data, eo);
     }
   return ECORE_CALLBACK_PASS_ON;
}

#include "e.h"

#define SMART_NAME     "e_pan"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;

struct _E_Smart_Data
{
   Evas_Object *smart_obj, *child_obj;
   Evas_Coord   x, y, w, h;
   Evas_Coord   child_w, child_h, px, py;
};

/* local subsystem functions */
static void _e_smart_child_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_smart_child_resize_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _e_smart_reconfigure(E_Smart_Data *sd);
static void _e_smart_add(Evas_Object *obj);
static void _e_smart_del(Evas_Object *obj);
static void _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _e_smart_show(Evas_Object *obj);
static void _e_smart_hide(Evas_Object *obj);
static void _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void _e_smart_clip_unset(Evas_Object *obj);
static void _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_pan_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_pan_child_set(Evas_Object *obj, Evas_Object *child)
{
   API_ENTRY return;
   if (child == sd->child_obj) return;
   if (sd->child_obj)
     {
        evas_object_clip_unset(sd->child_obj);
        evas_object_smart_member_del(sd->child_obj);
        evas_object_event_callback_del(sd->child_obj, EVAS_CALLBACK_FREE, _e_smart_child_del_hook);
        evas_object_event_callback_del(sd->child_obj, EVAS_CALLBACK_RESIZE, _e_smart_child_resize_hook);
        sd->child_obj = NULL;
     }
   if (child)
     {
        int r, g, b, a;

        sd->child_obj = child;
        evas_object_smart_member_add(sd->child_obj, sd->smart_obj);
        evas_object_geometry_get(sd->child_obj, NULL, NULL,
                                 &sd->child_w, &sd->child_h);
        evas_object_event_callback_add(child, EVAS_CALLBACK_FREE,
                                       _e_smart_child_del_hook, sd);
        evas_object_event_callback_add(child, EVAS_CALLBACK_RESIZE,
                                       _e_smart_child_resize_hook, sd);
        evas_object_color_get(sd->smart_obj, &r, &g, &b, &a);
        evas_object_color_set(sd->child_obj, r, g, b, a);
        evas_object_clip_set(sd->child_obj, evas_object_clip_get(sd->smart_obj));
        if (evas_object_visible_get(sd->smart_obj))
          evas_object_show(sd->child_obj);
        else evas_object_hide(sd->child_obj);
        _e_smart_reconfigure(sd);
     }
   evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

E_API Evas_Object *
e_pan_child_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->child_obj;
}

E_API void
e_pan_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   API_ENTRY return;
   if (x > (sd->child_w - sd->w)) x = sd->child_w - sd->w;
   if (y > (sd->child_h - sd->h)) y = sd->child_h - sd->h;
   if (x < 0) x = 0;
   if (y < 0) y = 0;
   if ((x == sd->px) && (y == sd->py)) return;
   sd->px = x;
   sd->py = y;
   _e_smart_reconfigure(sd);
   evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

E_API void
e_pan_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   API_ENTRY return;
   if (x) *x = sd->px;
   if (y) *y = sd->py;
}

E_API void
e_pan_max_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   API_ENTRY return;
   if (x)
     {
        if (sd->w < sd->child_w) *x = sd->child_w - sd->w;
        else *x = 0;
     }
   if (y)
     {
        if (sd->h < sd->child_h) *y = sd->child_h - sd->h;
        else *y = 0;
     }
}

E_API void
e_pan_child_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   API_ENTRY return;
   if (w) *w = sd->child_w;
   if (h) *h = sd->child_h;
}

/* local subsystem functions */
static void
_e_smart_child_del_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;

   sd = data;
   sd->child_obj = NULL;
   evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_child_resize_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;
   Evas_Coord w = 0, h = 0;

   sd = data;
   if (!sd->child_obj) return;
   evas_object_geometry_get(sd->child_obj, NULL, NULL, &w, &h);
   if ((w != sd->child_w) || (h != sd->child_h))
     {
        sd->child_w = w;
        sd->child_h = h;
        _e_smart_reconfigure(sd);
     }
   evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   if (sd->child_obj)
     evas_object_move(sd->child_obj, sd->x - sd->px, sd->y - sd->py);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = E_NEW(E_Smart_Data, 1);
   if (!sd) return;
   sd->smart_obj = obj;
   sd->px = 0;
   sd->py = 0;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->child_w = 0;
   sd->child_h = 0;
   evas_object_smart_data_set(obj, sd);
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   e_pan_child_set(obj, NULL);
   E_FREE(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
   evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     evas_object_show(sd->child_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     evas_object_hide(sd->child_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     evas_object_color_set(sd->child_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     evas_object_clip_set(sd->child_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     evas_object_clip_unset(sd->child_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#include "e.h"

/* local subsystem functions */
static void      _e_path_free(E_Path *ep);
static void      _e_path_cache_free(E_Path *ep);
static Eina_Bool _e_path_cache_free_cb(const Eina_Hash *hash, const void *key, void *data, void *fdata);

/* externally accessible functions */
E_API E_Path *
e_path_new(void)
{
   E_Path *ep;

   ep = E_OBJECT_ALLOC(E_Path, E_PATH_TYPE, _e_path_free);
   return ep;
}

E_API void
e_path_default_path_append(E_Path *ep, const char *path)
{
   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);
   if (!path) return;
   if (path[0] == '~')
     {
        E_Path_Dir *epd;
        char *new_path;
        const char *home_dir;
        int len1, len2;

        home_dir = e_user_homedir_get();
        len1 = strlen(home_dir);
        len2 = strlen(path);
        new_path = malloc(len1 + len2 + 1);
        if (!new_path) return;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          {
             free(new_path);
             return;
          }

        strcpy(new_path, home_dir);
        strcat(new_path, path + 1);
        epd->dir = eina_stringshare_add(new_path);
        free(new_path);
        ep->default_dir_list = eina_list_append(ep->default_dir_list, epd);
     }
   else
     {
        E_Path_Dir *epd;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          return;
        epd->dir = eina_stringshare_add(path);
        ep->default_dir_list = eina_list_append(ep->default_dir_list, epd);
     }
   _e_path_cache_free(ep);
}

E_API void
e_path_user_path_set(E_Path *ep, Eina_List **user_dir_list)
{
   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);

   ep->user_dir_list = user_dir_list;
   _e_path_cache_free(ep);
}

E_API void
e_path_user_path_append(E_Path *ep, const char *path)
{
   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);
   if (!path) return;
   if (path[0] == '~')
     {
        E_Path_Dir *epd;
        char *new_path;
        const char *home_dir;
        int len1, len2;

        home_dir = e_user_homedir_get();
        len1 = strlen(home_dir);
        len2 = strlen(path);
        new_path = malloc(len1 + len2 + 1);
        if (!new_path) return;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          {
             free(new_path);
             return;
          }

        strcpy(new_path, home_dir);
        strcat(new_path, path + 1);
        epd->dir = eina_stringshare_add(new_path);
        free(new_path);
        *(ep->user_dir_list) = eina_list_append(*(ep->user_dir_list), epd);
     }
   else
     {
        E_Path_Dir *epd;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          return;
        epd->dir = eina_stringshare_add(path);
        *(ep->user_dir_list) = eina_list_append(*(ep->user_dir_list), epd);
     }
   _e_path_cache_free(ep);
}

E_API void
e_path_user_path_prepend(E_Path *ep, const char *path)
{
   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);
   if (!path) return;
   if (path[0] == '~')
     {
        E_Path_Dir *epd;
        char *new_path;
        const char *home_dir;
        int len1, len2;

        home_dir = e_user_homedir_get();
        len1 = strlen(home_dir);
        len2 = strlen(path);
        new_path = malloc(len1 + len2 + 1);
        if (!new_path) return;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          {
             free(new_path);
             return;
          }

        strcpy(new_path, home_dir);
        strcat(new_path, path + 1);
        epd->dir = eina_stringshare_add(new_path);
        free(new_path);
        *(ep->user_dir_list) = eina_list_prepend(*(ep->user_dir_list), epd);
     }
   else
     {
        E_Path_Dir *epd;
        epd = malloc(sizeof(E_Path_Dir));
        if (!epd)
          return;
        epd->dir = eina_stringshare_add(path);
        *(ep->user_dir_list) = eina_list_prepend(*(ep->user_dir_list), epd);
     }
   _e_path_cache_free(ep);
}

E_API void
e_path_user_path_remove(E_Path *ep, const char *path)
{
   Eina_List *l;
   E_Path_Dir *epd;

   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);
   if (!path) return;
   if (path[0] == '~')
     {
        char *new_path;
        const char *home_dir;
        int len1, len2;

        home_dir = e_user_homedir_get();
        len1 = strlen(home_dir);
        len2 = strlen(path);
        new_path = malloc(len1 + len2 + 1);
        if (!new_path) return;
        strcpy(new_path, home_dir);
        strcat(new_path, path + 1);
        EINA_LIST_FOREACH(*(ep->user_dir_list), l, epd)
          {
             if (epd->dir)
               {
                  if (!strcmp(epd->dir, new_path))
                    {
                       *(ep->user_dir_list) = eina_list_remove_list(
                           *(ep->user_dir_list), l);
                       eina_stringshare_del(epd->dir);
                       free(epd);
                       free(new_path);
                       _e_path_cache_free(ep);
                       return;
                    }
               }
          }
        free(new_path);
     }
   else
     {
        EINA_LIST_FOREACH(*(ep->user_dir_list), l, epd)
          {
             if (epd->dir)
               {
                  if (!strcmp(epd->dir, path))
                    {
                       *(ep->user_dir_list) = eina_list_remove_list(
                           *(ep->user_dir_list), l);
                       eina_stringshare_del(epd->dir);
                       free(epd);
                       _e_path_cache_free(ep);
                       return;
                    }
               }
          }
     }
}

E_API void
e_path_user_path_clear(E_Path *ep)
{
   E_Path_Dir *epd;
   EINA_LIST_FREE(*(ep->user_dir_list), epd)
     {
        eina_stringshare_del(epd->dir);
        free(epd);
     }
   _e_path_cache_free(ep);
}

E_API Eina_Stringshare *
e_path_find(E_Path *ep, const char *file)
{
   Eina_List *l;
   E_Path_Dir *epd;
   char *str;
   Eina_Stringshare *ret;
   char buf[PATH_MAX] = "";

   E_OBJECT_CHECK_RETURN(ep, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(ep, E_PATH_TYPE, NULL);

   if (!file) return NULL;
   str = eina_hash_find(ep->hash, file);
   if (str) return eina_stringshare_ref(str);
   /* Look in the default dir list */
   EINA_LIST_FOREACH(ep->default_dir_list, l, epd)
     {
        if (epd->dir)
          {
             snprintf(buf, sizeof(buf), "%s/%s", epd->dir, file);
             if (ecore_file_exists(buf))
               {
                  if (!ep->hash)
                    ep->hash = eina_hash_string_superfast_new(NULL);
                  if (eina_hash_population(ep->hash) >= 512)
                    _e_path_cache_free(ep);
                  ret = eina_stringshare_add(buf);
                  eina_hash_add(ep->hash, file, ret);
                  return eina_stringshare_ref(ret);
               }
          }
     }
   /* Look in the users dir list */
   EINA_LIST_FOREACH(*(ep->user_dir_list), l, epd)
     {
        if (epd->dir)
          {
             snprintf(buf, sizeof(buf), "%s/%s", epd->dir, file);
             if (ecore_file_exists(buf))
               {
                  if (!ep->hash)
                    ep->hash = eina_hash_string_superfast_new(NULL);
                  if (eina_hash_population(ep->hash) >= 512)
                    _e_path_cache_free(ep);
                  ret = eina_stringshare_add(buf);
                  eina_hash_add(ep->hash, file, ret);
                  return eina_stringshare_ref(ret);
               }
          }
     }
   return NULL;
}

E_API void
e_path_evas_append(E_Path *ep, Evas *evas)
{
   Eina_List *dir_list;
   E_Path_Dir *epd;

   E_OBJECT_CHECK(ep);
   E_OBJECT_TYPE_CHECK(ep, E_PATH_TYPE);
   if (!evas) return;

   dir_list = e_path_dir_list_get(ep);

   EINA_LIST_FREE(dir_list, epd)
     {
        if (epd->dir) evas_font_path_append(evas, epd->dir);
        eina_stringshare_del(epd->dir);
        free(epd);
     }
}

/* combine default_list and and user_list in and easy to use list */
E_API Eina_List *
e_path_dir_list_get(E_Path *ep)
{
   Eina_List *dir_list;
   Eina_List *l;
   E_Path_Dir *new_epd;
   E_Path_Dir *epd;

   dir_list = NULL;

   if (ep->user_dir_list)
     {
        EINA_LIST_FOREACH(*(ep->user_dir_list), l, epd)
          {
             new_epd = malloc(sizeof(E_Path_Dir));
             new_epd->dir = eina_stringshare_add(epd->dir);
             dir_list = eina_list_append(dir_list, new_epd);
          }
     }

   EINA_LIST_FOREACH(ep->default_dir_list, l, epd)
     {
        new_epd = malloc(sizeof(E_Path_Dir));
        new_epd->dir = eina_stringshare_add(epd->dir);
        dir_list = eina_list_append(dir_list, new_epd);
     }

   return dir_list;
}

E_API void
e_path_dir_list_free(Eina_List *dir_list)
{
   E_Path_Dir *epd;

   EINA_LIST_FREE(dir_list, epd)
     {
        eina_stringshare_del(epd->dir);
        free(epd);
     }
}

/* local subsystem functions */
static void
_e_path_free(E_Path *ep)
{
   E_Path_Dir *epd;

   _e_path_cache_free(ep);
   EINA_LIST_FREE(ep->default_dir_list, epd)
     {
        eina_stringshare_del(epd->dir);
        free(epd);
     }
   free(ep);
}

static void
_e_path_cache_free(E_Path *ep)
{
   if (!ep->hash) return;
   eina_hash_foreach(ep->hash, _e_path_cache_free_cb, NULL);
   eina_hash_free(ep->hash);
   ep->hash = NULL;
}

static Eina_Bool
_e_path_cache_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   eina_stringshare_del(data);
   return 1;
}

#include "e.h"

#ifdef HAVE_WAYLAND
# include "e_comp_wl.h"
# ifndef EGL_TEXTURE_FORMAT
#  define EGL_TEXTURE_FORMAT		0x3080
# endif
# ifndef EGL_TEXTURE_RGBA
#  define EGL_TEXTURE_RGBA		0x305E
# endif
# ifndef DRM_FORMAT_ARGB8888
#  define DRM_FORMAT_ARGB8888           0x34325241
#  define DRM_FORMAT_XRGB8888           0x34325258
# endif
#endif
#ifndef HAVE_WAYLAND_ONLY
# include "e_comp_x.h"
#endif

#include <sys/mman.h>

static Eina_Hash *pixmaps[2] = {NULL};
static Eina_Hash *aliases[2] = {NULL};

struct _E_Pixmap
{
   unsigned int refcount;
   unsigned int failures;

   E_Client *client;
   E_Pixmap_Type type;

   int64_t win;
   int64_t alias;
   Ecore_Window parent;

   int w, h;

#ifndef HAVE_WAYLAND_ONLY
   void *image;
   void *visual;
   int ibpp, ibpl;
   unsigned int cmap;
   uint32_t pixmap;
   Eina_List *images_cache;
#endif

#ifdef HAVE_WAYLAND
   E_Comp_Wl_Buffer *buffer;
   E_Comp_Wl_Buffer *held_buffer;
   struct wl_listener buffer_destroy_listener;
   struct wl_listener held_buffer_destroy_listener;
   void *data;
   Eina_Rectangle opaque;
   Eina_List *free_buffers;
   Eina_List *busy_list;
#endif

   Eina_Bool usable E_BITFIELD;
   Eina_Bool dirty E_BITFIELD;
   Eina_Bool image_argb E_BITFIELD;
};

#ifdef HAVE_WAYLAND

static void
_e_pixmap_cb_deferred_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Comp_Wl_Buffer *buffer;

   buffer = container_of(listener, E_Comp_Wl_Buffer, deferred_destroy_listener);
   buffer->discarding_pixmap->free_buffers =
     eina_list_remove(buffer->discarding_pixmap->free_buffers, buffer);
   buffer->discarding_pixmap = NULL;
}

static void
_e_pixmap_cb_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Pixmap *cp;

   cp = container_of(listener, E_Pixmap, buffer_destroy_listener);
   cp->buffer = NULL;
   cp->buffer_destroy_listener.notify = NULL;
}

static void
_e_pixmap_cb_held_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   E_Pixmap *cp;

   cp = container_of(listener, E_Pixmap, held_buffer_destroy_listener);
   cp->held_buffer = NULL;
   cp->held_buffer_destroy_listener.notify = NULL;
}
#endif

static void
_e_pixmap_clear(E_Pixmap *cp, Eina_Bool cache)
{
   cp->w = cp->h = 0;
   cp->image_argb = EINA_FALSE;
   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (cp->pixmap)
          {
             ecore_x_pixmap_free(cp->pixmap);
             cp->pixmap = 0;
             ecore_x_e_comp_pixmap_set(cp->parent ?: (Ecore_X_Window)cp->win, 0);
             e_pixmap_image_clear(cp, cache);
          }
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        e_pixmap_image_clear(cp, cache);
#endif
        break;
      default:
        break;
     }
}

#ifndef HAVE_WAYLAND_ONLY
static void
_e_pixmap_image_clear_x(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Eina_List *list = data;
   E_FREE_LIST(list, ecore_x_image_free);
}
#endif

#ifdef HAVE_WAYLAND
static void
_e_pixmap_wl_resource_release(E_Comp_Wl_Buffer *buffer)
{
   buffer->busy--;
   if (buffer->dmabuf_buffer)
     linux_dmabuf_buffer_unref(buffer->dmabuf_buffer);
   if (buffer->busy) return;

   if (buffer->pool)
     {
        wl_shm_pool_unref(buffer->pool);
        buffer->pool = NULL;
     }

   if (buffer->destroyed)
     {
        free(buffer);
        return;
     }

   wl_buffer_send_release(buffer->resource);
}

static void
_e_pixmap_wayland_buffer_release(E_Pixmap *cp, E_Comp_Wl_Buffer *buffer)
{
   if (!buffer) return;

   if (e_comp->rendering)
     {
        if (buffer->discarding_pixmap) return;

        buffer->discarding_pixmap = cp;
        buffer->deferred_destroy_listener.notify =
          _e_pixmap_cb_deferred_buffer_destroy;
        wl_signal_add(&buffer->destroy_signal,
                      &buffer->deferred_destroy_listener);
        cp->free_buffers = eina_list_append(cp->free_buffers, buffer);
        return;
     }

   if (buffer->busy == 1)
     {
        // we are at busy 1 abut about to go to 0 below, so remove now
        cp->busy_list = eina_list_remove(cp->busy_list, buffer);
     }
   _e_pixmap_wl_resource_release(buffer);
}

static void
_e_pixmap_wl_buffers_free(E_Pixmap *cp)
{
   E_Comp_Wl_Buffer *b;

   EINA_LIST_FREE(cp->free_buffers, b)
     {
        wl_list_remove(&b->deferred_destroy_listener.link);
        b->deferred_destroy_listener.notify = NULL;
        _e_pixmap_wayland_buffer_release(cp, b);
        b->discarding_pixmap = NULL;
     }
}

static void
_e_pixmap_wayland_image_clear(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);

   if (cp->busy_list)
     {
        E_Comp_Wl_Buffer *buffer;

        EINA_LIST_FREE(cp->busy_list, buffer)
          {
             buffer->busy--;
             if (buffer->dmabuf_buffer)
               linux_dmabuf_buffer_unref(buffer->dmabuf_buffer);
          }
     }

   if (!cp->held_buffer) return;
   if (!cp->held_buffer->pool) return;

   _e_pixmap_wayland_buffer_release(cp, cp->held_buffer);
   if (cp->held_buffer_destroy_listener.notify)
     {
        wl_list_remove(&cp->held_buffer_destroy_listener.link);
        cp->held_buffer_destroy_listener.notify = NULL;
     }

   cp->data = NULL;
   cp->held_buffer = NULL;
}
#endif

static void
_e_pixmap_free(E_Pixmap *cp)
{
   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (!cp->images_cache) break;
        if (cp->client)
          evas_object_event_callback_add(cp->client->frame,
                                         EVAS_CALLBACK_FREE,
                                         _e_pixmap_image_clear_x,
                                         cp->images_cache);
        else
          {
             void *i;

             EINA_LIST_FREE(cp->images_cache, i)
               ecore_job_add((Ecore_Cb)ecore_x_image_free, i);
          }
        cp->images_cache = NULL;
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        _e_pixmap_wayland_image_clear(cp);
        if (cp->buffer_destroy_listener.notify)
          {
             wl_list_remove(&cp->buffer_destroy_listener.link);
             cp->buffer_destroy_listener.notify = NULL;
          }
#endif
        break;
      default:
        break;
     }
   _e_pixmap_clear(cp, 1);
   free(cp);
}

static E_Pixmap *
_e_pixmap_new(E_Pixmap_Type type)
{
   E_Pixmap *cp;

   cp = E_NEW(E_Pixmap, 1);
   cp->type = type;
   cp->w = cp->h = 0;
   cp->refcount = 1;
   cp->dirty = 1;
   return cp;
}

static E_Pixmap *
_e_pixmap_find(E_Pixmap_Type type, va_list *l)
{
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Window xwin;
#endif
#ifdef HAVE_WAYLAND
   int64_t id;
#endif
   E_Pixmap *cp;

   if (!pixmaps[type]) return NULL;
   switch (type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        xwin = va_arg(*l, uint32_t);
        cp = eina_hash_find(aliases[type], &xwin);
        if (!cp) cp = eina_hash_find(pixmaps[type], &xwin);
        return cp;
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        id = va_arg(*l, int64_t);
        cp = eina_hash_find(aliases[type], &id);
        if (!cp) cp = eina_hash_find(pixmaps[type], &id);
        return cp;
#endif
        break;
      default: break;
     }
   return NULL;
}

E_API int
e_pixmap_free(E_Pixmap *cp)
{
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Window xwin = cp ? cp->win : 0;
   Ecore_X_Window alias_xwin = cp ? cp->alias : 0;
#endif
#ifdef HAVE_WAYLAND
   int64_t id = cp ? cp->win : 0;
   int64_t alias_id = cp ? cp->alias : 0;
#endif
   E_Pixmap_Type type;

   if (!cp) return 0;
   if (--cp->refcount) return cp->refcount;
   type = cp->type;
   e_pixmap_image_clear(cp, EINA_FALSE);
   switch (type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (alias_xwin) eina_hash_del(aliases[type], &alias_xwin, cp);
        eina_hash_del(pixmaps[type], &xwin, cp);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        if (alias_id) eina_hash_del(aliases[type], &alias_id, cp);
        eina_hash_del(pixmaps[type], &id, cp);
#endif
        break;
      default: break;
     }
   return 0;
}

E_API E_Pixmap *
e_pixmap_ref(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, NULL);
   cp->refcount++;
   return cp;
}

E_API E_Pixmap *
e_pixmap_new(E_Pixmap_Type type, ...)
{
   E_Pixmap *cp = NULL;
   va_list l;
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Window xwin;
#endif
#ifdef HAVE_WAYLAND
   int64_t id;
#endif

   EINA_SAFETY_ON_TRUE_RETURN_VAL((type != E_PIXMAP_TYPE_WL) &&
                                  (type != E_PIXMAP_TYPE_X), NULL);
   va_start(l, type);
   switch (type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        xwin = va_arg(l, uint32_t);
        if (pixmaps[type])
          {
             cp = eina_hash_find(pixmaps[type], &xwin);
             if (cp)
               {
                  cp->refcount++;
                  break;
               }
          }
        else
          pixmaps[type] = eina_hash_int32_new((Eina_Free_Cb)_e_pixmap_free);
        cp = _e_pixmap_new(type);
        cp->win = xwin;
        eina_hash_add(pixmaps[type], &xwin, cp);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        id = va_arg(l, int64_t);
        if (pixmaps[type])
          {
             cp = eina_hash_find(pixmaps[type], &id);
             if (cp)
               {
                  cp->refcount++;
                  break;
               }
          }
        else
          {
             pixmaps[type] = eina_hash_int64_new((Eina_Free_Cb)_e_pixmap_free);
          }
        cp = _e_pixmap_new(type);
        cp->win = id;
        eina_hash_add(pixmaps[type], &id, cp);
#endif
        break;
      default: break;
     }
   va_end(l);
   return cp;
}

E_API E_Pixmap_Type
e_pixmap_type_get(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, 9999);
   return cp->type;
}

E_API void
e_pixmap_parent_window_set(E_Pixmap *cp, Ecore_Window win)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   if (cp->parent == win) return;

   e_pixmap_usable_set(cp, 0);
   e_pixmap_clear(cp);

   cp->parent = win;
}

E_API void
e_pixmap_visual_cmap_set(E_Pixmap *cp, void *visual, unsigned int cmap)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   if (cp->type != E_PIXMAP_TYPE_X) return;
#ifndef HAVE_WAYLAND_ONLY
   cp->visual = visual;
   cp->cmap = cmap;
#else
   (void) visual;
   (void) cmap;
#endif
}

E_API void *
e_pixmap_visual_get(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, NULL);
   if (cp->type != E_PIXMAP_TYPE_X) return NULL;
#ifndef HAVE_WAYLAND_ONLY
   return cp->visual;
#endif
   return NULL;
}

E_API uint32_t
e_pixmap_pixmap_get(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, 0);
#ifndef HAVE_WAYLAND_ONLY
   if (e_pixmap_is_x(cp)) return cp->pixmap;
#endif
   return 0;
}

E_API void
e_pixmap_usable_set(E_Pixmap *cp, Eina_Bool set)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   cp->usable = !!set;
}

E_API Eina_Bool
e_pixmap_usable_get(const E_Pixmap *cp)
{
   if (!cp) return EINA_FALSE;
//   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);
   return cp->usable;
}

E_API Eina_Bool
e_pixmap_dirty_get(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);
   return cp->dirty;
}

E_API void
e_pixmap_clear(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   _e_pixmap_clear(cp, 0);
   cp->dirty = EINA_TRUE;
}

E_API void
e_pixmap_dirty(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   cp->dirty = 1;
}

E_API Eina_Bool
e_pixmap_refresh(E_Pixmap *cp)
{
   Eina_Bool success = EINA_FALSE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

   if (!cp->dirty) return EINA_TRUE;
   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        {
           uint32_t pixmap;
           int pw, ph;
           E_Comp_X_Client_Data *cd = NULL;

           if (!cp->usable)
             {
                cp->failures++;
                return EINA_FALSE;
             }
           pixmap =
             ecore_x_composite_name_window_pixmap_get(cp->parent ?:
                                                      (Ecore_X_Window)cp->win);
           if (cp->client)
             {
                cd = (E_Comp_X_Client_Data*)cp->client->comp_data;
             }
           success = !!pixmap;
           if (!success) break;
           if (cd && cd->pw && cd->ph)
             {
                pw = cd->pw;
                ph = cd->ph;
             }
           else
             ecore_x_pixmap_geometry_get(pixmap, NULL, NULL, &pw, &ph);
           success = (pw > 0) && (ph > 0);
           if (success)
             {
                if ((pw != cp->w) || (ph != cp->h))
                  {
                     ecore_x_pixmap_free(cp->pixmap);
                     cp->pixmap = pixmap;
                     cp->w = pw, cp->h = ph;
                     ecore_x_e_comp_pixmap_set(cp->parent ?:
                                               (Ecore_X_Window)cp->win,
                                               cp->pixmap);
                     e_pixmap_image_clear(cp, 0);
                  }
                else
                  ecore_x_pixmap_free(pixmap);
             }
           else
             ecore_x_pixmap_free(pixmap);
        }
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        {
           E_Comp_Wl_Buffer *buffer = cp->buffer;
           int format;

           cp->w = cp->h = 0;
           cp->image_argb = EINA_FALSE;

           if (!buffer) return EINA_FALSE;

           cp->w = buffer->w;
           cp->h = buffer->h;

           if (buffer->shm_buffer)
             format = wl_shm_buffer_get_format(buffer->shm_buffer);
           else if (buffer->dmabuf_buffer)
             format = buffer->dmabuf_buffer->attributes.format;
           else if (e_comp_wl->wl.glapi)
             {
                e_comp_wl->wl.glapi->evasglQueryWaylandBuffer
                  (e_comp_wl->wl.gl, buffer->resource, EGL_TEXTURE_FORMAT,
                      &format);
             }
           else return EINA_FALSE;

           switch (format)
             {
              case DRM_FORMAT_ARGB8888:
              case WL_SHM_FORMAT_ARGB8888:
              case EGL_TEXTURE_RGBA:
                cp->image_argb = EINA_TRUE;
                break;
              default:
                cp->image_argb = EINA_FALSE;
                break;
             }

           success = ((cp->w > 0) && (cp->h > 0));
        }
#endif
        break;
      default:
        break;
     }

   if (success)
     {
        cp->dirty = 0;
        cp->failures = 0;
     }
   else
     cp->failures++;
   return success;
}

E_API Eina_Bool
e_pixmap_size_changed(E_Pixmap *cp, int w, int h)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);
   if (cp->dirty) return EINA_TRUE;
   return (w != cp->w) || (h != cp->h);
}

E_API Eina_Bool
e_pixmap_size_get(E_Pixmap *cp, int *w, int *h)
{
   if (w) *w = 0;
   if (h) *h = 0;
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);
   if (w) *w = cp->w;
   if (h) *h = cp->h;
   return (cp->w > 0) && (cp->h > 0);
}

E_API unsigned int
e_pixmap_failures_get(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, 0);
   return cp->failures;
}

E_API void
e_pixmap_client_set(E_Pixmap *cp, E_Client *ec)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
   if (cp->client && ec) CRI("ACK!");
   cp->client = ec;
}

E_API E_Client *
e_pixmap_client_get(E_Pixmap *cp)
{
   if (!cp) return NULL;
   return cp->client;
}

E_API E_Pixmap *
e_pixmap_find(E_Pixmap_Type type, ...)
{
   va_list l;
   E_Pixmap *cp;

   va_start(l, type);
   cp = _e_pixmap_find(type, &l);
   va_end(l);
   return cp;
}

E_API E_Client *
e_pixmap_find_client(E_Pixmap_Type type, ...)
{
   va_list l;
   E_Pixmap *cp;

   va_start(l, type);
   cp = _e_pixmap_find(type, &l);
   va_end(l);
   return (!cp) ? NULL : cp->client;
}

E_API int64_t
e_pixmap_window_get(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, 0);
   return cp->win;
}

E_API void *
e_pixmap_resource_get(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, NULL);
   if (cp->type != E_PIXMAP_TYPE_WL)
     {
        CRI("ACK");
        return NULL;
     }
#ifdef HAVE_WAYLAND
   return cp->buffer;
#endif
   return NULL;
}

E_API void
e_pixmap_resource_set(E_Pixmap *cp, void *resource)
{
   if ((!cp) || (cp->type != E_PIXMAP_TYPE_WL)) return;
#ifdef HAVE_WAYLAND
   if (cp->buffer == resource) return;

   if (cp->buffer)
     {
        if (resource)
          {
        if (cp->buffer->busy == 1)
          {
             // about to go to busy 0, so remove from list
             cp->busy_list = eina_list_remove(cp->busy_list, cp->buffer);
          }
        _e_pixmap_wl_resource_release(cp->buffer);
          }
     }

   if (cp->buffer_destroy_listener.notify)
     {
        wl_list_remove(&cp->buffer_destroy_listener.link);
        cp->buffer_destroy_listener.notify = NULL;
     }
   cp->buffer = resource;
   if (!cp->buffer) return;
   cp->buffer_destroy_listener.notify = _e_pixmap_cb_buffer_destroy;
   wl_signal_add(&cp->buffer->destroy_signal,
                 &cp->buffer_destroy_listener);

   if (cp->buffer->dmabuf_buffer)
     linux_dmabuf_buffer_ref(cp->buffer->dmabuf_buffer);
   cp->buffer->busy++;
   if (cp->buffer->busy > 0)
     {
        cp->busy_list = eina_list_remove(cp->busy_list, cp->buffer);
        cp->busy_list = eina_list_append(cp->busy_list, cp->buffer);
     }
#else
   (void)resource;
#endif
}

E_API Ecore_Window
e_pixmap_parent_window_get(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, 0);
   return cp->parent;
}

E_API Eina_Bool
e_pixmap_is_pixels(E_Pixmap *cp)
{
   switch (cp->type)
     {
        case E_PIXMAP_TYPE_X:
          return EINA_FALSE;
#ifdef HAVE_WAYLAND
        case E_PIXMAP_TYPE_WL:
          if (!cp->buffer) return EINA_TRUE;
          if (cp->buffer->shm_buffer) return EINA_TRUE;
          return EINA_FALSE;
#endif
        default:
          return EINA_TRUE;
     }
}

#ifdef HAVE_WAYLAND
static void
_e_pixmap_scanout_handler(void *data, Evas_Native_Surface_Status status)
{
   E_Comp_Wl_Buffer *buffer = data;

   printf("EWL: %s, Status: %d\n", __FUNCTION__, status);

   switch (status)
     {
      case EVAS_NATIVE_SURFACE_STATUS_SCANOUT_ON:
        if (buffer->dmabuf_buffer)
          linux_dmabuf_buffer_ref(buffer->dmabuf_buffer);
        buffer->busy++;
        // XXX: fixme - handle cp->busy_list
        break;
      case EVAS_NATIVE_SURFACE_STATUS_SCANOUT_OFF:
        // busy--
        // XXX: fixme - handle cp->busy_list
        _e_pixmap_wl_resource_release(buffer);
        break;
      case EVAS_NATIVE_SURFACE_STATUS_PLANE_ASSIGN:
        if (buffer->dmabuf_buffer)
          linux_dmabuf_buffer_ref(buffer->dmabuf_buffer);
        // XXX: fixme - handle cp->busy_list
        buffer->busy++;
        break;
      case EVAS_NATIVE_SURFACE_STATUS_PLANE_RELEASE:
        // busy--
        // XXX: fixme - handle cp->busy_list
        _e_pixmap_wl_resource_release(buffer);
        break;
     }
}
#endif

E_API Eina_Bool
e_pixmap_native_surface_init(E_Pixmap *cp, Evas_Native_Surface *ns)
{
   Eina_Bool ret = EINA_FALSE;

   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);
   EINA_SAFETY_ON_NULL_RETURN_VAL(ns, EINA_FALSE);

   /* This structure is stack automatic in the caller, so it's all
    * uninitialized. Clear it to 0 so we don't have uninit data for
    * variables only present in a newer version of native surface
    * than this code was written for.
    *
    * The other option would be to set ns->version to whatever version
    * this code was actually written against, but I've been told all
    * native surface users are expected to set ns->version to the
    * version provided in the headers (EVAS_NATIVE_SURFACE_VERSION)
    */
   memset(ns, 0, sizeof(*ns));
   ns->version = EVAS_NATIVE_SURFACE_VERSION;
   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        ns->type = EVAS_NATIVE_SURFACE_X11;
        ns->data.x11.visual = cp->visual;
        ns->data.x11.pixmap = cp->pixmap;
        ret = EINA_TRUE;
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        if (!cp->buffer) return EINA_FALSE;
        if (cp->buffer->dmabuf_buffer)
          {
             static signed char use_hw_planes = -1;
             ns->type = EVAS_NATIVE_SURFACE_WL_DMABUF;

             ns->data.wl_dmabuf.attr = &cp->buffer->dmabuf_buffer->attributes;
             ns->data.wl_dmabuf.resource = cp->buffer->resource;
             if (use_hw_planes == -1)
               {
                  if (getenv("E_USE_HARDWARE_PLANES")) use_hw_planes = 1;
                  else use_hw_planes = 0;
               }
             if (use_hw_planes)
               {
                  ns->data.wl_dmabuf.scanout.handler =
                    _e_pixmap_scanout_handler;
                  ns->data.wl_dmabuf.scanout.data = cp->buffer;
               }
             ret = EINA_TRUE;
          }
        else if (!cp->buffer->shm_buffer)
          {
             ns->type = EVAS_NATIVE_SURFACE_WL;
             ns->data.wl.legacy_buffer = cp->buffer->resource;
             ret = EINA_TRUE;
          }
        else ret = EINA_FALSE;
#endif
        break;
      default:
        break;
     }

   return ret;
}

E_API void
e_pixmap_image_clear(E_Pixmap *cp, Eina_Bool cache)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);

   if (!cache)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_pixmap_is_x(cp))
          if (!cp->image) return;
#endif
#ifdef HAVE_WAYLAND
        if (cp->type == E_PIXMAP_TYPE_WL)
          if (!cp->buffer) return;
#endif
     }

   cp->failures = 0;
   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (cache)
          {
             void *i;

             EINA_LIST_FREE(cp->images_cache, i)
               ecore_job_add((Ecore_Cb)ecore_x_image_free, i);
          }
        else
          {
             cp->images_cache = eina_list_append(cp->images_cache, cp->image);
             cp->image = NULL;
          }
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        _e_pixmap_wl_buffers_free(cp);
        if (cache)
          {
             E_Comp_Wl_Client_Data *cd;
             struct wl_resource *cb;
             Eina_List *free_list;
             double t;

             if ((!cp->client) || (!cp->client->comp_data)) return;

             cd = (E_Comp_Wl_Client_Data *)cp->client->comp_data;

             /* The destroy callback will remove items from the frame list
              * so we move the list to a temporary before walking it here
              */
             free_list = cd->frames;
             cd->frames = NULL;
             t = ecore_loop_time_get();
             EINA_LIST_FREE(free_list, cb)
               {
//                  printf("E: frame request: %p [%ix%i] %1.5f @ %1.5f\n", cp, cp->w, cp->h, t, ecore_time_get());
                  wl_callback_send_done(cb, t * 1000);
                  wl_resource_destroy(cb);
               }
          }
#endif
        break;
      default:
        break;
     }
}

E_API Eina_Bool
e_pixmap_image_refresh(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

#ifndef HAVE_WAYLAND_ONLY
   if (cp->image) return EINA_TRUE;
#endif

   if (cp->dirty)
     {
        CRI("BUG! PIXMAP %p IS DIRTY!", cp);
        return EINA_FALSE;
     }

   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (cp->image) return EINA_TRUE;
        if ((!cp->visual) || (!cp->client->depth)) return EINA_FALSE;
        cp->image =
          ecore_x_image_new(cp->w, cp->h, cp->visual, cp->client->depth);
        if (cp->image)
          cp->image_argb = ecore_x_image_is_argb32_get(cp->image);
        return !!cp->image;
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        {
           if (cp->held_buffer == cp->buffer) return EINA_TRUE;

           if (cp->held_buffer) _e_pixmap_wayland_image_clear(cp);

           /* This catches the case where a client (*cough* xwayland)
            * deletes a buffer we haven't released
            */
           if (!cp->buffer) return EINA_FALSE;

           if (!cp->buffer->shm_buffer) return EINA_TRUE;

           cp->held_buffer = cp->buffer;
           if (!cp->held_buffer) return EINA_TRUE;

           cp->held_buffer->pool =
             wl_shm_buffer_ref_pool(cp->held_buffer->shm_buffer);
           cp->held_buffer->busy++;
           cp->data = wl_shm_buffer_get_data(cp->buffer->shm_buffer);
           cp->held_buffer_destroy_listener.notify =
             _e_pixmap_cb_held_buffer_destroy;

           if (cp->held_buffer_destroy_listener.link.next)
             wl_list_remove(&cp->held_buffer_destroy_listener.link);
           wl_signal_add(&cp->held_buffer->destroy_signal,
                         &cp->held_buffer_destroy_listener);
           return EINA_TRUE;
        }
#endif
        break;
      default:
        break;
     }

   return EINA_FALSE;
}

E_API Eina_Bool
e_pixmap_image_exists(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

#ifndef HAVE_WAYLAND_ONLY
   if (cp->type == E_PIXMAP_TYPE_X)
     return !!cp->image;
#endif
#ifdef HAVE_WAYLAND
   return (!!cp->data) ||
     (cp->buffer && ((e_comp->gl && (!cp->buffer->shm_buffer)) ||
                     cp->buffer->dmabuf_buffer));
#endif

   return EINA_FALSE;
}

E_API Eina_Bool
e_pixmap_image_is_argb(const E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        return cp->image && cp->image_argb;
#endif
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        if (cp->usable)
          return cp->image_argb;
        /* only cursors can be override in wayland */
        if (cp->client->override) return EINA_TRUE;
#endif
        default: break;
     }
   return EINA_FALSE;
}

E_API void *
e_pixmap_image_data_get(E_Pixmap *cp)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, NULL);

   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if (cp->image)
          return ecore_x_image_data_get(cp->image, &cp->ibpl, NULL, &cp->ibpp);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        return cp->data;
#endif
        break;
      default:
        break;
     }
   return NULL;
}

E_API Eina_Bool
e_pixmap_image_data_argb_convert(E_Pixmap *cp, void *pix, void *ipix, Eina_Rectangle *r, int stride)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

   if (cp->image_argb) return EINA_TRUE;

   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
        if (cp->image_argb) return EINA_TRUE;
#ifndef HAVE_WAYLAND_ONLY
        return ecore_x_image_to_argb_convert(ipix, cp->ibpp, cp->ibpl,
                                             cp->cmap, cp->visual,
                                             r->x, r->y, r->w, r->h,
                                             pix, stride, r->x, r->y);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
        if (cp->image_argb) return EINA_TRUE;
        return EINA_FALSE;
      default:
        break;
     }
   return EINA_FALSE;
}

E_API Eina_Bool
e_pixmap_image_draw(E_Pixmap *cp, const Eina_Rectangle *r)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_FALSE);

   switch (cp->type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        if ((!cp->image) || (!cp->pixmap)) return EINA_FALSE;
        return ecore_x_image_get(cp->image, cp->pixmap, r->x, r->y, r->x, r->y, r->w, r->h);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
#endif
        (void) r;
        return EINA_TRUE;
      default:
        break;
     }
   return EINA_FALSE;
}

E_API void
e_pixmap_image_opaque_set(E_Pixmap *cp, int x, int y, int w, int h)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
#ifdef HAVE_WAYLAND
   EINA_RECTANGLE_SET(&cp->opaque, x, y, w, h);
#else
   (void)x;
   (void)y;
   (void)w;
   (void)h;
#endif
}

E_API void
e_pixmap_image_opaque_get(E_Pixmap *cp, int *x, int *y, int *w, int *h)
{
   EINA_SAFETY_ON_NULL_RETURN(cp);
#ifdef HAVE_WAYLAND
   if (x) *x = cp->opaque.x;
   if (y) *y = cp->opaque.y;
   if (w) *w = cp->opaque.w;
   if (h) *h = cp->opaque.h;
#else
   if (x) *x = 0;
   if (y) *y = 0;
   if (w) *w = 0;
   if (h) *h = 0;
#endif
}

E_API void
e_pixmap_alias(E_Pixmap *cp, E_Pixmap_Type type, ...)
{
   E_Pixmap *cp2;
   va_list l;
#ifndef HAVE_WAYLAND_ONLY
   Ecore_X_Window xwin;
#endif
#ifdef HAVE_WAYLAND
   int64_t id;
#endif

   va_start(l, type);
   switch (type)
     {
      case E_PIXMAP_TYPE_X:
#ifndef HAVE_WAYLAND_ONLY
        xwin = va_arg(l, uint32_t);
        if (!aliases[type])
          aliases[type] = eina_hash_int32_new(NULL);
        cp2 = eina_hash_find(aliases[type], &xwin);
        if ((cp2) && (!cp)) cp2->alias = 0;
        else if (cp) cp->alias = xwin;
        eina_hash_set(aliases[type], &xwin, cp);
#endif
        break;
      case E_PIXMAP_TYPE_WL:
#ifdef HAVE_WAYLAND
        id = va_arg(l, int64_t);
        if (!aliases[type])
          aliases[type] = eina_hash_int64_new(NULL);
        cp2 = eina_hash_find(aliases[type], &id);
        if ((cp2) && (!cp)) cp2->alias = 0;
        else if (cp) cp->alias = id;
        eina_hash_set(aliases[type], &id, cp);
#endif
        break;
      default: break;
     }
   va_end(l);
}

#ifdef HAVE_WAYLAND
E_API Eina_Bool
e_pixmap_dmabuf_test(struct linux_dmabuf_buffer *dmabuf)
{
   Evas_Native_Surface ns;
   Evas_Object *test;
   Eina_Bool ret;
   int size;
   void *data;

   memset(&ns, 0, sizeof(ns));

   ns.type = EVAS_NATIVE_SURFACE_WL_DMABUF;
   ns.version = EVAS_NATIVE_SURFACE_VERSION;
   ns.data.wl_dmabuf.attr = &dmabuf->attributes;
   ns.data.wl_dmabuf.resource = NULL;
   test = evas_object_image_add(e_comp->evas);
   evas_object_image_native_surface_set(test, &ns);
   ret = evas_object_image_load_error_get(test) == EVAS_LOAD_ERROR_NONE;
   evas_object_del(test);

   if (e_comp->gl || !ret)
      return ret;

   /* This is only legit for ARGB8888 */
   size = dmabuf->attributes.height * dmabuf->attributes.stride[0];
   data = mmap(NULL, size, PROT_READ, MAP_SHARED, dmabuf->attributes.fd[0], 0);
   if (data == MAP_FAILED) return EINA_FALSE;
   munmap(data, size);

   return EINA_TRUE;
}

/*
if (weston_check_egl_extension(extensions,
                               "EGL_EXT_image_dma_buf_import_modifiers")) {
   gr->query_dmabuf_formats =
   (void *) eglGetProcAddress("eglQueryDmaBufFormatsEXT");
   gr->query_dmabuf_modifiers =
   (void *) eglGetProcAddress("eglQueryDmaBufModifiersEXT");
   assert(gr->query_dmabuf_formats);
   assert(gr->query_dmabuf_modifiers);
   gr->has_dmabuf_import_modifiers = true;
}
*/

extern Eina_Bool e_comp_wl_query_dmabuf_formats(int max_formats, int *formats, int *num_formats);
extern Eina_Bool e_comp_wl_query_dmabuf_modifiers(int format, int max_modifiers, uint64_t *modifiers, Eina_Bool *external_only, int *num_modifiers);

E_API Eina_Bool
e_pixmap_dmabuf_formats_query(int **formats, int *num_formats)
{
   static const int fallback_formats[] =
     {
        DRM_FORMAT_ARGB8888,
        DRM_FORMAT_XRGB8888,
// support later ...
//      DRM_FORMAT_YUYV,
//      DRM_FORMAT_NV12,
//      DRM_FORMAT_YUV420,
//      DRM_FORMAT_YUV444,
//      DRM_FORMAT_XYUV8888,
     };
   Eina_Bool fallback = EINA_FALSE;
   int num = 0;

   if (!e_comp_wl_query_dmabuf_formats(0, NULL, &num))
     {
        num = 2;
        fallback = EINA_TRUE;
     }
   *formats = calloc(num, sizeof(int));
   if (!(*formats))
     {
        *num_formats = 0;
        return EINA_FALSE;
     }
   if (fallback)
     {
        memcpy(*formats, fallback_formats, num * sizeof(int));
        *num_formats = num;
        return EINA_TRUE;
     }
   if (!e_comp_wl_query_dmabuf_formats(num, *formats, &num))
     {
        *num_formats = 0;
        free(*formats);
        return EINA_FALSE;
     }
   *num_formats = num;
   return EINA_TRUE;
}

E_API Eina_Bool
e_pixmap_dmabuf_modifiers_query(int format, uint64_t **modifiers, int *num_modifiers)
{
   int num;

   if (!e_comp_wl_query_dmabuf_modifiers(format, 0, NULL, NULL, &num) ||
       (num == 0))
     {
        *num_modifiers = 0;
        return EINA_FALSE;
     }
   *modifiers = calloc(num, sizeof(uint64_t));
   if (!(*modifiers))
     {
        *num_modifiers = 0;
        return EINA_FALSE;
     }
   if (!e_comp_wl_query_dmabuf_modifiers
       (format, num, *modifiers, NULL, &num))
     {
        *num_modifiers = 0;
        free(*modifiers);
        return EINA_FALSE;
     }
   *num_modifiers = num;
   return EINA_TRUE;
}

#endif
#include "e.h"

E_API void
e_place_zone_region_smart_cleanup(E_Zone *zone)
{
   E_Desk *desk;
   Eina_List *clients = NULL;
   E_Client *ec;

   E_OBJECT_CHECK(zone);
   desk = e_desk_current_get(zone);
   E_CLIENT_FOREACH(ec)
     {
        /* Build a list of windows on this desktop and not iconified. */
        if ((ec->desk == desk) && (!ec->iconic) &&
            (!ec->lock_user_location) && (!e_client_util_ignored_get(ec)))
          {
             int area;
             Eina_List *ll;
             E_Client *ec2;

             /* Ordering windows largest to smallest gives better results */
             area = ec->w * ec->h;
             EINA_LIST_FOREACH(clients, ll, ec2)
               {
                  int testarea;

                  testarea = ec2->w * ec2->h;
                  /* Insert the ec if larger than the current ec */
                  if (area >= testarea)
                    {
                       clients = eina_list_prepend_relative_list(clients, ec, ll);
                       break;
                    }
               }
             /* Looped over all clients without placing, so place at end */
             if (!ll) clients = eina_list_append(clients, ec);
          }
     }

   /* Loop over the clients moving each one using the smart placement */
   EINA_LIST_FREE(clients, ec)
     {
        int new_x = zone->x, new_y = zone->y;
        Eina_List *l = eina_list_append(NULL, ec);

        e_place_zone_region_smart(zone, l, zone->x, zone->y,
                                  ec->w, ec->h, &new_x, &new_y);
        eina_list_free(l);
        evas_object_move(ec->frame, new_x, new_y);
     }
}

static int
_e_place_cb_sort_cmp(const void *v1, const void *v2)
{
   return (*((int *)v1)) - (*((int *)v2));
}

static Eina_Bool
ignore_client_and_break(const E_Client *ec)
{
   if (ec->fullscreen) return EINA_TRUE;
   if (ec->maximized)
     {
        E_Maximize max = ec->maximized & E_MAXIMIZE_DIRECTION;

        if (max == E_MAXIMIZE_FULLSCREEN) return EINA_TRUE;
        if (max == E_MAXIMIZE_BOTH) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static Eina_Bool
ignore_client(const E_Client *ec, const Eina_List *skiplist)
{
   if (eina_list_data_find(skiplist, ec)) return EINA_TRUE;
   if (e_client_util_ignored_get(ec)) return EINA_TRUE;
   if (!evas_object_visible_get(ec->frame)) return EINA_TRUE;
   if ((!e_client_has_xwindow(ec)) && (ec->netwm.type != E_WINDOW_TYPE_NORMAL)) return EINA_TRUE;

   return EINA_FALSE;
}

static int
_e_place_coverage_client_add(Eina_List *skiplist, int ar, int x, int y, int w, int h)
{
   E_Client *ec;
   int x2, y2, w2, h2;
   int iw, ih;
   int x0, x00, yy0, y00;

   E_CLIENT_REVERSE_FOREACH(ec)
     {
        if (ignore_client(ec, skiplist)) continue;
        if (ignore_client_and_break(ec)) break;
        x2 = ec->x; y2 = ec->y; w2 = ec->w; h2 = ec->h;
        if (E_INTERSECTS(x, y, w, h, x2, y2, w2, h2))
          {
             x0 = x;
             if (x < x2) x0 = x2;
             x00 = (x + w);
             if ((x2 + w2) < (x + w)) x00 = (x2 + w2);
             yy0 = y;
             if (y < y2) yy0 = y2;
             y00 = (y + h);
             if ((y2 + h2) < (y + h)) y00 = (y2 + h2);
             iw = x00 - x0;
             ih = y00 - yy0;
             ar += (iw * ih);
          }
     }
   return ar;
}

static int
_e_place_coverage_zone_obstacles_add_single(E_Zone_Obstacle *obs, int ar, int x, int y, int w, int h)
{
   int x2, y2, w2, h2;
   int x0, x00, yy0, y00;
   int iw, ih;

   x2 = obs->x; y2 = obs->y; w2 = obs->w; h2 = obs->h;
   if (!E_INTERSECTS(x, y, w, h, x2, y2, w2, h2)) return ar;

   /* FIXME: this option implies that windows should be resized when
    * an autohide shelf toggles its visibility, but it is not used correctly
    * and is instead used to determine whether shelves can be overlapped
    */
   if (!e_config->border_fix_on_shelf_toggle) return 0x7fffffff;

   x0 = x;
   if (x < x2) x0 = x2;
   x00 = (x + w);
   if ((x2 + w2) < (x + w)) x00 = (x2 + w2);
   yy0 = y;
   if (y < y2) yy0 = y2;
   y00 = (y + h);
   if ((y2 + h2) < (y + h)) y00 = (y2 + h2);
   iw = x00 - x0;
   ih = y00 - yy0;
   ar += (iw * ih);
   return ar;
}

static int
_e_place_coverage_zone_obstacles_add(E_Desk *desk, int ar, int x, int y, int w, int h)
{
   E_Zone_Obstacle *obs;

   EINA_INLIST_FOREACH(desk->obstacles, obs)
     ar = _e_place_coverage_zone_obstacles_add_single(obs, ar, x, y, w, h);
   EINA_INLIST_FOREACH(desk->zone->obstacles, obs)
     ar = _e_place_coverage_zone_obstacles_add_single(obs, ar, x, y, w, h);
   return ar;
}

static int *
_e_place_array_resize(int *array, int *pos, int *size)
{
   (*pos)++;
   if (*pos > *size)
     {
        *size += 32;
        E_REALLOC(array, int, *size);
     }
   return array;
}

static void
_e_place_desk_region_smart_obstacle_add(char *u_x, char *u_y, int **a_x, int **a_y, int *a_w, int *a_h, int *a_alloc_w, int *a_alloc_h, int zx, int zy, int zw, int zh, int bx, int by, int bw, int bh)
{
   if (bx < zx)
     {
        bw += bx;
        bx = zx;
     }
   if ((bx + bw) > zx + zw) bw = zx + zw - bx;
   if (bx >= zx + zw) return;
   if (by < zy)
     {
        bh += by;
        by = zy;
     }
   if ((by + bh) > zy + zh) bh = zy + zh - by;
   if (by >= zy + zh) return;
   if (!u_x[bx])
     {
        *a_x = _e_place_array_resize(*a_x, a_w, a_alloc_w);
        (*a_x)[*a_w - 1] = bx;
        u_x[bx] = 1;
     }
   if (!u_x[bx + bw])
     {
        *a_x = _e_place_array_resize(*a_x, a_w, a_alloc_w);
        (*a_x)[*a_w - 1] = bx + bw;
        u_x[bx + bw] = 1;
     }
   if (!u_y[by])
     {
        *a_y = _e_place_array_resize(*a_y, a_h, a_alloc_h);
        (*a_y)[*a_h - 1] = by;
        u_y[by] = 1;
     }
   if (!u_y[by + bh])
     {
        *a_y = _e_place_array_resize(*a_y, a_h, a_alloc_h);
        (*a_y)[*a_h - 1] = by + bh;
        u_y[by + bh] = 1;
     }
}

/* determine whether the "overlapping" area for a given geometry
 * is less than the previous smallest overlapping area.
 * if this obstacle geometry has less overlap, set it as the current
 * geometry to use
 */
static int
_e_place_desk_region_smart_area_check(Eina_List *skiplist, int x, int y, int w, int h, E_Desk *desk, int area, int *rx, int *ry)
{
   int ar = 0;

   ar = _e_place_coverage_client_add(skiplist, ar, x, y, w, h);

   if (e_config->window_placement_policy == E_WINDOW_PLACEMENT_SMART)
     ar = _e_place_coverage_zone_obstacles_add(desk, ar, x, y, w, h);

   if (ar < area)
     {
        *rx = x;
        *ry = y;
        if (ar == 0) return ar;
     }
   return ar;
}

/* calculate optimal placement based on "overlapping" area using:
 * - an obstacle's top-left and bottom-right points
 * - list of clients to skip
 * - current desk
 * - current least overlapping area
 * - pointers to current coords to use for placement
 * and then return the new least overlapping area
 */
static int
_e_place_desk_region_smart_area_calc(int x, int y, int xx, int yy, int zx, int zy, int zw, int zh, int w, int h, Eina_List *skiplist, E_Desk *desk, int area, int *rx, int *ry)
{
   /* check top-left corner placement */
   if ((x <= MAX(zx, zx + (zw - w))) && (y <= MAX(zy, zy + (zh - h))))
     {
        int ar = _e_place_desk_region_smart_area_check(skiplist, x, y, w, h, desk, area, rx, ry);
        if (!ar) return ar;
        if (ar < area) area = ar;
     }
   /* check top-right corner placement */
   if ((MAX(zx, xx - w) > zx) && (y <= MAX(zy, zy + (zh - h))))
     {
        int ar = _e_place_desk_region_smart_area_check(skiplist, xx - w, y, w, h, desk, area, rx, ry);
        if (!ar) return ar;
        if (ar < area) area = ar;
     }
   /* check bottom-right corner placement */
   if ((MAX(zx, xx - w) > zx) && (MAX(zy, yy - h) > zy))
     {
        int ar = _e_place_desk_region_smart_area_check(skiplist, xx - w, yy - h, w, h, desk, area, rx, ry);
        if (!ar) return ar;
        if (ar < area) area = ar;
     }
   /* check bottom-left corner placement */
   if ((x <= MAX(zx, zx + (zw - w))) && (MAX(zy, yy - h) > zy))
     {
        int ar = _e_place_desk_region_smart_area_check(skiplist, x, yy - h, w, h, desk, area, rx, ry);
        if (!ar) return ar;
        if (ar < area) area = ar;
     }
   return area;
}

E_API int
e_place_desk_region_smart(E_Desk *desk, Eina_List *skiplist, int x, int y, int w, int h, int *rx, int *ry)
{
   int a_w = 0, a_h = 0, a_alloc_w = 0, a_alloc_h = 0;
   int *a_x = NULL, *a_y = NULL;
   int zx, zy, zw, zh;
   char *u_x = NULL, *u_y = NULL;
   E_Client *ec;

   *rx = x;
   *ry = y;
#if 0
   /* DISABLE placement entirely for speed testing */
   return 1;
#endif

   if ((w <= 0) || (h <= 0))
     {
        printf("EEEK! trying to place 0x0 window!!!!\n");
        return 1;
     }

   /* FIXME: this NEEDS optimizing */
   a_w = 2;
   a_h = 2;
   a_x = E_NEW(int, 2);
   a_y = E_NEW(int, 2);
   a_alloc_w = 2;
   a_alloc_h = 2;

   zx = desk->zone->x;
   zy = desk->zone->y;
   zw = desk->zone->w;
   zh = desk->zone->h;

   u_x = calloc(zx + zw + 1, sizeof(char));
   u_y = calloc(zy + zh + 1, sizeof(char));

   a_x[0] = zx;
   a_x[1] = zx + zw;
   a_y[0] = zy;
   a_y[1] = zy + zh;

   u_x[zx] = 1;
   u_x[zx + zw] = 1;
   u_y[zy] = 1;
   u_y[zy + zh] = 1;

   if (e_config->window_placement_policy == E_WINDOW_PLACEMENT_SMART)
     {
        E_Zone_Obstacle *obs;

        EINA_INLIST_FOREACH(desk->obstacles, obs)
          {
             int bx, by, bw, bh;

             bx = obs->x;
             by = obs->y;
             bw = obs->w;
             bh = obs->h;
             if (E_INTERSECTS(bx, by, bw, bh, zx, zy, zw, zh))
               _e_place_desk_region_smart_obstacle_add(u_x, u_y, &a_x, &a_y,
                 &a_w, &a_h, &a_alloc_w, &a_alloc_h, zx, zy, zw, zh, bx, by, bw, bh);
          }
        EINA_INLIST_FOREACH(desk->zone->obstacles, obs)
          {
             int bx, by, bw, bh;

             bx = obs->x;
             by = obs->y;
             bw = obs->w;
             bh = obs->h;
             if (E_INTERSECTS(bx, by, bw, bh, zx, zy, zw, zh))
               _e_place_desk_region_smart_obstacle_add(u_x, u_y, &a_x, &a_y,
                 &a_w, &a_h, &a_alloc_w, &a_alloc_h, zx, zy, zw, zh, bx, by, bw, bh);
          }
     }

   E_CLIENT_REVERSE_FOREACH(ec)
     {
        int bx, by, bw, bh;

        if (ignore_client(ec, skiplist)) continue;
        if (ignore_client_and_break(ec)) break;

        bx = ec->x;
        by = ec->y;
        bw = ec->w;
        bh = ec->h;

        if (E_INTERSECTS(bx, by, bw, bh, zx, zy, zw, zh))
          _e_place_desk_region_smart_obstacle_add(u_x, u_y, &a_x, &a_y,
            &a_w, &a_h, &a_alloc_w, &a_alloc_h, zx, zy, zw, zh, bx, by, bw, bh);
     }
   qsort(a_x, a_w, sizeof(int), _e_place_cb_sort_cmp);
   qsort(a_y, a_h, sizeof(int), _e_place_cb_sort_cmp);
   free(u_x);
   free(u_y);

   {
      int i, j;
      int area = 0x7fffffff;

      if ((x <= zx + (zw - w)) &&
          (y <= zy + (zh - h)))
        {
           int ar = 0;

           ar = _e_place_coverage_client_add(skiplist, ar,
                                             x, y,
                                             w, h);

           if (e_config->window_placement_policy == E_WINDOW_PLACEMENT_SMART)
             ar = _e_place_coverage_zone_obstacles_add(desk, ar,
                                              x, y,
                                              w, h);

           if (ar < area)
             {
                area = ar;
                *rx = x;
                *ry = y;
                if (ar == 0) goto done;
             }
        }

      /* loop through all the obstacles in the arrays of coords
       * (a_x[i], a_y[j]) is top-left corner of obstacle
       * (a_x[i + 1], a_y[j + 1]) is bottom-right corner of obstacle
       */
      for (j = 0; j < a_h - 1; j++)
        for (i = 0; i < a_w - 1; i++)
          {
             area = _e_place_desk_region_smart_area_calc(a_x[i], a_y[j], a_x[i + 1], a_y[j + 1],
                                                         zx, zy, zw, zh, w, h, skiplist, desk, area, rx, ry);
             if (!area) goto done;
          }
   }
done:
   E_FREE(a_x);
   E_FREE(a_y);

   e_zone_desk_useful_geometry_get(desk->zone, desk, &zx, &zy, &zw, &zh);

   if ((*rx + w) > zx + zw) *rx = zx + zw - w;
   if (*rx < zx) *rx = zx;
   if ((*ry + h) > zy + zh) *ry = zy + zh - h;
   if (*ry < zy) *ry = zy;

//   printf("0 - PLACE %i %i | %ix%i\n", *rx, *ry, w, h);

   return 1;
}

E_API int
e_place_zone_region_smart(E_Zone *zone, Eina_List *skiplist, int x, int y, int w, int h, int *rx, int *ry)
{
   return e_place_desk_region_smart(e_desk_current_get(zone), skiplist,
                                    x, y, w, h, rx, ry);
}

E_API int
e_place_zone_cursor(E_Zone *zone, int x EINA_UNUSED, int y EINA_UNUSED, int w, int h, int it, int *rx, int *ry)
{
   int cursor_x = 0, cursor_y = 0;
   int zone_right, zone_bottom;
   int zx, zy, zw, zh;

   E_OBJECT_CHECK_RETURN(zone, 0);

   ecore_evas_pointer_xy_get(e_comp->ee, &cursor_x, &cursor_y);
   *rx = cursor_x - (w >> 1);
   *ry = cursor_y - (it >> 1);

   e_zone_useful_geometry_get(zone, &zx, &zy, &zw, &zh);

   if (*rx < zone->x)
     *rx = zx;

   if (*ry < zone->y)
     *ry = zy;

   zone_right = zx + zw;
   zone_bottom = zy + zh;

   if ((*rx + w) > zone_right)
     *rx = zone_right - w;

   if ((*ry + h) > zone_bottom)
     *ry = zone_bottom - h;

   return 1;
}

E_API int
e_place_zone_manual(E_Zone *zone, int w, int h, int *rx, int *ry)
{
   int cursor_x = 0, cursor_y = 0;

   E_OBJECT_CHECK_RETURN(zone, 0);

   ecore_evas_pointer_xy_get(e_comp->ee, &cursor_x, &cursor_y);
   if (rx) *rx = cursor_x - (w >> 1);
   if (ry) *ry = cursor_y - (h >> 1);

   return 1;
}

#include "e.h"

typedef struct _E_Pointer_Stack E_Pointer_Stack;
struct _E_Pointer_Stack
{
   void *obj;
   const char *type;
};

/* local variables */
static Eina_List *_hdlrs = NULL;
static Eina_List *_ptrs = NULL;

static Eina_Bool init = EINA_FALSE;

static inline void
_e_pointer_theme_buf(E_Pointer *ptr, char cursor[1024])
{
   const char *type = ptr->type;

   if (!type) type = "default";
   if (ptr->color)
     snprintf(cursor, 1024, "e/pointer/enlightenment/%s/color", type);
   else
     snprintf(cursor, 1024, "e/pointer/enlightenment/%s/mono", type);
}

static inline void
_e_pointer_hot_update(E_Pointer *ptr, int x, int y)
{
   if ((ptr->hot.x != x) || (ptr->hot.y != y))
     {
        ptr->hot.x = x;
        ptr->hot.y = y;
        ptr->hot.update = EINA_TRUE;
     }
}

static void
_e_pointer_active(E_Pointer *ptr)
{
   if (!ptr->idle) return;
   if (ptr->o_ptr)
     edje_object_signal_emit(ptr->o_ptr, "e,state,mouse,active", "e");
   ptr->idle = EINA_FALSE;
}

static void
_e_pointer_idle(E_Pointer *ptr)
{
   if (ptr->idle) return;
   if (ptr->o_ptr)
     edje_object_signal_emit(ptr->o_ptr, "e,state,mouse,idle", "e");
   ptr->idle = EINA_TRUE;
}

static Eina_Bool
_e_pointer_cb_idle_poller(void *data)
{
   E_Pointer *ptr;
   int x = 0, y = 0;

   if (!(ptr = data)) return ECORE_CALLBACK_RENEW;

   if ((e_powersave_mode_get() >= E_POWERSAVE_MODE_MEDIUM) ||
       (!e_config->idle_cursor))
     {
        ptr->idle_poll = NULL;
        return ECORE_CALLBACK_CANCEL;
     }

   if (ptr->canvas)
     ecore_evas_pointer_xy_get(ptr->ee, &x, &y);
#ifndef HAVE_WAYLAND_ONLY
   else
     ecore_x_pointer_xy_get(ptr->win, &x, &y);
#endif

   if ((ptr->x != x) || (ptr->y != y))
     {
        ptr->x = x;
        ptr->y = y;
        if (ptr->idle) _e_pointer_active(ptr);
        return ECORE_CALLBACK_RENEW;
     }

   if (!ptr->idle) _e_pointer_idle(ptr);

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_pointer_cb_idle_wait(void *data)
{
   E_Pointer *ptr;

   if (!(ptr = data)) return ECORE_CALLBACK_RENEW;
   ptr->idle_tmr = NULL;
   if ((e_powersave_mode_get() >= E_POWERSAVE_MODE_MEDIUM) ||
       (!e_config->idle_cursor))
     {
        E_FREE_FUNC(ptr->idle_poll, ecore_poller_del);
        return ECORE_CALLBACK_CANCEL;
     }

   if (!ptr->idle_poll)
     ptr->idle_poll = ecore_poller_add(ECORE_POLLER_CORE, 64,
                                       _e_pointer_cb_idle_poller, ptr);

   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_pointer_cb_idle_pre(void *data)
{
   E_Pointer *ptr;

   if (!(ptr = data)) return ECORE_CALLBACK_RENEW;

   if (ptr->canvas)
     ecore_evas_pointer_xy_get(ptr->ee, &ptr->x, &ptr->y);
#ifndef HAVE_WAYLAND_ONLY
   else
     ecore_x_pointer_xy_get(ptr->win, &ptr->x, &ptr->y);
#endif

   ptr->idle_tmr = ecore_timer_loop_add(4.0, _e_pointer_cb_idle_wait, ptr);

   return ECORE_CALLBACK_CANCEL;
}

static void
_e_pointer_active_handle(E_Pointer *ptr)
{
   _e_pointer_active(ptr);
   if (ptr->idle_tmr)
     ecore_timer_loop_reset(ptr->idle_tmr);
   else
     {
        E_FREE_FUNC(ptr->idle_poll, ecore_poller_del);
        if (e_powersave_mode_get() >= E_POWERSAVE_MODE_MEDIUM) return;
        if (!e_config->idle_cursor) return;
        ptr->idle_tmr = ecore_timer_loop_add(1.0, _e_pointer_cb_idle_pre, ptr);
     }
}

static Eina_Bool
_e_pointer_cb_mouse_down(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Pointer *ptr;

   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        _e_pointer_active_handle(ptr);
        if (e_powersave_mode_get() < E_POWERSAVE_MODE_EXTREME)
          {
             if (ptr->o_ptr)
               edje_object_signal_emit(ptr->o_ptr, "e,action,mouse,down", "e");
          }
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_pointer_cb_mouse_up(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Pointer *ptr;

   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        _e_pointer_active_handle(ptr);
        if (e_powersave_mode_get() < E_POWERSAVE_MODE_EXTREME)
          {
             if (ptr->o_ptr)
               edje_object_signal_emit(ptr->o_ptr, "e,action,mouse,up", "e");
          }
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_pointer_cb_mouse_move(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Pointer *ptr;

   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        _e_pointer_active_handle(ptr);
        if (e_powersave_mode_get() < E_POWERSAVE_MODE_HIGH)
          {
             if (ptr->o_ptr)
               edje_object_signal_emit(ptr->o_ptr, "e,action,mouse,move", "e");
          }
     }

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_pointer_cb_mouse_wheel(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Eina_List *l;
   E_Pointer *ptr;

   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        _e_pointer_active_handle(ptr);
        if (e_powersave_mode_get() < E_POWERSAVE_MODE_EXTREME)
          {
             if (ptr->o_ptr)
               edje_object_signal_emit(ptr->o_ptr, "e,action,mouse,wheel", "e");
          }
     }

   return ECORE_CALLBACK_PASS_ON;
}

static void
_hot_update(E_Pointer *ptr)
{
   int x = 0, y = 0, ox = 0, oy = 0;

   if (ptr->e_cursor && (e_comp->comp_type != E_PIXMAP_TYPE_WL))
     evas_object_geometry_get(ptr->buffer_o_ptr, &ox, &oy, NULL, NULL);
   else
     evas_object_geometry_get(ptr->o_ptr, &ox, &oy, NULL, NULL);
   evas_object_geometry_get(ptr->o_hot, &x, &y, NULL, NULL);
   _e_pointer_hot_update(ptr, x - ox, y - oy);
}

static void
_e_pointer_cb_hot_move(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Pointer *ptr = data;

   if (!ptr->e_cursor) return;
   if (!evas_object_visible_get(ptr->o_ptr)) return;
   _hot_update(ptr);
}

static void
_e_pointer_cb_hot_show(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   E_Pointer *ptr = data;

   if (!ptr->e_cursor) return;
   _hot_update(ptr);
}

static void
_del_cb(void *data, const Efl_Event *ev EINA_UNUSED) {
  E_Pointer *ptr = data;
  INF("Cursor-Object (%p) of %p was removed!", ptr->o_ptr, data);
  ptr->o_ptr = NULL;
  return;
}

static void
_e_pointer_pointer_canvas_init(E_Pointer *ptr, Evas *e, Evas_Object **o_ptr, Evas_Object **o_hot)
{
   /* create pointer object */
   *o_ptr = edje_object_add(e);

   efl_event_callback_add(*o_ptr, EFL_EVENT_DEL, _del_cb, ptr);

   /* create hotspot object */
   *o_hot = evas_object_rectangle_add(e);
   evas_object_color_set(*o_hot, 0, 0, 0, 0);


   evas_object_event_callback_add(*o_hot, EVAS_CALLBACK_MOVE,
                                  _e_pointer_cb_hot_move, ptr);
   evas_object_event_callback_add(*o_ptr, EVAS_CALLBACK_MOVE,
                                  _e_pointer_cb_hot_move, ptr);
   evas_object_event_callback_add(*o_hot, EVAS_CALLBACK_SHOW,
                                  _e_pointer_cb_hot_show, ptr);

   evas_object_move(*o_ptr, 0, 0);
   evas_object_resize(*o_ptr, ptr->w, ptr->h);
}

static void
_e_pointer_canvas_del(E_Pointer *ptr)
{
   E_FREE_FUNC(ptr->buffer_o_hot, evas_object_del);
   E_FREE_FUNC(ptr->buffer_o_ptr, evas_object_del);
   E_FREE_FUNC(ptr->buffer_evas, evas_free);
   E_FREE(ptr->pixels);
}

static void
_e_pointer_canvas_add(E_Pointer *ptr)
{
   Evas_Engine_Info_Buffer *einfo;
   int method = 0;

   /* try to create new canvas */
   if (!(ptr->buffer_evas = evas_new())) goto err;

   method = evas_render_method_lookup("buffer");
   evas_output_method_set(ptr->buffer_evas, method);
   evas_output_size_set(ptr->buffer_evas, ptr->w, ptr->h);
   evas_output_viewport_set(ptr->buffer_evas, 0, 0, ptr->w, ptr->h);

   /* try to allocate space for pixels */
   if (!(ptr->pixels = malloc(ptr->w * ptr->h * sizeof(int))))
     goto err;

   /* try to get the buffer engine info */
   einfo = (Evas_Engine_Info_Buffer *)(void *)
     evas_engine_info_get(ptr->buffer_evas);
   if (!einfo) goto err;

   /* fill in buffer engine info */
   einfo->info.depth_type = EVAS_ENGINE_BUFFER_DEPTH_ARGB32;
   einfo->info.dest_buffer = ptr->pixels;
   einfo->info.dest_buffer_row_bytes = (ptr->w * sizeof(int));
   einfo->info.use_color_key = 0;
   einfo->info.alpha_threshold = 0;
   einfo->info.func.new_update_region = NULL;
   einfo->info.func.free_update_region = NULL;

   /* set buffer engine info */
   evas_engine_info_set(ptr->buffer_evas, (Evas_Engine_Info *)einfo);

   _e_pointer_pointer_canvas_init(ptr, ptr->buffer_evas, &ptr->buffer_o_ptr, &ptr->buffer_o_hot);
   if (!ptr->evas)
     {
        ptr->evas = ptr->buffer_evas;
        ptr->o_ptr = ptr->buffer_o_ptr;
        ptr->o_hot = ptr->buffer_o_hot;
     }
   return;

err:
   _e_pointer_canvas_del(ptr);
}

static void
_e_pointer_canvas_resize(E_Pointer *ptr, int w, int h)
{
   Evas_Engine_Info_Buffer *einfo;

   if ((ptr->w == w) && (ptr->h == h)) return;
   ptr->w = w;
   ptr->h = h;
   evas_output_size_set(ptr->buffer_evas, w, h);
   evas_output_viewport_set(ptr->buffer_evas, 0, 0, w, h);

   ptr->pixels = realloc(ptr->pixels, (ptr->w * ptr->h * sizeof(int)));

   einfo = (Evas_Engine_Info_Buffer *)(void *)
     evas_engine_info_get(ptr->buffer_evas);
   EINA_SAFETY_ON_NULL_RETURN(einfo);

   einfo->info.dest_buffer = ptr->pixels;
   einfo->info.dest_buffer_row_bytes = (ptr->w * sizeof(int));
   evas_engine_info_set(ptr->buffer_evas, (Evas_Engine_Info *)einfo);

   evas_object_move(ptr->buffer_o_ptr, 0, 0);
   evas_object_resize(ptr->buffer_o_ptr, ptr->w, ptr->h);
}

static void
_e_pointer_stack_free(E_Pointer_Stack *stack)
{
   if (stack->type) eina_stringshare_del(stack->type);
   free(stack);
}

static void
_e_pointer_cb_free(E_Pointer *ptr)
{
   _ptrs = eina_list_remove(_ptrs, ptr);

   E_FREE_LIST(ptr->stack, _e_pointer_stack_free);

   eina_stringshare_del(ptr->type);
   eina_stringshare_del(ptr->deferred_type);

   E_FREE_FUNC(ptr->idle_tmr, ecore_timer_del);
   E_FREE_FUNC(ptr->idle_poll, ecore_poller_del);

   if (ptr->buffer_evas) _e_pointer_canvas_del(ptr);

   free(ptr);
}

static void
_e_pointer_x11_setup(E_Pointer *ptr, const char *cursor)
{
   if (ptr->e_cursor && (e_comp->comp_type != E_PIXMAP_TYPE_WL))
     {
        /* create a pointer canvas if we need to */
        if ((!ptr->buffer_evas) && ptr->win) _e_pointer_canvas_add(ptr);
        if (ptr->buffer_o_ptr && (ptr->buffer_o_ptr != ptr->o_ptr))
          {
             e_theme_edje_object_set(ptr->buffer_o_ptr, "base/theme/pointer", cursor);
             edje_object_part_swallow(ptr->buffer_o_ptr, "e.swallow.hotspot", ptr->buffer_o_hot);
             if (!init)
               {
                  edje_object_signal_emit(ptr->o_ptr, "e,state,init", "e");
                  edje_object_message_signal_process(ptr->o_ptr);
                  init = EINA_TRUE;
               }
          }
        return;
     }
   if (ptr->buffer_evas) _e_pointer_canvas_del(ptr);
#ifndef HAVE_WAYLAND_ONLY
   if (!e_comp_util_has_x()) return;
   Ecore_X_Cursor curs = 0;

   if (!strcmp(ptr->type, "move"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_FLEUR);
# if 0
   else if (!strcmp(ptr->type, "resize"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_SIZING);
# endif
   else if (!strcmp(ptr->type, "resize_tl"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_TOP_LEFT_CORNER);
   else if (!strcmp(ptr->type, "resize_t"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_TOP_SIDE);
   else if (!strcmp(ptr->type, "resize_tr"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_TOP_RIGHT_CORNER);
   else if (!strcmp(ptr->type, "resize_r"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_RIGHT_SIDE);
   else if (!strcmp(ptr->type, "resize_br"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_BOTTOM_RIGHT_CORNER);
   else if (!strcmp(ptr->type, "resize_b"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_BOTTOM_SIDE);
   else if (!strcmp(ptr->type, "resize_bl"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_BOTTOM_LEFT_CORNER);
   else if (!strcmp(ptr->type, "resize_l"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_LEFT_SIDE);
   else if (!strcmp(ptr->type, "entry"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_XTERM);
   else if (!strcmp(ptr->type, "default"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_LEFT_PTR);
   else if (!strcmp(ptr->type, "plus"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_PLUS);
   else if (!strcmp(ptr->type, "hand"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_HAND1);
   else if (!strcmp(ptr->type, "rotate"))
     curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_EXCHANGE);
   else
     {
        WRN("Unknown pointer ptr->type: %s\n", ptr->type);
        curs = ecore_x_cursor_shape_get(ECORE_X_CURSOR_ARROW);
     }
   if (!curs) WRN("X Cursor for %s is missing\n", ptr->type);
   ecore_x_window_cursor_set(ptr->win, curs);
   if (curs) ecore_x_cursor_free(curs);
#endif
}

static void
_e_pointer_type_set(E_Pointer *ptr, const char *type)
{
   /* check if pointer type is already set */
   if (!e_util_strcmp(ptr->type, type)) return;

   if (ptr->grabcount > 0)
     {
        eina_stringshare_replace(&(ptr->deferred_type), type);
        return;
     }
   eina_stringshare_replace(&ptr->type, type);

   /* don't show cursor if in hidden mode */
   if (!e_config->show_cursor)
     {
        e_pointer_hide(ptr);
        return;
     }

   if (ptr->e_cursor)
     {
        char cursor[1024];

        if ((!ptr->buffer_evas) && ptr->win) _e_pointer_canvas_add(ptr);
        _e_pointer_theme_buf(ptr, cursor);

        /* try to set the edje object theme */
        if (!e_theme_edje_object_set(ptr->o_ptr, "base/theme/pointer", cursor))
          cursor[0] = 0;
        edje_object_part_swallow(ptr->o_ptr, "e.swallow.hotspot", ptr->o_hot);
        if (!init)
          {
             edje_object_signal_emit(ptr->o_ptr, "e,state,init", "e");
             edje_object_message_signal_process(ptr->o_ptr);
             init = EINA_TRUE;
          }
        _e_pointer_x11_setup(ptr, cursor);
        if (!cursor[0]) return;

        _hot_update(ptr);

        if (ptr->canvas)
          e_pointer_object_set(ptr, NULL, 0, 0);
        else
          evas_object_show(ptr->o_ptr);
     }
   else
     _e_pointer_x11_setup(ptr, NULL);
}

EINTERN int
e_pointer_init(void)
{
   E_LIST_HANDLER_APPEND(_hdlrs, ECORE_EVENT_MOUSE_BUTTON_DOWN,
                         _e_pointer_cb_mouse_down, NULL);
   E_LIST_HANDLER_APPEND(_hdlrs, ECORE_EVENT_MOUSE_BUTTON_UP,
                         _e_pointer_cb_mouse_up, NULL);
   E_LIST_HANDLER_APPEND(_hdlrs, ECORE_EVENT_MOUSE_MOVE,
                         _e_pointer_cb_mouse_move, NULL);
   E_LIST_HANDLER_APPEND(_hdlrs, ECORE_EVENT_MOUSE_WHEEL,
                         _e_pointer_cb_mouse_wheel, NULL);
   init = EINA_FALSE;
   return 1;
}

EINTERN int
e_pointer_shutdown(void)
{
   E_FREE_LIST(_hdlrs, ecore_event_handler_del);
   init = EINA_FALSE;
   return 1;
}

E_API E_Pointer *
e_pointer_window_new(Ecore_Window win, Eina_Bool filled)
{
   E_Pointer *ptr = NULL;

   EINA_SAFETY_ON_FALSE_RETURN_VAL(win, NULL);

   /* allocate space for new pointer */
   if (!(ptr = E_OBJECT_ALLOC(E_Pointer, E_POINTER_TYPE, _e_pointer_cb_free)))
     return NULL;

   /* set default pointer properties */
   ptr->w = ptr->h = (double)e_config->cursor_size * e_scale;
   ptr->e_cursor = e_config->use_e_cursor;
   ptr->win = win;
   ptr->color = EINA_FALSE;
   if (e_comp->pointer)
     ptr->color = e_comp->pointer->color;

   /* set pointer default type */
   if (filled) e_pointer_type_push(ptr, ptr, "default");

   /* append this pointer to the list */
   _ptrs = eina_list_append(_ptrs, ptr);

   return ptr;
}

E_API E_Pointer *
e_pointer_canvas_new(Ecore_Evas *ee, Eina_Bool filled)
{
   E_Pointer *ptr = NULL;

   EINA_SAFETY_ON_FALSE_RETURN_VAL(ee, NULL);

   /* allocate space for new pointer */
   if (!(ptr = E_OBJECT_ALLOC(E_Pointer, E_POINTER_TYPE, _e_pointer_cb_free)))
     return NULL;

   /* set default pointer properties */
   ptr->color = EINA_TRUE;
   ptr->canvas = EINA_TRUE;
   ptr->w = ptr->h = (double)e_config->cursor_size * e_scale;
   ptr->e_cursor = 1;

   ptr->ee = ee;
   ptr->evas = ecore_evas_get(ee);
   _e_pointer_pointer_canvas_init(ptr, ptr->evas, &ptr->o_ptr, &ptr->o_hot);

   /* set pointer default type */
   if (filled) e_pointer_type_push(ptr, ptr, "default");

     /* append this pointer to the list */
   _ptrs = eina_list_append(_ptrs, ptr);

   _e_pointer_active_handle(ptr);

   return ptr;
}

E_API void
e_pointers_size_set(int size)
{
   Eina_List *l;
   E_Pointer *ptr;

   if (!e_config->show_cursor) return;

   size = (double)size * e_scale;
   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        if ((ptr->w == size) && (ptr->h == size)) continue;
        if (ptr->buffer_evas)
          _e_pointer_canvas_resize(ptr, size, size);
        if (ptr->canvas)
          {
             ptr->w = size;
             ptr->h = size;
             evas_object_resize(ptr->o_ptr, size, size);
          }
     }
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp_util_has_x())
     ecore_x_cursor_size_set(e_config->cursor_size * 3 / 4);
#endif
}

E_API void
e_pointer_hide(E_Pointer *ptr)
{
   if (ptr->buffer_evas)
     _e_pointer_canvas_del(ptr);
   if (ptr->canvas)
     evas_object_hide(ptr->o_ptr);
#ifndef HAVE_WAYLAND_ONLY
   if (ptr->win)
     ecore_x_window_cursor_set(ptr->win, 0);
#endif
}

E_API void
e_pointer_show(E_Pointer *ptr)
{
   EINA_SAFETY_ON_NULL_RETURN(ptr);
   EINA_SAFETY_ON_NULL_RETURN(ptr->o_ptr);

   if ((!ptr->buffer_evas) && ptr->win) _e_pointer_canvas_add(ptr);
   if (ptr->canvas)
     evas_object_show(ptr->o_ptr);
}

E_API void
e_pointer_type_push(E_Pointer *ptr, void *obj, const char *type)
{
   E_Pointer_Stack *stack;

   EINA_SAFETY_ON_NULL_RETURN(ptr);
   EINA_SAFETY_ON_NULL_RETURN(obj);

   _e_pointer_type_set(ptr, type);

   if (!(stack = E_NEW(E_Pointer_Stack, 1))) return;
   stack->type = eina_stringshare_ref(ptr->type);
   stack->obj = obj;
   ptr->stack = eina_list_prepend(ptr->stack, stack);
}

E_API void
e_pointer_type_pop(E_Pointer *ptr, void *obj, const char *type)
{
   Eina_List *l, *ll;
   E_Pointer_Stack *stack;

   EINA_SAFETY_ON_NULL_RETURN(ptr);

   EINA_LIST_FOREACH_SAFE(ptr->stack, l, ll, stack)
     {
        if (((stack->obj == obj) || (!obj)) &&
            ((!type) || (!e_util_strcmp(stack->type, type))))
          {
             _e_pointer_stack_free(stack);
             ptr->stack = eina_list_remove_list(ptr->stack, l);
             if (type) break;
          }
     }

   if ((!obj) && (!type))
     {
        if (!ptr->stack)
          {
             e_pointer_type_push(ptr, ptr, "default");
          }
     }
   else
     {
        if (!ptr->stack)
          {
             e_pointer_hide(ptr);
             eina_stringshare_replace(&ptr->type, NULL);
             return;
          }
     }

   if (!(stack = eina_list_data_get(ptr->stack))) return;

   _e_pointer_type_set(ptr, stack->type);

   /* eina_stringshare_replace(&ptr->type, stack->type); */
}

E_API void
e_pointer_mode_push(void *obj, E_Pointer_Mode mode)
{
   switch (mode)
     {
      case E_POINTER_RESIZE_TL:
        e_pointer_type_push(e_comp->pointer, obj, "resize_tl");
        break;

      case E_POINTER_RESIZE_T:
        e_pointer_type_push(e_comp->pointer, obj, "resize_t");
        break;

      case E_POINTER_RESIZE_TR:
        e_pointer_type_push(e_comp->pointer, obj, "resize_tr");
        break;

      case E_POINTER_RESIZE_R:
        e_pointer_type_push(e_comp->pointer, obj, "resize_r");
        break;

      case E_POINTER_RESIZE_BR:
        e_pointer_type_push(e_comp->pointer, obj, "resize_br");
        break;

      case E_POINTER_RESIZE_B:
        e_pointer_type_push(e_comp->pointer, obj, "resize_b");
        break;

      case E_POINTER_RESIZE_BL:
        e_pointer_type_push(e_comp->pointer, obj, "resize_bl");
        break;

      case E_POINTER_RESIZE_L:
        e_pointer_type_push(e_comp->pointer, obj, "resize_l");
        break;

      case E_POINTER_MOVE:
        e_pointer_type_push(e_comp->pointer, obj, "move");
        break;

      default: break;
     }
}

E_API void
e_pointer_mode_pop(void *obj, E_Pointer_Mode mode)
{
   switch (mode)
     {
      case E_POINTER_RESIZE_TL:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_tl");
        break;

      case E_POINTER_RESIZE_T:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_t");
        break;

      case E_POINTER_RESIZE_TR:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_tr");
        break;

      case E_POINTER_RESIZE_R:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_r");
        break;

      case E_POINTER_RESIZE_BR:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_br");
        break;

      case E_POINTER_RESIZE_B:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_b");
        break;

      case E_POINTER_RESIZE_BL:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_bl");
        break;

      case E_POINTER_RESIZE_L:
        e_pointer_type_pop(e_comp->pointer, obj, "resize_l");
        break;

      case E_POINTER_MOVE:
        e_pointer_type_pop(e_comp->pointer, obj, "move");
        break;

      default: break;
     }
}

E_API void
e_pointer_idler_before(void)
{
   Eina_List *l;
   E_Pointer *ptr;

   if (!e_config->show_cursor) return;

   if (ecore_evas_manual_render_get(e_comp->ee)) return;
   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        if ((!ptr->e_cursor) || (!ptr->buffer_evas)) continue;

        if (!ptr->o_ptr) {
          EINA_SAFETY_ERROR("ptr->o_ptr is NULL should not be NULL!");
        }

        if (ptr->hot.update)
          _e_pointer_type_set(ptr, ptr->type);

        if (ptr->buffer_evas)
          {
             Eina_List *updates;

             if ((updates = evas_render_updates(ptr->buffer_evas)))
               {
#ifndef HAVE_WAYLAND_ONLY
                  Ecore_X_Cursor cur;
                  int hotx = ptr->hot.x;
                  int hoty = ptr->hot.y;

                  if      (hotx < 0)       hotx = 0;
                  else if (hotx >= ptr->w) hotx = ptr->w - 1;
                  if      (hoty < 0)       hoty = 0;
                  else if (hoty >= ptr->h) hoty = ptr->h - 1;
                  cur = ecore_x_cursor_new(ptr->win, ptr->pixels,
                                           ptr->w, ptr->h, hotx, hoty);
                  ecore_x_window_cursor_set(ptr->win, cur);
                  ecore_x_cursor_free(cur);
#endif
                  evas_render_updates_free(updates);
               }
          }

        ptr->hot.update = EINA_FALSE;
     }
}

E_API void
e_pointer_object_set(E_Pointer *ptr, Evas_Object *obj, int x, int y)
{
   Evas_Object *o;
   E_Client *ec;
   int px, py;

   EINA_SAFETY_ON_NULL_RETURN(ptr);
   EINA_SAFETY_ON_NULL_RETURN(ptr->o_ptr);

   ecore_evas_cursor_get(ptr->ee, &o, NULL, &px, &py);
   if (o)
     {
        if (o == obj)
          {
             ecore_evas_object_cursor_set(ptr->ee, obj, E_LAYER_MAX - 1, x, y);
             return;
          }
        ec = e_comp_object_client_get(o);
        if (ec)
          {
             ec->hidden = 1;
             evas_object_hide(ec->frame);
          }
     }
   ec = NULL;
   if (obj)
     {
        ec = e_comp_object_client_get(obj);
        if (ec)
          ec->hidden = 1;
        ecore_evas_cursor_unset(ptr->ee);
        ecore_evas_object_cursor_set(ptr->ee, obj, E_LAYER_MAX - 1, x, y);
     }
   else if ((o != ptr->o_ptr) || (x != px) || (y != py))
     {
        ecore_evas_cursor_unset(ptr->ee);
        ecore_evas_object_cursor_set(ptr->ee, ptr->o_ptr, E_LAYER_MAX - 1,
                                     ptr->hot.x, ptr->hot.y);
        evas_object_show(ptr->o_ptr);
     }
   ptr->client.ec = ec;
   ptr->client.x = x;
   ptr->client.y = y;
}

E_API void
e_pointer_window_add(E_Pointer *ptr, Ecore_Window win)
{
   ptr->win = win;
   _e_pointer_x11_setup(ptr, "default");
}

E_API void
e_pointer_grab_set(E_Pointer *ptr, Eina_Bool grab)
{
   EINA_SAFETY_ON_NULL_RETURN(ptr);
   EINA_SAFETY_ON_NULL_RETURN(ptr->o_ptr);

   if (grab) ptr->grabcount++;
   else
     {
        if (ptr->grabcount > 0)
          {
             ptr->grabcount--;
             if ((ptr->grabcount == 0) && (ptr->deferred_type))
               {
                  _e_pointer_type_set(ptr, ptr->deferred_type);
                  eina_stringshare_del(ptr->deferred_type);
                  ptr->deferred_type = NULL;
               }
          }
     }
}

E_API void
e_pointer_reset(E_Pointer *ptr)
{
   EINA_SAFETY_ON_NULL_RETURN(ptr);

   /* free stack of pointer types */
   E_FREE_LIST(ptr->stack, _e_pointer_stack_free);

   eina_stringshare_del(ptr->type);
   ptr->type = NULL;
   eina_stringshare_del(ptr->deferred_type);
   ptr->deferred_type = NULL;

   /* reset pointer to default */
   e_pointer_type_push(ptr, ptr, "default");
}

EINTERN void
e_pointers_freeze_set(Eina_Bool set)
{
   Eina_List *l;
   E_Pointer *ptr;

   EINA_LIST_FOREACH(_ptrs, l, ptr)
     {
        if (isedje(ptr->o_ptr))
          edje_object_play_set(ptr->o_ptr, !set);
        if (isedje(ptr->buffer_o_ptr))
          edje_object_play_set(ptr->buffer_o_ptr, !set);
     }
}
#include "e.h"
#include <sys/select.h>

struct _E_Powersave_Deferred_Action
{
   void          (*func)(void *data);
   const void   *data;
   unsigned char delete_me E_BITFIELD;
};

struct _E_Powersave_Sleeper
{
   Ecore_Pipe *pipe;
   int         fd;
   Eina_Bool   go E_BITFIELD;
   Eina_Bool   free_requested E_BITFIELD;
};

/* local subsystem functions */
static void      _e_powersave_sleeper_cb_dummy(void *data EINA_UNUSED, void *buffer EINA_UNUSED, unsigned int bytes EINA_UNUSED);
static Eina_Bool _e_powersave_cb_deferred_timer(void *data);
static void      _e_powersave_mode_eval(void);
static void      _e_powersave_event_update_free(void *data EINA_UNUSED, void *event);
static void      _e_powersave_event_change_send(E_Powersave_Mode mode);
static void      _e_powersave_sleepers_wake(void);

/* local subsystem globals */
E_API int E_EVENT_POWERSAVE_UPDATE = 0;
E_API int E_EVENT_POWERSAVE_CONFIG_UPDATE = 0;
static int walking_deferred_actions = 0;
static Eina_List *deferred_actions = NULL;
static Ecore_Timer *deferred_timer = NULL;
static E_Powersave_Mode powersave_mode = E_POWERSAVE_MODE_LOW;
static E_Powersave_Mode powersave_mode_force = E_POWERSAVE_MODE_NONE;
static E_Powersave_Mode powersave_mode_screen = E_POWERSAVE_MODE_NONE;
static double defer_time = 5.0;
static Eina_Bool powersave_force = EINA_FALSE;
static Eina_List *powersave_sleepers = NULL;
static Eina_Bool powersave_deferred_suspend = EINA_FALSE;
static Eina_Bool powersave_deferred_hibernate = EINA_FALSE;
static Eina_Bool powersave_screen = EINA_FALSE;

/* externally accessible functions */
EINTERN int
e_powersave_init(void)
{
   _e_powersave_mode_eval();
   E_EVENT_POWERSAVE_UPDATE = ecore_event_type_new();
   E_EVENT_POWERSAVE_CONFIG_UPDATE = ecore_event_type_new();
   return 1;
}

EINTERN int
e_powersave_shutdown(void)
{
   return 1;
}

E_API E_Powersave_Deferred_Action *
e_powersave_deferred_action_add(void (*func)(void *data), const void *data)
{
   E_Powersave_Deferred_Action *pa;

   pa = calloc(1, sizeof(E_Powersave_Deferred_Action));
   if (!pa) return NULL;
   if (deferred_timer) ecore_timer_del(deferred_timer);
   deferred_timer = ecore_timer_loop_add(defer_time,
                                         _e_powersave_cb_deferred_timer,
                                         NULL);
   pa->func = func;
   pa->data = data;
   deferred_actions = eina_list_append(deferred_actions, pa);
   return pa;
}

E_API void
e_powersave_deferred_action_del(E_Powersave_Deferred_Action *pa)
{
   if (walking_deferred_actions)
     {
        pa->delete_me = 1;
        return;
     }
   else
     {
        deferred_actions = eina_list_remove(deferred_actions, pa);
        free(pa);
        if (!deferred_actions)
          {
             if (deferred_timer)
               {
                  ecore_timer_del(deferred_timer);
                  deferred_timer = NULL;
               }
          }
     }
}

E_API void
e_powersave_mode_set(E_Powersave_Mode mode)
{
   if (mode < e_config->powersave.min) mode = e_config->powersave.min;
   else if (mode > e_config->powersave.max) mode = e_config->powersave.max;

   if (powersave_mode == mode) return;
   printf("PWSAVE %i/%i\n", (int)mode, (int)E_POWERSAVE_MODE_FREEZE);
   powersave_mode = mode;

   if (powersave_force) return;
   _e_powersave_event_change_send(powersave_mode);
   _e_powersave_mode_eval();
}

E_API E_Powersave_Mode
e_powersave_mode_get(void)
{
   if (powersave_force) return powersave_mode_force;
//   if (powersave_screen) return powersave_mode_screen;
   return powersave_mode;
}

E_API void
e_powersave_mode_force(E_Powersave_Mode mode)
{
   if (mode == powersave_mode_force) return;
   printf("PWSAVE FORCE %i/%i\n", (int)mode, (int)E_POWERSAVE_MODE_FREEZE);
   powersave_force = EINA_TRUE;
   powersave_mode_force = mode;
   _e_powersave_event_change_send(powersave_mode_force);
   _e_powersave_mode_eval();
}

E_API void
e_powersave_mode_unforce(void)
{
   if (!powersave_force) return;
   printf("PWSAVE UNFORCE\n");
   powersave_force = EINA_FALSE;
   if (powersave_mode_force != powersave_mode)
     {
        _e_powersave_event_change_send(powersave_mode);
        _e_powersave_mode_eval();
     }
   powersave_mode_force = E_POWERSAVE_MODE_NONE;
}

E_API void
e_powersave_mode_screen_set(E_Powersave_Mode mode)
{
   if (mode == powersave_mode_screen) return;
   printf("PWSAVE SCREEN SET %i/%i\n", (int)mode, (int)E_POWERSAVE_MODE_FREEZE);
   powersave_screen = EINA_TRUE;
   powersave_mode_screen = mode;
   _e_powersave_event_change_send(powersave_mode_screen);
   _e_powersave_mode_eval();
}

E_API void
e_powersave_mode_screen_unset(void)
{
   if (!powersave_screen) return;
   printf("PWSAVE SCREEN UNSET\n");
   powersave_screen = EINA_FALSE;
   powersave_mode_screen = E_POWERSAVE_MODE_NONE;
   _e_powersave_event_change_send(powersave_mode_screen);
   _e_powersave_mode_eval();
}

E_API E_Powersave_Mode
e_powersave_mode_screen_get(void)
{
   if (!powersave_screen) return E_POWERSAVE_MODE_NONE;
   return powersave_mode_screen;
}

E_API E_Powersave_Sleeper *
e_powersave_sleeper_new(void)
{
   E_Powersave_Sleeper *sleeper;

   sleeper = E_NEW(E_Powersave_Sleeper, 1);
   sleeper->pipe = ecore_pipe_add(_e_powersave_sleeper_cb_dummy, NULL);
   if (!sleeper->pipe)
     {
        eina_freeq_ptr_add(eina_freeq_main_get(), sleeper, free, sizeof(*sleeper));
        return NULL;
     }
   sleeper->fd = ecore_pipe_read_fd(sleeper->pipe);
   ecore_pipe_freeze(sleeper->pipe);
   powersave_sleepers = eina_list_append(powersave_sleepers, sleeper);
   return (E_Powersave_Sleeper *)sleeper;
}

E_API void
e_powersave_sleeper_free(E_Powersave_Sleeper *sleeper)
{
   if (!sleeper) return;
   ecore_pipe_del(sleeper->pipe);
   eina_freeq_ptr_add(eina_freeq_main_get(), sleeper, free, sizeof(*sleeper));
   powersave_sleepers = eina_list_remove(powersave_sleepers, sleeper);
}

E_API void
e_powersave_sleeper_sleep(E_Powersave_Sleeper *sleeper, int poll_interval, Eina_Bool allow_save)
{
   double timf;
   unsigned int tim;
   fd_set rfds, wfds, exfds;
   struct timeval tv;
   int ret;
   char buf[1] = { 1 };

   if (!sleeper) return;
   if (allow_save)
     {
        E_Powersave_Mode pm = e_powersave_mode_get();

        if (powersave_screen) pm = powersave_mode_screen;

        if (pm == E_POWERSAVE_MODE_FREEZE)
          timf = 3600.0;
        else if (pm == E_POWERSAVE_MODE_EXTREME)
          timf = (double)poll_interval / 2.0;
        else if (pm == E_POWERSAVE_MODE_HIGH)
          timf = (double)poll_interval / 4.0;
        else
          timf = (double)poll_interval / 8.0;
     }
   else
     timf = (double)poll_interval / 8.0;

   FD_ZERO(&rfds);
   FD_ZERO(&wfds);
   FD_ZERO(&exfds);
   FD_SET(sleeper->fd, &rfds);

   tim = ((timf - fmod(ecore_time_get(), timf)) * 1000000.0);
   tv.tv_sec = tim / 1000000;
   tv.tv_usec = tim % 1000000;
   for (;;)
     {
        ret = select(sleeper->fd + 1, &rfds, &wfds, &exfds, &tv);
        if ((ret == 1) && (FD_ISSET(sleeper->fd, &rfds)))
          {
             if (read(sleeper->fd, buf, 1) < 0)
               fprintf(stderr, "%s: ERROR READING FROM FD\n", __func__);
             if (buf[0] == 1) // was woken up by mainloop to do another poll
               return;
          }
        else if (ret == 0)
          return;
     }
}

E_API void
e_powersave_defer_suspend(void)
{
   powersave_deferred_suspend = EINA_TRUE;
}

E_API void
e_powersave_defer_hibernate(void)
{
   powersave_deferred_hibernate = EINA_TRUE;
}

E_API void
e_powersave_defer_cancel(void)
{
   powersave_deferred_suspend = EINA_FALSE;
   powersave_deferred_hibernate = EINA_FALSE;
}

/* local subsystem functions */

static void
_e_powersave_sleepers_wake(void)
{
   E_Powersave_Sleeper *sleeper;
   Eina_List *l;
   char buf[1] = { 1 };

   EINA_LIST_FOREACH(powersave_sleepers, l, sleeper)
     {
        if (write(ecore_pipe_write_fd(sleeper->pipe), buf, 1) < 0)
          fprintf(stderr, "%s: ERROR WRITING TO FD\n", __func__);
        ecore_pipe_write(sleeper->pipe, buf, 1);
     }
}

static void
_e_powersave_sleeper_cb_dummy(void *data EINA_UNUSED, void *buffer EINA_UNUSED, unsigned int bytes EINA_UNUSED)
{
}

static Eina_Bool
_e_powersave_cb_deferred_timer(void *data EINA_UNUSED)
{
   E_Powersave_Deferred_Action *pa;

   walking_deferred_actions++;
   EINA_LIST_FREE(deferred_actions, pa)
     {
        if (!pa->delete_me) pa->func((void *)pa->data);
        free(pa);
     }
   walking_deferred_actions--;
   if (!deferred_actions) deferred_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_powersave_mode_eval(void)
{
   double t = 0.0;
   E_Powersave_Mode mode = e_powersave_mode_get();

   if (powersave_screen) mode = powersave_mode_screen;
   switch (mode)
     {
      case E_POWERSAVE_MODE_NONE:
        t = e_config->powersave.none; /* time to defer "power expensive" activities */
        break;

      case E_POWERSAVE_MODE_LOW:
        t = e_config->powersave.low;
        break;

      case E_POWERSAVE_MODE_MEDIUM:
        t = e_config->powersave.medium;
        break;

      case E_POWERSAVE_MODE_HIGH:
        t = e_config->powersave.high;
        break;

      case E_POWERSAVE_MODE_EXTREME:
        t = e_config->powersave.extreme;
        break;

      case E_POWERSAVE_MODE_FREEZE:
        t = 3600;
        break;

      default:
        return;
        break;
     }
   if (!EINA_DBL_EQ(t, defer_time))
     {
        if (deferred_timer) ecore_timer_del(deferred_timer);
        deferred_timer = ecore_timer_loop_add(defer_time,
                                              _e_powersave_cb_deferred_timer,
                                              NULL);
        defer_time = t;
     }
}

static void
_e_powersave_event_update_free(void *data EINA_UNUSED, void *event)
{
   E_Powersave_Mode mode;

   if (powersave_force) mode = powersave_mode_force;
   else mode = powersave_mode;
   free(event);

   if (mode > E_POWERSAVE_MODE_LOW)
     {
        if (powersave_deferred_hibernate)
          e_sys_action_do(E_SYS_HIBERNATE, NULL);
        else if (powersave_deferred_suspend)
          e_sys_action_do(E_SYS_SUSPEND, NULL);
     }
   powersave_deferred_hibernate = EINA_FALSE;
   powersave_deferred_suspend = EINA_FALSE;
}

static void
_e_powersave_event_change_send(E_Powersave_Mode mode)
{
   E_Event_Powersave_Update *ev;

   printf("PWSAVE TO %i/%i\n", (int)mode, (int)E_POWERSAVE_MODE_FREEZE);
   ev = E_NEW(E_Event_Powersave_Update, 1);
   ev->mode = mode;
   ecore_event_add(E_EVENT_POWERSAVE_UPDATE, ev, _e_powersave_event_update_free, NULL);
   _e_powersave_sleepers_wake();
}
#include "config.h"
#include <Eina.h>
#include "e_macros.h"
#ifdef E_LOGGING
#include "e_log.h"
#endif
static Eina_Prefix *pfx = NULL;

static const char *_prefix_path_data = NULL;
static unsigned int _prefix_path_data_len = 0;

static void
_env_set(const char *var, const char *val)
{
   if (val)
     {
#ifdef HAVE_SETENV
        setenv(var, val, 1);
#else
        char buf[8192];

        snprintf(buf, sizeof(buf), "%s=%s", var, val);
        putenv(strdup(buf));
#endif
     }
   else
     {
#ifdef HAVE_UNSETENV
        unsetenv(var);
#else
        if (getenv(var)) putenv(var);
#endif
     }
}

/* externally accessible functions */
E_API int
e_prefix_determine(char *argv0)
{
   if (pfx) return 1;

   pfx = eina_prefix_new(argv0, e_prefix_determine,
                         "E", "enlightenment", "AUTHORS",
                         PACKAGE_BIN_DIR,
                         PACKAGE_LIB_DIR,
                         PACKAGE_DATA_DIR,
                         LOCALE_DIR);
   if (!pfx) return 0;

   _prefix_path_data = eina_prefix_data_get(pfx);
   _prefix_path_data_len = strlen(_prefix_path_data);
#ifdef E_LOGGING
   INF("=================================");
   INF("Enlightenment relocation handling");
   INF("=================================");
   INF("PREFIX:  %s", eina_prefix_get(pfx));
   INF("BINDIR:  %s", eina_prefix_bin_get(pfx));
   INF("LIBDIR:  %s", eina_prefix_lib_get(pfx));
   INF("DATADIR: %s", eina_prefix_data_get(pfx));
   INF("LOCALE:  %s", eina_prefix_locale_get(pfx));
   INF("=================================");
#else
   printf("=================================\n");
   printf("Enlightenment relocation handling\n");
   printf("=================================\n");
   printf("PREFIX:  %s\n", eina_prefix_get(pfx));
   printf("BINDIR:  %s\n", eina_prefix_bin_get(pfx));
   printf("LIBDIR:  %s\n", eina_prefix_lib_get(pfx));
   printf("DATADIR: %s\n", eina_prefix_data_get(pfx));
   printf("LOCALE:  %s\n", eina_prefix_locale_get(pfx));
   printf("=================================\n");
#endif
   _env_set("E_PREFIX", eina_prefix_get(pfx));
   _env_set("E_BIN_DIR", eina_prefix_bin_get(pfx));
   _env_set("E_LIB_DIR", eina_prefix_lib_get(pfx));
   _env_set("E_DATA_DIR", eina_prefix_data_get(pfx));
   _env_set("E_LOCALE_DIR", eina_prefix_locale_get(pfx));
   return 1;
}

EINTERN void
e_prefix_shutdown(void)
{
   if (!pfx) return;
   _prefix_path_data = NULL;
   _prefix_path_data_len = 0;
   eina_prefix_free(pfx);
   pfx = NULL;
}

E_API void
e_prefix_fallback(void)
{
}

E_API const char *
e_prefix_get(void)
{
   return eina_prefix_get(pfx);
}

E_API const char *
e_prefix_locale_get(void)
{
   return eina_prefix_locale_get(pfx);
}

E_API const char *
e_prefix_bin_get(void)
{
   return eina_prefix_bin_get(pfx);
}

E_API const char *
e_prefix_data_get(void)
{
   return eina_prefix_data_get(pfx);
}

E_API const char *
e_prefix_lib_get(void)
{
   return eina_prefix_lib_get(pfx);
}

E_API size_t
e_prefix_data_concat_len(char *dst, size_t size, const char *path, size_t path_len)
{
   return eina_str_join_len(dst, size, '/', _prefix_path_data, _prefix_path_data_len, path, path_len);
}

E_API size_t
e_prefix_data_concat(char *dst, size_t size, const char *path)
{
   return e_prefix_data_concat_len(dst, size, path, strlen(path));
}

E_API size_t EINA_PRINTF(3, 4)
e_prefix_data_snprintf(char *dst, size_t size, const char *fmt, ...)
{
   size_t off, ret;
   va_list ap;

   va_start(ap, fmt);

   off = _prefix_path_data_len + 1;
   if (size < _prefix_path_data_len + 2)
     {
        if (size > 1)
          {
             memcpy(dst, _prefix_path_data, size - 1);
             dst[size - 1] = '\0';
          }
        ret = off + vsnprintf(dst + off, size - off, fmt, ap);
        va_end(ap);
        return ret;
     }

   memcpy(dst, _prefix_path_data, _prefix_path_data_len);
   dst[_prefix_path_data_len] = '/';

   ret = off + vsnprintf(dst + off, size - off, fmt, ap);
   va_end(ap);
   return ret;
}

#include "e.h"

#define E_RANDR_CONFIG_VERSION 3

/////////////////////////////////////////////////////////////////////////
static Eina_Bool               _screen_closed(E_Randr2_Screen *s);
static void                    _animated_apply_abort(void);
static void                    _cb_delay_init_save(void *data);
static Eina_Bool               _cb_delay_timer(void *data);
static Eina_Bool               _cb_fade_animator(void *data);
static void                    _animated_apply(void);
static void                    _do_apply(void);
static void                    _info_free(E_Randr2 *r);
static E_Config_Randr2        *_config_load(void);
static void                    _config_free(E_Config_Randr2 *cfg);
static Eina_Bool               _config_save(E_Randr2 *r, E_Config_Randr2 *cfg);
static Eina_Bool               _config_update(E_Randr2 *r, E_Config_Randr2 *cfg, Eina_Bool );
static void                    _config_apply(E_Randr2 *r, E_Config_Randr2 *cfg);
static int                     _config_screen_match_count(E_Randr2 *r, E_Config_Randr2 *cfg);
static char                   *_screens_fingerprint(E_Randr2 *r);
static Eina_Bool               _screens_differ(E_Randr2 *r1, E_Randr2 *r2);
static Eina_Bool               _cb_screen_change_delay(void *data);
static E_Randr2_Screen        *_screen_output_find(const char *out);
static E_Randr2_Screen        *_screen_id_find(const char *id);
static void                    _screen_config_takeover(void);
static void                    _screen_config_do(E_Randr2_Screen *s);
static void                    _screen_config_eval(void);
static void                    _screen_config_maxsize(void);

/////////////////////////////////////////////////////////////////////////
static E_Config_DD   *_e_randr2_cfg_edd = NULL;
static E_Config_DD   *_e_randr2_cfg_screen_edd = NULL;
static Eina_List     *_ev_handlers = NULL;
static Ecore_Timer   *_screen_delay_timer = NULL;
static Eina_Bool      event_screen = EINA_FALSE;
static Eina_Bool      event_ignore = EINA_FALSE;
static Eina_Bool      initted = EINA_FALSE;
static Eina_Bool      blocked = EINA_FALSE;

/////////////////////////////////////////////////////////////////////////
E_API E_Config_Randr2 *e_randr2_cfg = NULL;
E_API E_Randr2        *e_randr2 = NULL;

E_API int              E_EVENT_RANDR_CHANGE = 0;

static void
_cb_delay_init_save(void *data EINA_UNUSED)
{
   e_randr2_config_save();
}

/////////////////////////////////////////////////////////////////////////
EINTERN Eina_Bool
e_randr2_init(void)
{
   int count;

   if (!E_EVENT_RANDR_CHANGE) E_EVENT_RANDR_CHANGE = ecore_event_type_new();
   if (blocked) return EINA_FALSE;
   if ((!e_comp->screen) || (!e_comp->screen->available) || (!e_comp->screen->available())) return EINA_FALSE;
   initted = EINA_TRUE;
   // create data descriptors for config storage
   _e_randr2_cfg_screen_edd =
     E_CONFIG_DD_NEW("E_Config_Randr2_Screen", E_Config_Randr2_Screen);
#undef T
#undef D
#define T E_Config_Randr2_Screen
#define D _e_randr2_cfg_screen_edd
   E_CONFIG_VAL(D, T, id, STR);
   E_CONFIG_VAL(D, T, rel_to, STR);
   E_CONFIG_VAL(D, T, rel_align, DOUBLE);
   E_CONFIG_VAL(D, T, mode_refresh, DOUBLE);
   E_CONFIG_VAL(D, T, mode_w, UINT);
   E_CONFIG_VAL(D, T, mode_h, INT);
   E_CONFIG_VAL(D, T, rotation, INT);
   E_CONFIG_VAL(D, T, priority, INT);
   E_CONFIG_VAL(D, T, rel_mode, UCHAR);
   E_CONFIG_VAL(D, T, enabled, UCHAR);
   E_CONFIG_VAL(D, T, ignore_disconnect, UCHAR);
   E_CONFIG_VAL(D, T, custom_label_screen, STR);
   E_CONFIG_VAL(D, T, profile, STR);
   E_CONFIG_VAL(D, T, scale_multiplier, DOUBLE);

   _e_randr2_cfg_edd = E_CONFIG_DD_NEW("E_Config_Randr2", E_Config_Randr2);
#undef T
#undef D
#define T E_Config_Randr2
#define D _e_randr2_cfg_edd
   E_CONFIG_VAL(D, T, version, INT);
   E_CONFIG_LIST(D, T, screens, _e_randr2_cfg_screen_edd);
   E_CONFIG_VAL(D, T, hotplug_response, DOUBLE);
   E_CONFIG_VAL(D, T, restore, UCHAR);
   E_CONFIG_VAL(D, T, ignore_hotplug_events, UCHAR);
   E_CONFIG_VAL(D, T, ignore_acpi_events, UCHAR);
   E_CONFIG_VAL(D, T, default_policy, UINT);

   // set up events from the driver
   if (e_comp->screen->init)
     e_comp->screen->init();
   // get current screen info
   e_randr2 = e_comp->screen->create();
   // from screen info calculate screen max dimensions
   _screen_config_maxsize();
   // load config and apply it
   e_randr2_cfg = _config_load();
   // only apply if restore is set AND at least one configured screen
   // matches one we have

   count = _config_screen_match_count(e_randr2, e_randr2_cfg);
   if (e_randr2_cfg->restore && count)
     {
        if (count != (int)eina_list_count(e_randr2->screens))
          {
             if (_config_update(e_randr2, e_randr2_cfg, 1))
               e_randr2_config_save();
          }
        _do_apply();
     }
   else
     {
        _config_update(e_randr2, e_randr2_cfg, 0);
        ecore_job_add(_cb_delay_init_save, NULL);
     }
   ecore_event_add(E_EVENT_RANDR_CHANGE, NULL, NULL, NULL);
   return EINA_TRUE;
}

EINTERN int
e_randr2_shutdown(void)
{
   if (!initted) return 0;
   initted = EINA_FALSE;
   _animated_apply_abort();
   // nuke any screen config delay handler
   if (_screen_delay_timer) ecore_timer_del(_screen_delay_timer);
   _screen_delay_timer = NULL;
   // stop listening to driver info
   if (e_comp->screen && e_comp->screen->shutdown)
     e_comp->screen->shutdown();
   // clear up all event handlers
   E_FREE_LIST(_ev_handlers, ecore_event_handler_del);
   // free up screen info
   _info_free(e_randr2);
   e_randr2 = NULL;
   _config_free(e_randr2_cfg);
   e_randr2_cfg = NULL;
   // free up data descriptors
   E_CONFIG_DD_FREE(_e_randr2_cfg_edd);
   E_CONFIG_DD_FREE(_e_randr2_cfg_screen_edd)
   return 1;
}

E_API void
e_randr2_stop(void)
{
   blocked = EINA_TRUE;
   if (initted) e_randr2_shutdown();
}

E_API Eina_Bool
e_randr2_config_save(void)
{
   // save our config
   return _config_save(e_randr2, e_randr2_cfg);
}

E_API void
e_randr2_config_apply(void)
{
   _animated_apply();
}

static E_Randr2_Screen *
_randr_screen_find(E_Randr2 *r, E_Randr2_Screen *src)
{
   Eina_List *l;
   E_Randr2_Screen *s;

   EINA_LIST_FOREACH(r->screens, l, s)
     {
        if ((s->info.name) && (src->info.name) &&
            (!strcmp(s->info.name, src->info.name)))
          return s;
     }
   return NULL;
}

static void
_randr_screen_props_copyover(E_Randr2 *rold, E_Randr2 *rnew)
{
   Eina_List *l;
   E_Randr2_Screen *s, *s2;

   // copy s->config.ignore_disconnect over from tr to e_randr2
   EINA_LIST_FOREACH(rnew->screens, l, s)
     {
        s2 = _randr_screen_find(rold, s);
        if (s2) s->config.ignore_disconnect = s2->config.ignore_disconnect;
     }
}

E_API void
e_randr2_screeninfo_update(void)
{
   E_Randr2 *tr = e_randr2;
   // re-fetch/update current screen info
   e_randr2 = e_comp->screen->create();
   if ((tr) && (e_randr2))
     {
        _randr_screen_props_copyover(tr, e_randr2);
        _info_free(tr);
     }
   _screen_config_maxsize();
}

/////////////////////////////////////////////////////////////////////////
static double _start_time = 0.0;
static Ecore_Animator *_fade_animator = NULL;
static Ecore_Timer *_apply_delay = NULL;
Eina_Bool _applying = EINA_FALSE;
static int _target_from = 0;
static int _target_to = 0;
static Evas_Object *_fade_obj = NULL;

static Eina_Bool
_screen_closed(E_Randr2_Screen *s)
{
   printf("RRR: check lid for %s...\n", s->info.name);
   if (!e_acpi_lid_is_closed()) return EINA_FALSE;
   if (s->info.is_lid)
     {
        printf("RRR:   is closed lid\n");
        return EINA_TRUE;
     }
   return EINA_FALSE;
}

static void
_animated_apply_abort(void)
{
   if (_apply_delay) ecore_timer_del(_apply_delay);
   if (_fade_animator) ecore_animator_del(_fade_animator);
   _apply_delay = NULL;
   _fade_animator = NULL;
   _applying = EINA_FALSE;
   _fade_obj = NULL;
}

static Eina_Bool
_cb_delay_timer(void *data EINA_UNUSED)
{
   _apply_delay = NULL;
   _target_from = 255;
   _target_to = 0;
   _start_time = ecore_loop_time_get();
   _fade_animator = ecore_animator_add(_cb_fade_animator, NULL);
   return EINA_FALSE;
}

static Eina_Bool
_cb_fade_animator(void *data EINA_UNUSED)
{
   double t = ecore_loop_time_get() - _start_time;
   const double duration = 0.5;
   int v;

   if (t < 0.0) t = 0.0;
   t = t / duration;
   if (t > 1.0) t = 1.0;
   t = ecore_animator_pos_map(t, ECORE_POS_MAP_SINUSOIDAL, 0.0, 0.0);
   v = _target_from + ((_target_to - _target_from) * t);
   if (t >= 1.0) v = _target_to;
   evas_object_color_set(_fade_obj, 0, 0, 0, v);
   if (v == _target_to)
     {
        if (_target_to == 255)
          {
             _apply_delay = ecore_timer_loop_add(1.0, _cb_delay_timer, NULL);
             _do_apply();
          }
        else
          {
             evas_object_del(_fade_obj);
             _fade_obj = NULL;
             _applying = EINA_FALSE;
          }
        _fade_animator = NULL;
        return EINA_FALSE;
     }
   return EINA_TRUE;
}

static void
_animated_apply(void)
{
   Evas *e;

   // fade out, config, wait 3 seconds, fade back in
   if (_applying) return;
   _applying = EINA_TRUE;
   _start_time = ecore_loop_time_get();
   e = e_comp->evas;
   _fade_obj = evas_object_rectangle_add(e);
   evas_object_pass_events_set(_fade_obj, EINA_TRUE);
   evas_object_color_set(_fade_obj, 0, 0, 0, 0);
   evas_object_move(_fade_obj, 0, 0);
   evas_object_resize(_fade_obj, 999999, 999999);
   evas_object_layer_set(_fade_obj, EVAS_LAYER_MAX);
   evas_object_show(_fade_obj);
   _target_from = 0;
   _target_to = 255;
   _fade_animator = ecore_animator_add(_cb_fade_animator, NULL);
}

static void
_do_apply(void)
{
   E_Randr2 *tr = e_randr2;
   // take current screen config and apply it to the driver
   printf("RRR: re-get info before applying..\n");
   tr = e_randr2;
   e_randr2 = e_comp->screen->create();
   if ((tr) && (e_randr2))
     {
        _randr_screen_props_copyover(tr, e_randr2);
        _info_free(tr);
     }
   _screen_config_maxsize();
   printf("RRR: apply config...\n");
   _config_apply(e_randr2, e_randr2_cfg);
   printf("RRR: takeover config...\n");
   _screen_config_takeover();
   printf("RRR: eval config...\n");
   _screen_config_eval();
   printf("RRR: really apply config...\n");
   e_comp->screen->apply();
   printf("RRR: done config...\n");
}

static void
_info_free(E_Randr2 *r)
{
   E_Randr2_Screen *s;
   E_Randr2_Mode *m;

   if (!r) return;
   // free up our randr screen data
   EINA_LIST_FREE(r->screens, s)
     {
        free(s->id);
        free(s->info.screen);
        free(s->info.name);
        free(s->info.edid);
        EINA_LIST_FREE(s->info.modes, m) free(m);
        free(s->config.relative.to);
        free(s->config.profile);
        free(s);
     }
   free(r);
}

static void
_config_upgrade(E_Config_Randr2 *cfg)
{
   if (cfg->version < 2)
     cfg->default_policy = E_RANDR2_POLICY_EXTEND;
   if (cfg->version < 3)
     cfg->hotplug_response = 1.0;
   cfg->version = E_RANDR_CONFIG_VERSION;
}

static E_Config_Randr2 *
_config_load(void)
{
   E_Config_Randr2 *cfg;

   // load config and check if version is up to date
   cfg = e_config_domain_load("e_randr2", _e_randr2_cfg_edd);
   if (cfg)
     {
        if (cfg->version < E_RANDR_CONFIG_VERSION)
          _config_upgrade(cfg);
        if (cfg->hotplug_response < 0.2)
          cfg->hotplug_response = 0.2;
        else if (cfg->hotplug_response > 9.0)
          cfg->hotplug_response = 9.0;
        printf("RRR: loaded existing config\n");
        return cfg;
     }

   // need new config
   cfg = calloc(1, sizeof(E_Config_Randr2));
   cfg->version = E_RANDR_CONFIG_VERSION;
   cfg->screens = NULL;
   cfg->restore = 1;
   cfg->ignore_hotplug_events = 0;
   cfg->ignore_acpi_events = 0;
   cfg->default_policy = E_RANDR2_POLICY_EXTEND;
   cfg->hotplug_response = 1.0;
   printf("RRR: fresh config\n");
   return cfg;
}

static void
_config_free(E_Config_Randr2 *cfg)
{
   E_Config_Randr2_Screen *cs;

   if (!cfg) return;
   // free config data
   EINA_LIST_FREE(cfg->screens, cs)
     {
        eina_stringshare_del(cs->id);
        eina_stringshare_del(cs->rel_to);
        eina_stringshare_del(cs->custom_label_screen);
        eina_stringshare_del(cs->profile);
        free(cs);
     }
   free(cfg);
}

static Eina_Bool
_config_save(E_Randr2 *r, E_Config_Randr2 *cfg)
{
   if ((!r) || (!cfg)) return EINA_FALSE;
   // save config struct to cfg file
   return e_config_domain_save("e_randr2", _e_randr2_cfg_edd, cfg);
}

static Eina_Bool
_config_ask_dialog(void *data)
{
   e_configure_registry_call("screen/screen_setup", NULL, data);
   free(data);
   return EINA_FALSE;
}

static Eina_Bool
_config_update(E_Randr2 *r, E_Config_Randr2 *cfg, Eina_Bool update_only)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   E_Config_Randr2_Screen *cs;
   Eina_Bool ret = EINA_FALSE;

   printf("--------------------------------------------------\n");
   EINA_LIST_FOREACH(r->screens, l, s)
     {
        printf("RRR: out id=%s:  connected=%i enabled=%i configured=%i\n",
               s->id, s->info.connected,
               s->config.enabled, s->config.configured);
        if (!s->id) continue;
        if (!s->info.connected) continue;
        if (_screen_closed(s)) continue;

        cs = e_randr2_config_screen_find(s, cfg);
        if (cs && update_only) continue;
        if (!cs)
          {
             cs = calloc(1, sizeof(E_Config_Randr2_Screen));
             if (cs)
               {
                  cs->id = eina_stringshare_add(s->id);
                  cfg->screens = eina_list_prepend(cfg->screens, cs);
               }
          }
        if (cs)
          {
             if (update_only)
               {
                  switch (cfg->default_policy)
                    {
                     case E_RANDR2_POLICY_EXTEND:
                       if (s->config.relative.mode < E_RANDR2_RELATIVE_TO_LEFT)
                         cs->rel_mode = E_RANDR2_RELATIVE_TO_RIGHT;
                       else
                         cs->rel_mode = s->config.relative.mode;
                       break;
                     case E_RANDR2_POLICY_CLONE:
                       cs->rel_mode = E_RANDR2_RELATIVE_CLONE;
                       break;
                     case E_RANDR2_POLICY_ASK:
                       if (starting)
                         ecore_timer_loop_add(2, _config_ask_dialog, eina_strdup(s->info.name));
                       else
                         ecore_timer_loop_add(0.01, _config_ask_dialog, eina_strdup(s->info.name));
                       EINA_FALLTHROUGH;
                       /* no break */
                     case E_RANDR2_POLICY_NONE:
                       cs->rel_mode = E_RANDR2_RELATIVE_NONE;
                       break;
                    }
               }
             else
               cs->rel_mode = s->config.relative.mode;
             if (update_only &&
               ((cfg->default_policy == E_RANDR2_POLICY_CLONE) ||
                (cfg->default_policy == E_RANDR2_POLICY_EXTEND)))
               {
                  E_Randr2_Mode *m = eina_list_data_get(s->info.modes);

                  cs->enabled = 1;
                  cs->mode_refresh = m->refresh;
                  cs->mode_w = m->w;
                  cs->mode_h = m->h;
                  if (s->config.relative.to)
                    cs->rel_to = eina_stringshare_add(s->config.relative.to);
                  else
                    {
                       /* find right-most screen */
                       E_Zone *zone = eina_list_last_data_get(e_comp->zones);
                       if (zone)
                         eina_stringshare_replace(&cs->rel_to, zone->randr2_id);
                    }
                  cs->rel_align = 0;
               }
             else
               {
                  cs->enabled = s->config.enabled;
                  cs->mode_refresh = s->config.mode.refresh;
                  cs->mode_w = s->config.mode.w;
                  cs->mode_h = s->config.mode.h;
                  if (s->config.relative.to)
                    cs->rel_to = eina_stringshare_add(s->config.relative.to);
                  cs->rel_align = s->config.relative.align;
               }
             cs->rotation = s->config.rotation;
             cs->priority = s->config.priority;
             if (cs->profile)
               {
                  printf("RRR: store config profile '%s'\n", cs->profile);
                  free(s->config.profile);
                  s->config.profile = strdup(cs->profile);
               }
             else
               {
                  free(s->config.profile);
                  s->config.profile = NULL;
               }
             printf("RRR: store scale mul %1.5f\n", cs->scale_multiplier);
             s->config.scale_multiplier = cs->scale_multiplier;
             s->config.ignore_disconnect = cs->ignore_disconnect;
             ret = EINA_TRUE;
          }
     }
   printf("--------------------------------------------------\n");
   return ret;
}

static void
_config_really_apply(E_Randr2_Screen *s, E_Config_Randr2_Screen *cs)
{
   if (cs)
     {
        s->config.enabled = EINA_TRUE;
        s->config.mode.w = cs->mode_w;
        s->config.mode.h = cs->mode_h;
        s->config.mode.refresh = cs->mode_refresh;
        s->config.mode.preferred = EINA_FALSE;
        printf("RRR: really apply rotation=%i\n", cs->rotation);
        s->config.rotation = cs->rotation;
        s->config.priority = cs->priority;
        free(s->config.relative.to);
        if (cs->rel_to) s->config.relative.to = strdup(cs->rel_to);
        else s->config.relative.to = NULL;
        s->config.relative.mode = cs->rel_mode;
        s->config.relative.align = cs->rel_align;
        free(s->config.profile);
        if (cs->profile) s->config.profile = strdup(cs->profile);
        else s->config.profile = NULL;
        s->config.scale_multiplier = cs->scale_multiplier;
        s->config.ignore_disconnect = cs->ignore_disconnect;
        printf("RRR: really apply '%s' ignore discon %i\n", s->info.name, s->config.ignore_disconnect);
     }
   else
     {
        s->config.enabled = EINA_FALSE;
        s->config.geom.x = 0;
        s->config.geom.y = 0;
        s->config.geom.w = 0;
        s->config.geom.h = 0;
        s->config.mode.w = 0;
        s->config.mode.h = 0;
        s->config.mode.refresh = 0.0;
        s->config.mode.preferred = EINA_FALSE;
        s->config.rotation = 0;
        s->config.priority = 0;
        free(s->config.relative.to);
        s->config.relative.to = NULL;
        s->config.relative.mode = E_RANDR2_RELATIVE_NONE;
        s->config.relative.align = 0.0;
        free(s->config.profile);
        s->config.profile = NULL;
        s->config.scale_multiplier = 0.0;
        s->config.ignore_disconnect = EINA_FALSE;
     }
}

static void
_config_apply(E_Randr2 *r, E_Config_Randr2 *cfg)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   E_Config_Randr2_Screen *cs;

   if ((!r) || (!cfg)) return;
   EINA_LIST_FOREACH(r->screens, l, s)
     {
        printf("RRR: apply '%s'...\n", s->info.name);
        cs = NULL;
        if ((!_screen_closed(s)) && (s->info.connected))
          cs = e_randr2_config_screen_find(s, cfg);
        printf("RRR: connected =  %i\n", s->info.connected);
        if ((cs) && (cs->enabled))
          {
             printf("RRR: ... enabled\n");
             printf("RRR: ... priority = %i\n", cs->priority);
             _config_really_apply(s, cs);
          }
        else
          {
             printf("RRR: ... disabled\n");
             if (!s->config.ignore_disconnect)
               _config_really_apply(s, NULL);
             else
               printf("RRR: ... ignore disconnected\n");
          }
        s->config.configured = EINA_TRUE;
     }
}

static int
_config_screen_match_count(E_Randr2 *r, E_Config_Randr2 *cfg)
{
   Eina_List *l, *ll;
   E_Randr2_Screen *s;
   E_Config_Randr2_Screen *cs;
   int count = 0;

   EINA_LIST_FOREACH(cfg->screens, l, cs)
     {
        if (!cs->id) continue;
        EINA_LIST_FOREACH(r->screens, ll, s)
          {
             if ((!s->id) || (!s->info.connected) ||
                 (_screen_closed(s))) continue;
             if (!strcmp(cs->id, s->id)) count++;
          }
     }
   return count;
}

static char *
_screens_fingerprint(E_Randr2 *r)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   Eina_Strbuf *buf;
   char *str;

   buf = eina_strbuf_new();
   if (!buf) return NULL;
   EINA_LIST_FOREACH(r->screens, l, s)
     {
        if (!s->id) eina_strbuf_append(buf, ":NULL:");
        else
          {
             eina_strbuf_append(buf, ":");
             eina_strbuf_append(buf, s->id);
             eina_strbuf_append(buf, ":");
// Don't do this asbecause this forces a screen replug when you open and
// close your laptop lid and if that is the only thing you open or close...
//             if (s->info.lid_closed) eina_strbuf_append(buf, ":LC:");
//             else eina_strbuf_append(buf, ":LO:");
          }
     }
   str = eina_strbuf_string_steal(buf);
   eina_strbuf_free(buf);
   return str;
}

static Eina_Bool
_screens_differ(E_Randr2 *r1, E_Randr2 *r2)
{
   char *s1, *s2;
   Eina_Bool changed = EINA_FALSE;
   Eina_List *l, *ll;
   E_Randr2_Screen *s, *ss;
   int r1_screen_num = 0, r2_screen_num = 0;

   // check monitor outputs and edids, plugged in things
   s1 = _screens_fingerprint(r1);
   s2 = _screens_fingerprint(r2);
   if ((!s1) && (!s2)) return EINA_FALSE;
   printf("RRR: check fingerprint...\n");
   if ((s1) && (s2) && (strcmp(s1, s2))) changed = EINA_TRUE;
   printf("RRR: ... fingerprint says %i\n", changed);
   free(s1);
   free(s2);
   // check screen config
   printf("RRR: screens lists %i -> %i\n", eina_list_count(r1->screens), eina_list_count(r2->screens));
   printf("RRR: --------\n");
   EINA_LIST_FOREACH(r1->screens, l, s)
     {
        if (!s->id) continue;
        if ((s->info.connected) &&
            (!((s->info.is_lid) && (s->info.lid_closed))))
          r1_screen_num++;
     }
   printf("RRR: --------\n");
   EINA_LIST_FOREACH(r2->screens, l, s)
     {
        if (!s->id) continue;
        if ((s->info.connected) &&
            (!((s->info.is_lid) && (s->info.lid_closed))))
          r2_screen_num++;
     }
   printf("RRR: --------\n");
   printf("RRR: screens %i -> %i\n", r1_screen_num, r2_screen_num);
   printf("RRR: --------\n");
   EINA_LIST_FOREACH(r2->screens, l, s)
     {
        if (s->id)
          printf("RRR: look at r2 screen ID %s\n", s->id);
        else
          printf("RRR: look at r2 screen ID NIL\n");
        if (!s->id) continue;
        EINA_LIST_FOREACH(r1->screens, ll, ss)
          {
             if (ss->id)
               printf("RRR: look at r1 screen ID %s\n", ss->id);
             else
               printf("RRR: look at r1 screen ID NIL\n");
             if ((ss->id) && (!strcmp(s->id, ss->id))) break;
             ss = NULL;
          }
        if (!ss)
          {
             printf("RRR: do change because cannot find screen matching ID %s\n", s->id);
             changed = EINA_TRUE;
          }
        else if ((s->config.geom.x != ss->config.geom.x) ||
                 (s->config.geom.y != ss->config.geom.y) ||
                 (s->config.geom.w != ss->config.geom.w) ||
                 (s->config.geom.h != ss->config.geom.h) ||
                 (s->config.mode.w != ss->config.mode.w) ||
                 (s->config.mode.h != ss->config.mode.h) ||
                 (s->config.enabled != ss->config.enabled) ||
                 (s->config.rotation != ss->config.rotation))
          {
             printf("RRR: do change because geom/mode/rotation don't match\n");
             printf("RRR: geom=%i,%i-%ix%i mode=%ix%i enable=%i rot=%i  !=  geom=%i,%i-%ix%i mode=%ix%i enable=%i rot=%i\n",
                    s->config.geom.x, s->config.geom.y,
                    s->config.geom.w, s->config.geom.h,
                    s->config.mode.w, s->config.mode.h,
                    s->config.enabled, s->config.rotation,
                    ss->config.geom.x, ss->config.geom.y,
                    ss->config.geom.w, ss->config.geom.h,
                    ss->config.mode.w, ss->config.mode.h,
                    ss->config.enabled, ss->config.rotation);
             changed = EINA_TRUE;
          }
        else
          {
             if (r1_screen_num != r2_screen_num)
               {
                  printf("RRR: do change because screen count changed\n");
                  changed = EINA_TRUE;
               }
             else
               {
                  if ((r2_screen_num != 1) &&
                      (s->info.lid_closed != ss->info.lid_closed))
                    {
                       printf("RRR: change because laptop lid open/close and number of screens > 1\n");
                       changed = EINA_TRUE;
                    }
                  else
                    printf("RRR: skip change because of single laptop lid\n");
               }
          }
     }
   printf("RRR: --------\n");
   printf("RRR: changed = %i\n", changed);
   return changed;
}

static Eina_Bool
_cb_deferred_suspend_screen_change(void *data EINA_UNUSED)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   int lids = 0;
   int ext_screens = 0;

   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (s->info.is_lid) lids++;
        else if ((s->config.enabled) &&
                 (s->config.geom.w > 0) &&
                 (s->config.geom.h > 0))
          ext_screens++;
     }
   printf("RRR: =========================== deferred suspend.... %i %i\n", lids, ext_screens);
   if ((lids > 0) && (ext_screens == 0))
     {
        if ((e_config->screensaver_suspend_on_ac) ||
            (e_powersave_mode_get() > E_POWERSAVE_MODE_LOW))
          {
             printf("RRR: =========================== powermd low / suspend on ac");
             e_sys_action_do(E_SYS_SUSPEND, NULL);
          }
     }
   return EINA_FALSE;
}

static Eina_Bool
_cb_screen_change_delay(void *data EINA_UNUSED)
{
   Eina_Bool change = EINA_FALSE;
   _screen_delay_timer = NULL;
   printf("RRR: ... %i %i\n", event_screen, event_ignore);
   // if we had a screen plug/unplug etc. event and we shouldn't ignore it...
   if ((event_screen) && (!event_ignore))
     {
        Eina_List *l;
        E_Randr2_Screen *s;
        int lid_screens = 0;
        int close_lid_screens = 0;
        int external_screens = 0;
        int prev_external_screens = 0;
        E_Randr2 *rtemp;

        printf("RRR: reconfigure screens due to event...\n");
        rtemp = e_comp->screen->create();
        if (rtemp)
          {
             if (e_randr2) _randr_screen_props_copyover(e_randr2, rtemp);
             if (_screens_differ(e_randr2, rtemp)) change = EINA_TRUE;
             if (e_randr2_cfg->default_policy != E_RANDR2_POLICY_NONE)
               {
                  if (_config_update(rtemp, e_randr2_cfg, 1))
                    {
                       e_randr2_config_save();
                       if (e_randr2_cfg->default_policy != E_RANDR2_POLICY_ASK)
                         change = EINA_TRUE;
                    }
               }
             EINA_LIST_FOREACH(rtemp->screens, l, s)
               {
                  printf("RRR: scr: %s lid=%i conn=%i\n", s->id, s->info.is_lid, s->info.connected);
//                  if (!s->id) continue;
                  if (s->info.is_lid)
                    {
                       printf("RRR: is lid, lid++\n");
                       lid_screens++;
                       if ((s->info.lid_closed) && (!s->config.ignore_disconnect))
                         {
                            printf("RRR: is lid, is closed, closed++\n");
                            close_lid_screens++;
                         }
                    }
                  else
                    {
                       if (s->info.connected)
                         {
                            printf("RRR: is not lid, is connected, ext++\n");
                            external_screens++;
                         }
                       else
                         {
                            printf("RRR: is not lid, is not connected\n");
                         }
                    }
               }
             EINA_LIST_FOREACH(e_randr2->screens, l, s)
               {
                  printf("RRR: prev_scr: %s lid=%i conn=%i\n", s->id, s->info.is_lid, s->info.connected);
//                  if (!s->id) continue;
                  if (!s->info.is_lid)
                    {
                       if (s->info.connected) prev_external_screens++;
                    }
               }
             printf("RRR: lids=%i closed=%i ext=%i prev_ext=%i\n", lid_screens, close_lid_screens, external_screens, prev_external_screens);
             _info_free(rtemp);
          }
        printf("RRR: change = %i\n", change);
        // we plugged or unplugged some monitor - re-apply config so
        // known screens can be configured
        if (change)
          {
             if ((lid_screens > 0) && (close_lid_screens == lid_screens) &&
                 (external_screens == 0) && (external_screens == prev_external_screens))
               {
                  printf("RRR: skip change with all lids closed and no ext\n");
                  change = EINA_FALSE;
                  e_screensaver_now_set(EINA_TRUE);
                  e_screensaver_attrs_set
                    (1, e_config->screensaver_blanking,
                     e_config->screensaver_expose);
                  e_screensaver_update();
                  e_dpms_force_update();
                  e_screensaver_activate();
                  e_screensaver_eval(EINA_TRUE);
                  // force dpms...
               }
             if ((lid_screens > 0) && (close_lid_screens < lid_screens) &&
                 (external_screens == 0) && (external_screens == prev_external_screens))
               {
                  printf("RRR: skip change with lid screens open and no ext\n");
                  change = EINA_FALSE;
                  e_screensaver_now_set(EINA_FALSE);
                  e_screensaver_attrs_set
                    (e_config->screensaver_timeout,
                     e_config->screensaver_blanking,
                     e_config->screensaver_expose);
                  e_screensaver_update();
                  e_dpms_force_update();
                  e_screensaver_deactivate();
               }
             if (change)
               e_randr2_config_apply();
          }
        if (change)
          {
             if ((lid_screens > 0) && (close_lid_screens == lid_screens) &&
                 (external_screens == 0))
               {
                  printf("RRR: have all closed laptop screens and no external\n");
                  if ((e_config->screensaver_suspend_on_ac) ||
                      (e_powersave_mode_get() > E_POWERSAVE_MODE_LOW))
                    {
                       printf("RRR: we should try and suspend now because on ac or suspend on ac is on\n");
                       ecore_timer_add(1.0, _cb_deferred_suspend_screen_change, NULL);
                    }
               }
          }
     }
   // update screen info after the above apply or due to external changes
   e_randr2_screeninfo_update();
   e_comp_canvas_resize(e_randr2->w, e_randr2->h);
   e_randr2_screens_setup(e_comp->w, e_comp->h);
   e_comp_canvas_update();
   // tell the rest of e some screen reconfigure thing happened
   ecore_event_add(E_EVENT_RANDR_CHANGE, NULL, NULL, NULL);
   event_screen = EINA_FALSE;
   event_ignore = EINA_FALSE;
   return EINA_FALSE;
}

static E_Randr2_Screen *
_screen_output_find(const char *out)
{
   E_Randr2_Screen *s;
   Eina_List *l;
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (!strcmp(s->info.name, out)) return s;
     }
   return NULL;
}

static E_Randr2_Screen *
_screen_id_find(const char *id)
{
   E_Randr2_Screen *s;
   Eina_List *l;
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (!strcmp(s->id, id)) return s;
     }
   return NULL;
}

static void
_screen_config_takeover(void)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        s->config.configured = EINA_TRUE;
     }
}

static E_Config_Randr2_Screen *_config_screen_string_find(E_Config_Randr2 *cfg, const char *id);
static E_Randr2_Screen *_screen_fuzzy_fallback_find(E_Config_Randr2 *cfg, const char *id);

static E_Config_Randr2_Screen *
_config_screen_string_find(E_Config_Randr2 *cfg, const char *id)
{
   Eina_List *l;
   E_Config_Randr2_Screen *cs;

   if ((!id) || (!cfg)) return NULL;
   EINA_LIST_FOREACH(cfg->screens, l, cs)
     {
        if (!cs->id) continue;
        if (!strcmp(cs->id, id)) return cs;
     }
   return NULL;
}

static E_Randr2_Screen *
_screen_fuzzy_fallback_find(E_Config_Randr2 *cfg, const char *id)
{
   E_Randr2_Screen *s = NULL;
   char *p, *name;

   // strip out everythng in the string from / on as that is edid
   // and fall back to finding just the output name in the rel
   // to identifier, rather than the specific screen id
   p = strchr(id, '/');
   if (!p) return NULL;
   name = alloca((p - id) + 1);
   strncpy(name, id, p - id);
   name[p - id] = 0;

   s = _screen_id_find(id);
   if (!s) s = _screen_output_find(name);
   if (!s)
     {
        E_Config_Randr2_Screen *cs;

        cs = _config_screen_string_find(cfg, name);
        if ((cs) && (cs->id)) return _screen_fuzzy_fallback_find(cfg, cs->id);
     }
   return s;
}

static E_Config_Randr2_Screen *
_config_screen_clone_resolve(E_Config_Randr2 *cfg, const char *id, int *x, int *y)
{
   E_Config_Randr2_Screen *cs;
   E_Randr2_Screen *s;
   char *p, *name;

   cs = _config_screen_string_find(cfg, id);
   if (!cs) return NULL;

   name = alloca(strlen(cs->id) + 1);
   strcpy(name, cs->id);
   if ((p = strchr(name, '/'))) *p = 0;

   s = _screen_id_find(cs->id);
   if (!s) s = _screen_output_find(name);
   if (!s)
     {
        if ((cs->rel_mode == E_RANDR2_RELATIVE_CLONE) && (cs->rel_to))
          return _config_screen_clone_resolve(cfg, cs->rel_to, x, y);
        return NULL;
     }
   printf("RRR: resolve clone... [%s]\n", cs->id);
   _screen_config_do(s);
   *x = s->config.geom.x;
   *y = s->config.geom.y;
   return cs;
}

static Eina_List *
_screen_clones_find(Eina_List *screens, E_Randr2_Screen *s)
{
   Eina_List *clones = NULL, *l;
   E_Randr2_Screen *s2, *sclone;
   Eina_Bool added;

   // go over all screens and as long as we have found another screen that is
   // cloned from something in the clone set, then keep looking.
   clones = eina_list_append(clones, s);
   added = EINA_TRUE;
   while (added)
     {
        added = EINA_FALSE;
        // og over all screens
        EINA_LIST_FOREACH(screens, l, s2)
          {
             // skip looking at screens we already have in our set
             if (eina_list_data_find(clones, s2)) continue;
             // if this clones another screen... get that as sclone
             if ((s2->config.relative.to) &&
                 (s2->config.relative.mode == E_RANDR2_RELATIVE_CLONE))
               {
                  sclone = _screen_fuzzy_fallback_find(e_randr2_cfg,
                                                       s2->config.relative.to);
                  if (!sclone) continue;
                  // if the screen s2 is relative to is not in our list, add
                  // s2 to our clones list as well
                  if (!eina_list_data_find(clones, sclone))
                    {
                       clones = eina_list_append(clones, sclone);
                       added = EINA_TRUE;
                    }
                  if (!eina_list_data_find(clones, s2))
                    {
                       clones = eina_list_append(clones, s2);
                       added = EINA_TRUE;
                    }
                  if (added)
                    {
                       // break our list walk, and iterate while again
                       break;
                    }
              }
          }
     }
   return clones;
}

static void
_screen_clones_common_sync(Eina_List *clones)
{
   E_Randr2_Screen *s, *sbase = NULL;
   E_Randr2_Mode *m, *m2, *mcommon = NULL;
   Eina_List *modes = NULL, *l, *l2, *l3;
   Eina_Bool common;
   int d, diff = 0x7fffffff;

   // find the base/root/master screen for clones
   printf("RRR: find base/root for list=%p count=%i\n", clones, eina_list_count(clones));
   EINA_LIST_FOREACH(clones, l, s)
     {
        // simple check - if it doesn't clone something else - then it's
        // the master (doesn't handle missing screens)
        printf("RRR: clone=%p mode=%i\n", s, s->config.relative.mode);
        if ((s->config.relative.mode != E_RANDR2_RELATIVE_CLONE) &&
            (s->config.relative.mode != E_RANDR2_RELATIVE_NONE) &&
            (s->config.relative.mode != E_RANDR2_RELATIVE_UNKNOWN))
          {
             printf("RRR: got it\n");
             sbase = s;
             break;
          }
     }
   if (!sbase) return;
   // store all modes that master/base screen has and we'll "weed them out"
   EINA_LIST_FOREACH(sbase->info.modes, l, m)
     {
        modes = eina_list_append(modes, m);
     }
   // ensure it's configured
   printf("RRR: clone common sync... %p %p\n", sbase, s);
   _screen_config_do(sbase);
again:
   // we took all modes in the "master"
   EINA_LIST_FOREACH(modes, l, m)
     {
        // find all other screens in the clone list and...
        EINA_LIST_FOREACH(clones, l2, s)
          {
             if (s == sbase) continue; // skip if its the base/master
             // see if mode "m" is common to other clones
             common = EINA_FALSE;
             EINA_LIST_FOREACH(s->info.modes, l3, m2)
               {
                  /// only check res, not refresh
                  if ((m->w == m2->w) && (m->h == m2->h))
                    {
                       common = EINA_TRUE;
                       break;
                    }
               }
             // m is not common with modes in screen s - so removed it
             if (!common)
               {
                  modes = eina_list_remove_list(modes, l);
                  // the list is no longer save to walk - so let's just
                  // walk it again from scratch
                  goto again;
               }
          }
     }
   // no modes in common :(
   if (!modes) return;
   common = EINA_FALSE;
   EINA_LIST_FOREACH(modes, l, m)
     {
        // one of the common modes matches the base config - we are ok
        if ((m->w == sbase->config.mode.w) && (m->h == sbase->config.mode.h))
          {
             modes = eina_list_free(modes);
             return;
          }
     }
   // find a common mode since current config doesn't match
   EINA_LIST_FOREACH(modes, l, m)
     {
        // calculate a "difference" based on a combo of diff in area pixels
        // actual resolutin pixels (squared) and refresh delta * 10 squared
        d = abs((sbase->config.mode.w * sbase->config.mode.h) - (m->w * m->h));
        d += (sbase->config.mode.w - m->w) * (sbase->config.mode.w - m->w);
        d += (sbase->config.mode.h - m->h) * (sbase->config.mode.h - m->h);
        d += ((sbase->config.mode.refresh - m->refresh) * 10) *
             ((sbase->config.mode.refresh - m->refresh) * 10);
        if ((m->w > sbase->config.mode.w) || (m->h > sbase->config.mode.h))
          continue;
        if (d < diff)
          {
             diff = d;
             mcommon = m;
          }
     }
   modes = eina_list_free(modes);
   // no common mode with least difference found
   if (!mcommon) return;
   // we have a common mode - apply it to the base screen
   s = sbase;
   s->config.mode.w = mcommon->w;
   s->config.mode.h = mcommon->h;
   s->config.mode.refresh = mcommon->refresh;
   printf("RRR: clones common sync=%ix%i rotation=%i\n", s->config.mode.w, s->config.mode.h, s->config.rotation);
   if ((s->config.rotation == 0) || (s->config.rotation == 180))
     {
        s->config.geom.w = s->config.mode.w;
        s->config.geom.h = s->config.mode.h;
     }
   else
     {
        s->config.geom.w = s->config.mode.h;
        s->config.geom.h = s->config.mode.w;
     }
}

static int _config_do_recurse = 0;

static void
_screen_config_do(E_Randr2_Screen *s)
{
   E_Randr2_Screen *s2 = NULL;
   Eina_List *cloneset;

   printf("RRR: screen do '%s'\n", s->info.name);
   if (_config_do_recurse > 5)
     {
        printf("RRR: screen config loop!\n");
        return;
     }
   _config_do_recurse++;
   // find dependent clones and find a common config res
   cloneset = _screen_clones_find(e_randr2->screens, s);
   if (cloneset)
     {
        _screen_clones_common_sync(cloneset);
        eina_list_free(cloneset);
     }
   // if screen has a dependency...
   if ((s->config.relative.mode != E_RANDR2_RELATIVE_UNKNOWN) &&
       (s->config.relative.mode != E_RANDR2_RELATIVE_NONE) &&
       (s->config.relative.to))
     {
        // if this screen is relative TO something (clone or left/right etc.
        // then calculate what it is relative to first
        s2 = _screen_fuzzy_fallback_find(e_randr2_cfg, s->config.relative.to);
        printf("RRR: '%s' is relative to '%s'\n", s->info.name, s2 ? s2->info.name : "NONE");
        if (s2)
          {
             _screen_config_do(s2);
             if (!s2->config.enabled) s2 = NULL;
          }
     }
   s->config.geom.x = 0;
   s->config.geom.y = 0;
   printf("RRR: screen config do %ix%i rotation=%i\n", s->config.mode.w, s->config.mode.h, s->config.rotation);
   if ((s->config.rotation == 0) || (s->config.rotation == 180))
     {
        s->config.geom.w = s->config.mode.w;
        s->config.geom.h = s->config.mode.h;
     }
   else
     {
        s->config.geom.w = s->config.mode.h;
        s->config.geom.h = s->config.mode.w;
     }
   if (s2)
     {
        if (s->config.relative.mode == E_RANDR2_RELATIVE_CLONE)
          {
             printf("RRR: clone relative\n");
             s->config.geom.x = s2->config.geom.x;
             s->config.geom.y = s2->config.geom.y;
             s->config.geom.w = s2->config.geom.w;
             s->config.geom.h = s2->config.geom.h;
             s->config.mode.w = s2->config.mode.w;
             s->config.mode.h = s2->config.mode.h;
             printf("RRR: screen config do rotation=%i\n", s2->config.rotation);
             s->config.rotation = s2->config.rotation;
             s->config.mode.refresh = s2->config.mode.refresh;
          }
        else if (s->config.relative.mode == E_RANDR2_RELATIVE_TO_LEFT)
          {
             printf("RRR: to left relative\n");
             s->config.geom.x = s2->config.geom.x - s->config.geom.w;
             s->config.geom.y = s2->config.geom.y +
             ((s2->config.geom.h - s->config.geom.h) *
              s->config.relative.align);
          }
        else if (s->config.relative.mode == E_RANDR2_RELATIVE_TO_RIGHT)
          {
             printf("RRR: to right relative\n");
             s->config.geom.x = s2->config.geom.x + s2->config.geom.w;
             s->config.geom.y = s2->config.geom.y +
             ((s2->config.geom.h - s->config.geom.h) *
              s->config.relative.align);
          }
        else if (s->config.relative.mode == E_RANDR2_RELATIVE_TO_ABOVE)
          {
             printf("RRR: to above relative\n");
             s->config.geom.x = s2->config.geom.x +
             ((s2->config.geom.w - s->config.geom.w) *
              s->config.relative.align);
             s->config.geom.y = s2->config.geom.y - s->config.geom.h;
          }
        else if (s->config.relative.mode == E_RANDR2_RELATIVE_TO_BELOW)
          {
             printf("RRR: to below relative\n");
             s->config.geom.x = s2->config.geom.x +
             ((s2->config.geom.w - s->config.geom.w) *
              s->config.relative.align);
             s->config.geom.y = s2->config.geom.y + s2->config.geom.h;
          }
     }
   else
     {
        if ((s->config.relative.mode == E_RANDR2_RELATIVE_CLONE) &&
            (s->config.relative.to))
          {
             E_Config_Randr2_Screen *cs;
             int x = 0, y = 0;

             cs = _config_screen_clone_resolve(e_randr2_cfg,
                                               s->config.relative.to, &x, &y);
             printf("RRR: clone relative - config %p\n", cs);
             if (cs)
               {
                  s->config.geom.x = x;
                  s->config.geom.y = y;
                  s->config.mode.w = cs->mode_w;
                  s->config.mode.h = cs->mode_h;
                  printf("RRR: clone cs rotation=%i\n", cs->rotation);
                  s->config.rotation = cs->rotation;
                  s->config.mode.refresh = cs->mode_refresh;
                  if ((cs->rotation == 0) || (cs->rotation == 180))
                    {
                       s->config.geom.w = s->config.mode.w;
                       s->config.geom.h = s->config.mode.h;
                    }
                  else
                    {
                       s->config.geom.w = s->config.mode.h;
                       s->config.geom.h = s->config.mode.w;
                    }
               }
          }
     }
   _config_do_recurse--;
}

static void
_screen_config_eval(void)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   int minx, miny, maxx, maxy;

   printf("RRR:--------------------------------1\n");
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (s->config.configured)
          {
             printf("RRR: screen config eval this...\n");
             _screen_config_do(s);
          }
     }
   minx = 65535;
   miny = 65535;
   maxx = -65536;
   maxy = -65536;
   printf("RRR:--------------------------------2\n");
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (!s->config.enabled) continue;
        if (s->config.geom.x < minx) minx = s->config.geom.x;
        if (s->config.geom.y < miny) miny = s->config.geom.y;
        if ((s->config.geom.x + s->config.geom.w) > maxx)
          maxx = s->config.geom.x + s->config.geom.w;
        if ((s->config.geom.y + s->config.geom.h) > maxy)
          maxy = s->config.geom.y + s->config.geom.h;
        printf("RRR: s: '%s' @ %i %i - %ix%i\n",
               s->info.name,
               s->config.geom.x, s->config.geom.y,
               s->config.geom.w, s->config.geom.h);
     }
   printf("RRR:--- %i %i -> %i %i\n", minx, miny, maxx, maxy);
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        s->config.geom.x -= minx;
        s->config.geom.y -= miny;
     }
   e_randr2->w = maxx - minx;
   e_randr2->h = maxy - miny;
}

static void
_screen_config_maxsize(void)
{
   Eina_List *l;
   E_Randr2_Screen *s;
   int maxx, maxy;

   maxx = -65536;
   maxy = -65536;
   printf("RRR:-------------------------------- 2\n");
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (!s->config.enabled) continue;
        if ((s->config.geom.x + s->config.geom.w) > maxx)
          maxx = s->config.geom.x + s->config.geom.w;
        if ((s->config.geom.y + s->config.geom.h) > maxy)
          maxy = s->config.geom.y + s->config.geom.h;
        printf("RRR: '%s': %i %i %ix%i\n",
               s->info.name,
               s->config.geom.x, s->config.geom.y,
               s->config.geom.w, s->config.geom.h);
     }
   printf("RRR: result max: %ix%i\n", maxx, maxy);
   e_randr2->w = maxx;
   e_randr2->h = maxy;
}

static int
_screen_sort_cb(const void *data1, const void *data2)
{
   const E_Randr2_Screen *s1 = data1, *s2 = data2;
   int dif;

   dif = -(s1->config.priority - s2->config.priority);
   if (dif == 0)
     {
        dif = s1->config.geom.x - s2->config.geom.x;
        if (dif == 0)
          dif = s1->config.geom.y - s2->config.geom.y;
     }
   return dif;
}

E_API void
e_randr2_screen_refresh_queue(Eina_Bool lid_event)
{
   // delay handling of screen shances as they can come in in a series over
   // time and thus we can batch up responding to them by waiting 1.0 sec
   if (_screen_delay_timer)
     ecore_timer_loop_reset(_screen_delay_timer);
   else
     {
        double t = 1.0;

        if ((e_randr2_cfg) && (e_randr2_cfg->hotplug_response > 0.2))
          t = e_randr2_cfg->hotplug_response;
        _screen_delay_timer = ecore_timer_loop_add(t, _cb_screen_change_delay, NULL);
     }
   event_screen |= !!lid_event;
}

E_API E_Config_Randr2_Screen *
e_randr2_config_screen_find(E_Randr2_Screen *s, E_Config_Randr2 *cfg)
{
   Eina_List *l;
   E_Config_Randr2_Screen *cs;

   if ((!s) || (!cfg)) return NULL;
   if (!s->id) return NULL;
   EINA_LIST_FOREACH(cfg->screens, l, cs)
     {
        if (!cs->id) continue;
        if (!strcmp(cs->id, s->id)) return cs;
     }
   return NULL;
}

E_API void
e_randr2_screens_setup(int rw, int rh)
{
   int i;
   E_Screen *screen;
   Eina_List *screens = NULL, *screens_rem;
   Eina_List *all_screens = NULL;
   Eina_List *l, *ll;
   E_Randr2_Screen *s, *s2, *s_chosen;
   Eina_Bool removed;

   if ((!e_randr2) || (!e_randr2->screens)) goto out;
   // put screens in tmp list
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if ((s->config.enabled) &&
            (s->config.geom.w > 0) &&
            (s->config.geom.h > 0))
          {
             screens = eina_list_append(screens, s);
          }
     }
   // remove overlapping screens - if a set of screens overlap, keep the
   // smallest/lowest res
   do
     {
        removed = EINA_FALSE;

        EINA_LIST_FOREACH(screens, l, s)
          {
             screens_rem = NULL;

             EINA_LIST_FOREACH(l->next, ll, s2)
               {
                  if (E_INTERSECTS(s->config.geom.x, s->config.geom.y,
                                   s->config.geom.w, s->config.geom.h,
                                   s2->config.geom.x, s2->config.geom.y,
                                   s2->config.geom.w, s2->config.geom.h))
                    {
                       if (!screens_rem)
                         screens_rem = eina_list_append(screens_rem, s);
                       screens_rem = eina_list_append(screens_rem, s2);
                    }
               }
             // we have intersecting screens - choose the lowest res one
             if (screens_rem)
               {
                  removed = EINA_TRUE;
                  // find the smallest screen (chosen one)
                  s_chosen = NULL;
                  EINA_LIST_FOREACH(screens_rem, ll, s2)
                    {
                       if (!s_chosen) s_chosen = s2;
                       else
                         {
                            if ((s_chosen->config.geom.w *
                                 s_chosen->config.geom.h) >
                                (s2->config.geom.w *
                                 s2->config.geom.h))
                              s_chosen = s2;
                         }
                    }
                  // remove all from screens but the chosen one
                  EINA_LIST_FREE(screens_rem, s2)
                    {
                       if (s2 != s_chosen)
                         screens = eina_list_remove_list(screens, l);
                    }
                  // break our list walk and try again
                  break;
               }
          }
     }
   while (removed);
   // sort screens by priority etc.
   screens = eina_list_sort(screens, 0, _screen_sort_cb);
   i = 0;
   EINA_LIST_FOREACH(screens, l, s)
     {
        screen = E_NEW(E_Screen, 1);
        screen->escreen = screen->screen = i;
        screen->x = s->config.geom.x;
        screen->y = s->config.geom.y;
        screen->w = s->config.geom.w;
        screen->h = s->config.geom.h;
        if (s->id) screen->id = strdup(s->id);

        all_screens = eina_list_append(all_screens, screen);
        printf("xinerama screen %i %i %ix%i\n", screen->x, screen->y, screen->w, screen->h);
        INF("E INIT: XINERAMA SCREEN: [%i][%i], %ix%i+%i+%i",
            i, i, screen->w, screen->h, screen->x, screen->y);
        i++;
     }
   eina_list_free(screens);
   // if we have NO screens at all (above - i will be 0) AND we have no
   // existing screens set up in xinerama - then just say root window size
   // is the entire screen. this should handle the case where you unplug ALL
   // screens from an existing setup (unplug external monitors and/or close
   // laptop lid), in which case as long as at least one screen is configured
   // in xinerama, it will be left-as is until next time we re-eval screen
   // setup and have at least one screen
   printf("xinerama setup............... %i %p\n", i, e_xinerama_screens_all_get());
   if ((i == 0) && (!e_xinerama_screens_all_get()))
     {
out:
        screen = E_NEW(E_Screen, 1);
        screen->escreen = screen->screen = 0;
        screen->x = 0;
        screen->y = 0;
        if ((rw > 0) && (rh > 0))
          screen->w = rw, screen->h = rh;
        else
          ecore_evas_screen_geometry_get(e_comp->ee, NULL, NULL, &screen->w, &screen->h);
        all_screens = eina_list_append(all_screens, screen);
     }
   e_xinerama_screens_set(all_screens);
}

E_API E_Randr2_Screen *
e_randr2_screen_id_find(const char *id)
{
   Eina_List *l;
   E_Randr2_Screen *s;

   if (!id) return NULL;
   EINA_LIST_FOREACH(e_randr2->screens, l, s)
     {
        if (!strcmp(id, s->id)) return s;
     }
   return NULL;
}

E_API double
e_randr2_screen_dpi_get(E_Randr2_Screen *s)
{
   double dpi1, dpi2;

   if ((s->info.size.w <= 0) || (s->info.size.h <= 0)) return 0.0;
   dpi1 = (25.4 * (double)(s->config.mode.w)) / (double)(s->info.size.w);
   dpi2 = (25.4 * (double)(s->config.mode.h)) / (double)(s->info.size.h);
   return (dpi1 + dpi2) / 2.0;
}

static int
_modelist_sort(const void *a, const void *b)
{
   const E_Randr2_Mode *ma = a, *mb = b;

   /* largest resolutions first */
   if ((ma->w * ma->h) > (mb->w * mb->h)) return -1;
   /* highest refresh first */
   if (ma->refresh > mb->refresh) return -1;
   return 1;
}

EAPI void
e_randr2_screen_modes_sort(E_Randr2_Screen *s)
{
   EINA_SAFETY_ON_NULL_RETURN(s);
   s->info.modes = eina_list_sort(s->info.modes, 0, _modelist_sort);
}
#include "e.h"

#define REMEMBER_HIERARCHY 1
#define REMEMBER_SIMPLE    0

E_API int E_EVENT_REMEMBER_UPDATE = -1;
E_API E_Config_DD *e_remember_edd = NULL; //created in e_config.c

typedef struct _E_Remember_List E_Remember_List;

struct _E_Remember_List
{
   Eina_List *list;
};

/* local subsystem functions */
static void        _e_remember_free(E_Remember *rem);
static void        _e_remember_update(E_Client *ec, E_Remember *rem);
static E_Remember *_e_remember_find(E_Client *ec, int check_usable, Eina_Bool sr);
static void        _e_remember_cb_hook_pre_post_fetch(void *data, E_Client *ec);
static void        _e_remember_cb_hook_eval_post_new_client(void *data, E_Client *ec);
static void        _e_remember_init_edd(void);
static Eina_Bool   _e_remember_restore_cb(void *data, int type, void *event);

/* local subsystem globals */
static Eina_List *hooks = NULL;
static E_Config_DD *e_remember_list_edd = NULL;
static E_Remember_List *remembers = NULL;
static Eina_List *handlers = NULL;
static Ecore_Idler *remember_idler = NULL;
static Eina_List *remember_idler_list = NULL;

/* static Eina_List *e_remember_restart_list = NULL; */

/* externally accessible functions */
EINTERN int
e_remember_init(E_Startup_Mode mode)
{
   Eina_List *ll, *l = NULL;
   E_Remember *rem;
   E_Client_Hook *h;

   if (mode == E_STARTUP_START)
     {
        EINA_LIST_FOREACH_SAFE(e_config->remembers, l, ll, rem)
          {
             if ((rem->apply & E_REMEMBER_APPLY_RUN) && (rem->prop.command))
               {
                  if (!e_util_exe_safe_run(rem->prop.command, NULL))
                    {
                       e_util_dialog_show(_("Run Error"),
                                          _("Enlightenment was unable to fork a child process:<ps/>"
                                            "<ps/>"
                                            "%s<ps/>"),
                                          rem->prop.command);
                    }
               }
             if (rem->apply & E_REMEMBER_APPLY_UUID)
               {
                  e_config->remembers = eina_list_remove_list(e_config->remembers, l);
                  _e_remember_free(rem);
               }
          }
     }
   E_EVENT_REMEMBER_UPDATE = ecore_event_type_new();

   h = e_client_hook_add(E_CLIENT_HOOK_EVAL_PRE_POST_FETCH,
                         _e_remember_cb_hook_pre_post_fetch, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_EVAL_POST_NEW_CLIENT,
                         _e_remember_cb_hook_eval_post_new_client, NULL);
   if (h) hooks = eina_list_append(hooks, h);
   h = e_client_hook_add(E_CLIENT_HOOK_UNIGNORE,
                         _e_remember_cb_hook_pre_post_fetch, NULL);
   h = e_client_hook_add(E_CLIENT_HOOK_UNIGNORE,
                         _e_remember_cb_hook_eval_post_new_client, NULL);
   if (h) hooks = eina_list_append(hooks, h);

   _e_remember_init_edd();
   remembers = e_config_domain_load("e_remember_restart", e_remember_list_edd);

   if (remembers)
     {
        handlers = eina_list_append
            (handlers, ecore_event_handler_add
              (E_EVENT_MODULE_INIT_END, _e_remember_restore_cb, NULL));
     }

   return 1;
}

EINTERN int
e_remember_shutdown(void)
{
   E_FREE_LIST(hooks, e_client_hook_del);

   E_CONFIG_DD_FREE(e_remember_edd);
   E_CONFIG_DD_FREE(e_remember_list_edd);

   E_FREE_LIST(handlers, ecore_event_handler_del);
   if (remember_idler) ecore_idler_del(remember_idler);
   remember_idler = NULL;
   remember_idler_list = eina_list_free(remember_idler_list);

   return 1;
}

E_API void
e_remember_internal_save(void)
{
   const Eina_List *l;
   E_Client *ec;
   E_Remember *rem;

   //printf("internal save %d\n", restart);
   if (!remembers)
     remembers = E_NEW(E_Remember_List, 1);
   else
     {
        EINA_LIST_FREE(remembers->list, rem)
          _e_remember_free(rem);
        remember_idler_list = eina_list_free(remember_idler_list);
     }

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     {
        if ((!ec->internal) || e_client_util_ignored_get(ec)) continue;

        rem = E_NEW(E_Remember, 1);
        if (!rem) break;

        e_remember_default_match_set(rem, ec);
        rem->apply = (E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE |
                      E_REMEMBER_APPLY_BORDER | E_REMEMBER_APPLY_LAYER |
                      E_REMEMBER_APPLY_SHADE | E_REMEMBER_APPLY_ZONE |
                      E_REMEMBER_APPLY_DESKTOP | E_REMEMBER_APPLY_LOCKS |
                      E_REMEMBER_APPLY_SKIP_WINLIST |
                      E_REMEMBER_APPLY_SKIP_PAGER |
                      E_REMEMBER_APPLY_SKIP_TASKBAR |
                      E_REMEMBER_APPLY_OFFER_RESISTANCE |
                      E_REMEMBER_APPLY_OPACITY |
                      E_REMEMBER_APPLY_VOLUME);
        _e_remember_update(ec, rem);

        remembers->list = eina_list_append(remembers->list, rem);
     }

   e_config_domain_save("e_remember_restart", e_remember_list_edd, remembers);
}

static Eina_Bool
_e_remember_restore_idler_cb(void *d EINA_UNUSED)
{
   E_Remember *rem;
   E_Action *act_fm = NULL, *act;
   Eina_Bool done = EINA_FALSE;

   EINA_LIST_FREE(remember_idler_list, rem)
     {
        if (!rem->class) continue;
        if (rem->no_reopen) continue;
        if (done) break;

        if (!strncmp(rem->class, "e_fwin::", 8))
          {
             if (!act_fm)
               act_fm = e_action_find("fileman");
             if (!act_fm) continue;
             /* at least '/' */
             if (!rem->class[9]) continue;

             act_fm->func.go(NULL, rem->class + 8);
          }
        else if (!strncmp(rem->class, "_config::", 9))
          {
             char *param = NULL;
             char path[1024];
             const char *p;

             p = rem->class + 9;
             if ((param = strstr(p, "::")))
               {
                  snprintf(path, (param - p) + sizeof(char), "%s", p);
                  param = param + 2;
               }
             else
               snprintf(path, sizeof(path), "%s", p);

             if (e_configure_registry_exists(path))
               {
                  e_configure_registry_call(path, NULL, param);
               }
          }
        else if (!strcmp(rem->class, "_configure"))
          {
             /* TODO this is just for settings panel. it could also
                use e_configure_registry_item_add */
             /* ..or make a general path for window that are started
                by actions */

             act = e_action_find("configuration");
             if (act)
               act->func.go(NULL, NULL);
          }
        done = EINA_TRUE;
     }
   if (!done) remember_idler = NULL;

   return done;
}

static Eina_Bool
_e_remember_restore_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   handlers = eina_list_free(handlers);
   if (!remembers->list) return ECORE_CALLBACK_PASS_ON;
   remember_idler_list = eina_list_clone(remembers->list);
   remember_idler = ecore_idler_add(_e_remember_restore_idler_cb, NULL);
   return ECORE_CALLBACK_PASS_ON;
}

E_API E_Remember *
e_remember_new(void)
{
   E_Remember *rem;

   rem = E_NEW(E_Remember, 1);
   if (!rem) return NULL;
   e_config->remembers = eina_list_prepend(e_config->remembers, rem);
   return rem;
}

E_API int
e_remember_usable_get(E_Remember *rem)
{
   if ((rem->apply_first_only) && (rem->used_count > 0)) return 0;
   return 1;
}

E_API void
e_remember_use(E_Remember *rem)
{
   rem->used_count++;
   if (rem->version < E_REMEMBER_VERSION)
     {
        /* upgrade remembers as they get used */
        switch (rem->version)
          {
           case 0:
             rem->prop.opacity = 255; //don't let people wreck themselves with old configs
           //fall through
           default: break;
          }
        rem->version = E_REMEMBER_VERSION;
        e_config_save_queue();
     }
}

E_API void
e_remember_unuse(E_Remember *rem)
{
   if (rem->used_count) rem->used_count--;
}

E_API void
e_remember_del(E_Remember *rem)
{
   E_Client *ec;

   E_CLIENT_FOREACH(ec)
     {
        if ((ec->remember != rem) && (ec->sr_remember != rem)) continue;

        if (ec->remember == rem)
          ec->remember = NULL;
        else
          ec->sr_remember = NULL;
        e_remember_unuse(rem);
     }

   _e_remember_free(rem);
}

E_API void
e_remember_apply(E_Remember *rem, E_Client *ec)
{
   int temporary = 0;
   if (ec->internal && remembers && ec->icccm.class && ec->icccm.class[0])
     {
        Eina_List *l;
        EINA_LIST_FOREACH(remembers->list, l, rem)
          {
             if (rem->class && !strcmp(rem->class, ec->icccm.class))
               break;
          }
        if (rem)
          {
             temporary = 1;
             remembers->list = eina_list_remove(remembers->list, rem);
             remember_idler_list = eina_list_remove(remember_idler_list, rem);
             if (!remembers->list)
               e_config_domain_save("e_remember_restart",
                                    e_remember_list_edd, remembers);
          }
        else rem = ec->remember;
     }

   if (!rem)
     return;
   rem->applying = 1;
   if (rem->apply & E_REMEMBER_APPLY_ZONE)
     {
        E_Zone *zone;

        zone = e_comp_zone_number_get(rem->prop.zone);
        if (zone)
          e_client_zone_set(ec, zone);
     }
   if (rem->apply & E_REMEMBER_APPLY_DESKTOP)
     {
        E_Desk *desk;

        desk = e_desk_at_xy_get(ec->zone, rem->prop.desk_x, rem->prop.desk_y);
        if (desk)
          {
             if (ec->desk != desk)
               ec->hidden = 0;
             e_client_desk_set(ec, desk);
             if (e_config->desk_auto_switch)
               e_desk_show(desk);
          }
     }
   if (rem->apply & E_REMEMBER_APPLY_BORDER)
     {
        eina_stringshare_replace(&ec->bordername, NULL);
        ec->bordername = eina_stringshare_ref(rem->prop.border);
        ec->border.changed = 1;
        EC_CHANGED(ec);
     }
   if (rem->apply & E_REMEMBER_APPLY_SIZE)
     {
        int w, h;
        int uzx = 0, uzy = 0, uzw = 0, uzh = 0;

        e_zone_useful_geometry_get(ec->zone, &uzx, &uzy, &uzw, &uzh);

        w = ec->client.w;
        h = ec->client.h;
        if (rem->prop.pos_w) ec->client.w = rem->prop.pos_w;
        if (rem->prop.pos_h) ec->client.h = rem->prop.pos_h;
        if (uzw != rem->prop.res_x)
          {
             if (ec->client.w > (uzw - rem->prop.frame_w))
               ec->client.w = uzw - rem->prop.frame_w;
          }
        if (uzh != rem->prop.res_y)
          {
             if (ec->client.h > (uzh - rem->prop.frame_h))
               ec->client.h = uzh - rem->prop.frame_h;
          }
        if (ec->icccm.min_w > ec->client.w)
          ec->client.w = ec->icccm.min_w;
        if ((ec->icccm.max_w > 0) && (ec->icccm.max_w < ec->client.w))
          ec->client.w = ec->icccm.max_w;
        if (ec->icccm.min_h > ec->client.h)
          ec->client.h = ec->icccm.min_h;
        if ((ec->icccm.max_h > 0) && (ec->icccm.max_h < ec->client.h))
          ec->client.h = ec->icccm.max_h;
        e_comp_object_frame_wh_adjust(ec->frame, ec->client.w, ec->client.h,
                                      &ec->w, &ec->h);
        if (rem->prop.maximize)
          {
             ec->saved.x = rem->prop.pos_x;
             ec->saved.y = rem->prop.pos_y;
             ec->saved.w = ec->client.w;
             ec->saved.h = ec->client.h;
             ec->maximized = rem->prop.maximize | e_config->maximize_policy;
             ec->changes.need_maximize = 1;
          }
        if ((w != ec->client.w) || (h != ec->client.h))
          {
             ec->changes.size = 1;
             ec->changes.shape = 1;
             EC_CHANGED(ec);
          }
     }
   if ((rem->apply & E_REMEMBER_APPLY_POS) && (!ec->re_manage))
     {
        int uzx = 0, uzy = 0, uzw = 0, uzh = 0;

        e_zone_useful_geometry_get(ec->zone, &uzx, &uzy, &uzw, &uzh);
        ec->x = rem->prop.pos_x;
        ec->y = rem->prop.pos_y;
        if (uzw != rem->prop.res_x)
          {
             int px;

             px = uzw - (ec->w + rem->prop.frame_w);
             if (px < 1) px = 0;
             else px = (3 * ec->x) / px;
             if (px < 1)
               {
                  if (uzw >= (rem->prop.res_x / 3))
                    ec->x = rem->prop.pos_x;
                  else
                    ec->x = ((rem->prop.pos_x - 0) * uzw) /
                      (rem->prop.res_x / 3);
               }
             else if (px == 1)
               {
                  if (uzw >= (rem->prop.res_x / 3))
                    ec->x = (uzw / 2) +
                      (px - (rem->prop.res_x / 2)) -
                      (ec->w / 2);
                  else
                    ec->x = (uzw / 2) +
                      (((px - (rem->prop.res_x / 2)) * uzw) /
                       (rem->prop.res_x / 3)) -
                      (ec->w / 2);
               }
             else // >= 2
               {
                  if (uzw >= (rem->prop.res_x / 3))
                    ec->x = uzw +
                      rem->prop.pos_x - rem->prop.res_x +
                      (rem->prop.w - ec->client.w);
                  else
                    ec->x = uzw +
                      (((rem->prop.pos_x - rem->prop.res_x) * uzw) /
                       (rem->prop.res_x / 3)) +
                      (rem->prop.w - ec->client.w);
               }
             if ((rem->prop.pos_x >= 0) && (ec->x < 0))
               ec->x = 0;
             else if (((rem->prop.pos_x + rem->prop.w) < rem->prop.res_x) &&
                      ((ec->x + ec->client.w + rem->prop.frame_w) > (uzw - (uzx - ec->zone->x))))
               ec->x = (uzw - (uzx - ec->zone->x)) - ec->client.w - rem->prop.frame_w;
          }
        if (uzh != rem->prop.res_y)
          {
             int py;

             py = uzh - (ec->h + rem->prop.frame_h);
             if (py < 1) py = 0;
             else py = (3 * ec->y) / py;
             if (py < 1)
               {
                  if (uzh >= (rem->prop.res_y / 3))
                    ec->y = rem->prop.pos_y;
                  else
                    ec->y = ((rem->prop.pos_y - 0) * uzh) /
                      (rem->prop.res_y / 3);
               }
             else if (py == 1)
               {
                  if (uzh >= (rem->prop.res_y / 3))
                    ec->y = (uzh / 2) +
                      (py - (rem->prop.res_y / 2)) -
                      (ec->h / 2);
                  else
                    ec->y = (uzh / 2) +
                      (((py - (rem->prop.res_y / 2)) * uzh) /
                       (rem->prop.res_y / 3)) -
                      (ec->h / 2);
               }
             else // >= 2
               {
                  if (uzh >= (rem->prop.res_y / 3))
                    ec->y = uzh +
                      rem->prop.pos_y - rem->prop.res_y +
                      (rem->prop.h - ec->client.h);
                  else
                    ec->y = uzh +
                      (((rem->prop.pos_y - rem->prop.res_y) * uzh) /
                       (rem->prop.res_y / 3)) +
                      (rem->prop.h - ec->client.h);
               }
             if ((rem->prop.pos_y >= 0) && (ec->y < 0))
               ec->y = 0;
             else if (((rem->prop.pos_y + rem->prop.h) < rem->prop.res_y) &&
                      ((ec->y + ec->client.h + rem->prop.frame_h) > (uzh - (uzy - ec->zone->y))))
               ec->y = (uzh - (uzy - ec->zone->y)) - ec->client.h - rem->prop.frame_h;
          }
        //		  if (ec->zone->w != rem->prop.res_x)
        //		    ec->x = (rem->prop.pos_x * ec->zone->w) / rem->prop.res_x;
        //		  if (ec->zone->h != rem->prop.res_y)
        //		    ec->y = (rem->prop.pos_y * ec->zone->h) / rem->prop.res_y;
        if (
          /* upper left */
          (!E_INSIDE(ec->x, ec->y, 0, 0, ec->zone->w, ec->zone->h)) &&
          /* upper right */
          (!E_INSIDE(ec->x + ec->w, ec->y, 0, 0, ec->zone->w, ec->zone->h)) &&
          /* lower left */
          (!E_INSIDE(ec->x, ec->y + ec->h, 0, 0, ec->zone->w, ec->zone->h)) &&
          /* lower right */
          (!E_INSIDE(ec->x + ec->w, ec->y + ec->h, 0, 0, ec->zone->w, ec->zone->h))
          )
          {
             e_comp_object_util_center_pos_get(ec->frame, &ec->x, &ec->y);
             rem->prop.pos_x = ec->x;
             rem->prop.pos_y = ec->y;
          }
        ec->x += uzx;
        ec->y += uzy;
        ec->placed = 1;
        ec->changes.pos = 1;
        EC_CHANGED(ec);
     }
   if (rem->apply & E_REMEMBER_APPLY_LAYER)
     {
        evas_object_layer_set(ec->frame, rem->prop.layer);
     }
   if (rem->apply & E_REMEMBER_APPLY_FULLSCREEN)
     {
        if (rem->prop.fullscreen)
          e_client_fullscreen(ec, e_config->fullscreen_policy);
     }
   if (rem->apply & E_REMEMBER_APPLY_STICKY)
     {
        if (rem->prop.sticky) e_client_stick(ec);
     }
   if (rem->apply & E_REMEMBER_APPLY_SHADE)
     {
        if (rem->prop.shaded >= 100)
          e_client_shade(ec, rem->prop.shaded - 100);
        else if (rem->prop.shaded >= 50)
          e_client_unshade(ec, rem->prop.shaded - 50);
     }
   if (rem->apply & E_REMEMBER_APPLY_LOCKS)
     {
        ec->lock_user_location = rem->prop.lock_user_location;
        ec->lock_client_location = rem->prop.lock_client_location;
        ec->lock_user_size = rem->prop.lock_user_size;
        ec->lock_client_size = rem->prop.lock_client_size;
        ec->lock_user_stacking = rem->prop.lock_user_stacking;
        ec->lock_client_stacking = rem->prop.lock_client_stacking;
        ec->lock_user_iconify = rem->prop.lock_user_iconify;
        ec->lock_client_iconify = rem->prop.lock_client_iconify;
        ec->lock_user_desk = rem->prop.lock_user_desk;
        ec->lock_client_desk = rem->prop.lock_client_desk;
        ec->lock_user_sticky = rem->prop.lock_user_sticky;
        ec->lock_client_sticky = rem->prop.lock_client_sticky;
        ec->lock_user_shade = rem->prop.lock_user_shade;
        ec->lock_client_shade = rem->prop.lock_client_shade;
        ec->lock_user_maximize = rem->prop.lock_user_maximize;
        ec->lock_client_maximize = rem->prop.lock_client_maximize;
        ec->lock_user_fullscreen = rem->prop.lock_user_fullscreen;
        ec->lock_client_fullscreen = rem->prop.lock_client_fullscreen;
        ec->lock_border = rem->prop.lock_border;
        ec->lock_close = rem->prop.lock_close;
        ec->lock_focus_in = rem->prop.lock_focus_in;
        ec->lock_focus_out = rem->prop.lock_focus_out;
        ec->lock_life = rem->prop.lock_life;
     }
   if (rem->apply & E_REMEMBER_APPLY_SKIP_WINLIST)
     ec->user_skip_winlist = rem->prop.skip_winlist;
   if (rem->apply & E_REMEMBER_APPLY_SKIP_PAGER)
     ec->netwm.state.skip_pager = rem->prop.skip_pager;
   if (rem->apply & E_REMEMBER_APPLY_SKIP_TASKBAR)
     ec->netwm.state.skip_taskbar = rem->prop.skip_taskbar;
   if (rem->apply & E_REMEMBER_APPLY_ICON_PREF)
     ec->icon_preference = rem->prop.icon_preference;
   if (rem->apply & E_REMEMBER_APPLY_OFFER_RESISTANCE)
     ec->offer_resistance = rem->prop.offer_resistance;
   if (rem->apply & E_REMEMBER_SET_FOCUS_ON_START)
     ec->want_focus = 1;
   if (rem->apply & E_REMEMBER_APPLY_OPACITY)
     ec->netwm.opacity = rem->prop.opacity;
   if (rem->apply & E_REMEMBER_APPLY_VOLUME)
     {
        if (!ec->volume_control_enabled)
          {
             ec->volume_control_enabled = EINA_TRUE;
             ec->volume = rem->prop.volume;
             ec->volume_min = rem->prop.volume_min;
             ec->volume_max = rem->prop.volume_max;
             ec->mute = rem->prop.mute;
          }
        else
          {
             e_client_volume_set(ec, rem->prop.volume);
             e_client_volume_mute_set(ec, rem->prop.mute);
          }
     }

   rem->applying = 0;
   if (temporary)
     _e_remember_free(rem);
}

E_API E_Remember *
e_remember_find_usable(E_Client *ec)
{
   E_Remember *rem;

   rem = _e_remember_find(ec, 1, 0);
   return rem;
}

E_API E_Remember *
e_remember_find(E_Client *ec)
{
   E_Remember *rem;

   rem = _e_remember_find(ec, 0, 0);
   return rem;
}

E_API E_Remember *
e_remember_sr_find(E_Client *ec)
{
   return _e_remember_find(ec, 1, 1);
}

E_API void
e_remember_match_update(E_Remember *rem)
{
   int max_count = 0;

   if (rem->match & E_REMEMBER_MATCH_NAME) max_count += 2;
   if (rem->match & E_REMEMBER_MATCH_CLASS) max_count += 2;
   if (rem->match & E_REMEMBER_MATCH_TITLE) max_count += 2;
   if (rem->match & E_REMEMBER_MATCH_ROLE) max_count += 2;
   if (rem->match & E_REMEMBER_MATCH_TYPE) max_count += 2;
   if (rem->match & E_REMEMBER_MATCH_TRANSIENT) max_count += 2;
   if (rem->apply_first_only) max_count++;

   if (max_count != rem->max_score)
     {
        /* The number of matches for this remember has changed so we
         * need to remove from list and insert back into the appropriate
         * location. */
        Eina_List *l = NULL;
        E_Remember *r;

        rem->max_score = max_count;
        e_config->remembers = eina_list_remove(e_config->remembers, rem);

        EINA_LIST_FOREACH(e_config->remembers, l, r)
          {
             if (r->max_score <= rem->max_score) break;
          }

        if (l)
          e_config->remembers = eina_list_prepend_relative_list(e_config->remembers, rem, l);
        else
          e_config->remembers = eina_list_append(e_config->remembers, rem);
     }
}

E_API int
e_remember_default_match_set(E_Remember *rem, E_Client *ec)
{
   const char *title, *clasz, *name, *role;
   int match;

   eina_stringshare_replace(&rem->name, NULL);
   eina_stringshare_replace(&rem->class, NULL);
   eina_stringshare_replace(&rem->title, NULL);
   eina_stringshare_replace(&rem->role, NULL);

   name = ec->icccm.name;
   if (!name || name[0] == 0) name = NULL;
   clasz = ec->icccm.class;
   if (!clasz || clasz[0] == 0) clasz = NULL;
   role = ec->icccm.window_role;
   if (!role || role[0] == 0) role = NULL;

   match = E_REMEMBER_MATCH_TRANSIENT;
   if (ec->icccm.transient_for != 0)
     rem->transient = 1;
   else
     rem->transient = 0;

   if (name && clasz)
     {
        match |= E_REMEMBER_MATCH_NAME | E_REMEMBER_MATCH_CLASS;
        rem->name = eina_stringshare_ref(name);
        rem->class = eina_stringshare_ref(clasz);
     }
   else if ((title = e_client_util_name_get(ec)) && title[0])
     {
        match |= E_REMEMBER_MATCH_TITLE;
        rem->title = eina_stringshare_ref(title);
     }
   if (role)
     {
        match |= E_REMEMBER_MATCH_ROLE;
        rem->role = eina_stringshare_ref(role);
     }
   if (ec->netwm.type != E_WINDOW_TYPE_UNKNOWN)
     {
        match |= E_REMEMBER_MATCH_TYPE;
        rem->type = ec->netwm.type;
     }

   rem->match = match;

   return match;
}

E_API void
e_remember_update(E_Client *ec)
{
   if (((!ec->remember) || ec->remember->keep_settings) && (!ec->sr_remember)) return;
   if (ec->remember) _e_remember_update(ec, ec->remember);
   if (ec->sr_remember) _e_remember_update(ec, ec->sr_remember);
   e_config_save_queue();
}

static void
_e_remember_event_free(void *d EINA_UNUSED, void *event)
{
   E_Event_Remember_Update *ev = event;
   UNREFD(ev->ec, 10);
   e_object_unref(E_OBJECT(ev->ec));
   free(ev);
}

static void
_e_remember_update(E_Client *ec, E_Remember *rem)
{
   if (rem->applying) return;
   if (rem->apply & E_REMEMBER_APPLY_POS ||
       rem->apply & E_REMEMBER_APPLY_SIZE)
     {
        int uzx = 0, uzy = 0, uxw = 0, uzh = 0;

        e_zone_useful_geometry_get(ec->zone, &uzx, &uzy, &uxw, &uzh);
        if (ec->fullscreen || ec->maximized)
          {
             rem->prop.pos_x = ec->saved.x - uzx;
             rem->prop.pos_y = ec->saved.y - uzy;
             rem->prop.pos_w = ec->saved.w;
             rem->prop.pos_h = ec->saved.h;
             rem->prop.frame_w = ec->w - ec->client.w;
             rem->prop.frame_h = ec->h - ec->client.h;
          }
        else
          {
             rem->prop.pos_x = ec->x - uzx;
             rem->prop.pos_y = ec->y - uzy;
             rem->prop.res_x = uxw;
             rem->prop.res_y = uzh;
             rem->prop.pos_w = ec->client.w;
             rem->prop.pos_h = ec->client.h;
             rem->prop.w = ec->client.w;
             rem->prop.h = ec->client.h;
             rem->prop.frame_w = ec->w - ec->client.w;
             rem->prop.frame_h = ec->h - ec->client.h;
          }
        rem->prop.maximize = ec->maximized & E_MAXIMIZE_DIRECTION;
     }
   if (rem->apply & E_REMEMBER_APPLY_LAYER)
     {
        if (ec->fullscreen)
          rem->prop.layer = ec->saved.layer;
        else
          rem->prop.layer = ec->layer;
     }
   if (rem->apply & E_REMEMBER_APPLY_LOCKS)
     {
        rem->prop.lock_user_location = ec->lock_user_location;
        rem->prop.lock_client_location = ec->lock_client_location;
        rem->prop.lock_user_size = ec->lock_user_size;
        rem->prop.lock_client_size = ec->lock_client_size;
        rem->prop.lock_user_stacking = ec->lock_user_stacking;
        rem->prop.lock_client_stacking = ec->lock_client_stacking;
        rem->prop.lock_user_iconify = ec->lock_user_iconify;
        rem->prop.lock_client_iconify = ec->lock_client_iconify;
        rem->prop.lock_user_desk = ec->lock_user_desk;
        rem->prop.lock_client_desk = ec->lock_client_desk;
        rem->prop.lock_user_sticky = ec->lock_user_sticky;
        rem->prop.lock_client_sticky = ec->lock_client_sticky;
        rem->prop.lock_user_shade = ec->lock_user_shade;
        rem->prop.lock_client_shade = ec->lock_client_shade;
        rem->prop.lock_user_maximize = ec->lock_user_maximize;
        rem->prop.lock_client_maximize = ec->lock_client_maximize;
        rem->prop.lock_user_fullscreen = ec->lock_user_fullscreen;
        rem->prop.lock_client_fullscreen = ec->lock_client_fullscreen;
        rem->prop.lock_border = ec->lock_border;
        rem->prop.lock_close = ec->lock_close;
        rem->prop.lock_focus_in = ec->lock_focus_in;
        rem->prop.lock_focus_out = ec->lock_focus_out;
        rem->prop.lock_life = ec->lock_life;
     }
   if (rem->apply & E_REMEMBER_APPLY_SHADE)
     {
        if (ec->shaded)
          rem->prop.shaded = (100 + ec->shade_dir);
        else
          rem->prop.shaded = (50 + ec->shade_dir);
     }
   if (rem->apply & E_REMEMBER_APPLY_ZONE)
     {
        rem->prop.zone = ec->zone->num;
     }
   if (rem->apply & E_REMEMBER_APPLY_SKIP_WINLIST)
     rem->prop.skip_winlist = ec->user_skip_winlist;
   if (rem->apply & E_REMEMBER_APPLY_STICKY)
     rem->prop.sticky = ec->sticky;
   if (rem->apply & E_REMEMBER_APPLY_SKIP_PAGER)
     rem->prop.skip_pager = ec->netwm.state.skip_pager;
   if (rem->apply & E_REMEMBER_APPLY_SKIP_TASKBAR)
     rem->prop.skip_taskbar = ec->netwm.state.skip_taskbar;
   if (rem->apply & E_REMEMBER_APPLY_ICON_PREF)
     rem->prop.icon_preference = ec->icon_preference;
   if (rem->apply & E_REMEMBER_APPLY_DESKTOP)
     e_desk_xy_get(ec->desk, &rem->prop.desk_x, &rem->prop.desk_y);
   if (rem->apply & E_REMEMBER_APPLY_FULLSCREEN)
     rem->prop.fullscreen = ec->fullscreen;
   if (rem->apply & E_REMEMBER_APPLY_OFFER_RESISTANCE)
     rem->prop.offer_resistance = ec->offer_resistance;
   if (rem->apply & E_REMEMBER_APPLY_OPACITY)
     rem->prop.opacity = ec->netwm.opacity;
   if (rem->apply & E_REMEMBER_APPLY_BORDER)
     {
        if (ec->borderless)
          eina_stringshare_replace(&rem->prop.border, "borderless");
        else
          eina_stringshare_replace(&rem->prop.border, ec->bordername);
     }
   if (rem->apply & E_REMEMBER_APPLY_UUID)
     {
        eina_stringshare_refplace(&rem->uuid, ec->uuid);
        rem->pid = ec->netwm.pid;
        rem->apply_first_only = 1;
     }
   if (rem->apply & E_REMEMBER_APPLY_VOLUME)
     {
        rem->prop.volume = ec->volume;
        rem->prop.volume_min = ec->volume_min;
        rem->prop.volume_max = ec->volume_max;
        rem->prop.mute = ec->mute;
     }

   rem->no_reopen = ec->internal_no_reopen;
   {
      E_Event_Remember_Update *ev;

      ev = malloc(sizeof(E_Event_Remember_Update));
      if (!ev) return;
      ev->ec = ec;
      REFD(ec, 10);
      e_object_ref(E_OBJECT(ec));
      ecore_event_add(E_EVENT_REMEMBER_UPDATE, ev, _e_remember_event_free, NULL);
   }
}

/* local subsystem functions */
static E_Remember *
_e_remember_find(E_Client *ec, int check_usable, Eina_Bool sr)
{
   Eina_List *l = NULL;
   E_Remember *rem;

#if REMEMBER_SIMPLE
   EINA_LIST_FOREACH(e_config->remembers, l, rem)
     {
        int required_matches;
        int matches;
        const char *title = "";

        matches = 0;
        required_matches = 0;
        if (rem->match & E_REMEMBER_MATCH_NAME) required_matches++;
        if (rem->match & E_REMEMBER_MATCH_CLASS) required_matches++;
        if (rem->match & E_REMEMBER_MATCH_TITLE) required_matches++;
        if (rem->match & E_REMEMBER_MATCH_ROLE) required_matches++;
        if (rem->match & E_REMEMBER_MATCH_TYPE) required_matches++;
        if (rem->match & E_REMEMBER_MATCH_TRANSIENT) required_matches++;

        if (ec->netwm.name) title = ec->netwm.name;
        else title = ec->icccm.title;

        if ((rem->match & E_REMEMBER_MATCH_NAME) &&
            ((!e_util_strcmp(rem->name, ec->icccm.name)) ||
             (e_util_both_str_empty(rem->name, ec->icccm.name))))
          matches++;
        if ((rem->match & E_REMEMBER_MATCH_CLASS) &&
            ((!e_util_strcmp(rem->class, ec->icccm.class)) ||
             (e_util_both_str_empty(rem->class, ec->icccm.class))))
          matches++;
        if ((rem->match & E_REMEMBER_MATCH_TITLE) &&
            ((!e_util_strcmp(rem->title, title)) ||
             (e_util_both_str_empty(rem->title, title))))
          matches++;
        if ((rem->match & E_REMEMBER_MATCH_ROLE) &&
            ((!e_util_strcmp(rem->role, ec->icccm.window_role)) ||
             (e_util_both_str_empty(rem->role, ec->icccm.window_role))))
          matches++;
        if ((rem->match & E_REMEMBER_MATCH_TYPE) &&
            (rem->type == ec->netwm.type))
          matches++;
        if ((rem->match & E_REMEMBER_MATCH_TRANSIENT) &&
            (((rem->transient) && (ec->icccm.transient_for != 0)) ||
             ((!rem->transient) && (ec->icccm.transient_for == 0))))
          matches++;
        if (matches >= required_matches)
          return rem;
     }
   return NULL;
#endif
#if REMEMBER_HIERARCHY
   /* This search method finds the best possible match available and is
    * based on the fact that the list is sorted, with those remembers
    * with the most possible matches at the start of the list. This
    * means, as soon as a valid match is found, it is a match
    * within the set of best possible matches. */
   EINA_LIST_FOREACH(e_config->remembers, l, rem)
     {
        const char *title = "";

        if ((check_usable) && (!e_remember_usable_get(rem)))
          continue;

        if (sr)
          {
             if (!eina_streq(rem->uuid, ec->uuid)) continue;
             if (rem->uuid)
               {
                  if (rem->pid != ec->netwm.pid) continue;
                  return rem;
               }
          }
        else if (rem->apply & E_REMEMBER_APPLY_UUID) continue;

        if (ec->netwm.name) title = ec->netwm.name;
        else title = ec->icccm.title;

        /* For each type of match, check whether the match is
         * required, and if it is, check whether there's a match. If
         * it fails, then go to the next remember */
        if (rem->match & E_REMEMBER_MATCH_NAME &&
            !e_util_glob_match(ec->icccm.name, rem->name))
          continue;
        if (rem->match & E_REMEMBER_MATCH_CLASS &&
            !e_util_glob_match(ec->icccm.class, rem->class))
          continue;
        if (rem->match & E_REMEMBER_MATCH_TITLE &&
            !e_util_glob_match(title, rem->title))
          continue;
        if (rem->match & E_REMEMBER_MATCH_ROLE &&
            e_util_strcmp(rem->role, ec->icccm.window_role) &&
            !e_util_both_str_empty(rem->role, ec->icccm.window_role))
          continue;
        if (rem->match & E_REMEMBER_MATCH_TYPE &&
            rem->type != (int)ec->netwm.type)
          continue;
        if (rem->match & E_REMEMBER_MATCH_TRANSIENT &&
            !(rem->transient && ec->icccm.transient_for != 0) &&
            !(!rem->transient) && (ec->icccm.transient_for == 0))
          continue;

        return rem;
     }

   return NULL;
#endif
}

static void
_e_remember_free(E_Remember *rem)
{
   e_config->remembers = eina_list_remove(e_config->remembers, rem);
   if (rem->name) eina_stringshare_del(rem->name);
   if (rem->class) eina_stringshare_del(rem->class);
   if (rem->title) eina_stringshare_del(rem->title);
   if (rem->role) eina_stringshare_del(rem->role);
   if (rem->prop.border) eina_stringshare_del(rem->prop.border);
   if (rem->prop.command) eina_stringshare_del(rem->prop.command);
   if (rem->prop.desktop_file) eina_stringshare_del(rem->prop.desktop_file);
   eina_stringshare_del(rem->uuid);
   free(rem);
}

static void
_e_remember_cb_hook_eval_post_new_client(void *data EINA_UNUSED, E_Client *ec)
{
   // remember only when window was modified
   // if (!ec->new_client) return;
   if (e_client_util_ignored_get(ec)) return;
   if ((ec->internal) && (!ec->remember) &&
       (e_config->remember_internal_windows) &&
       (!ec->internal_no_remember) &&
       (ec->icccm.class && ec->icccm.class[0]))
     {
        E_Remember *rem;

        if (!strncmp(ec->icccm.class, "e_fwin", 6))
          {
             if (!e_config->remember_internal_fm_windows) return;
          }
        else
          {
             if (!e_config->remember_internal_windows)
               return;
          }

        rem = e_remember_new();
        if (!rem) return;

        e_remember_default_match_set(rem, ec);

        rem->apply = E_REMEMBER_APPLY_POS | E_REMEMBER_APPLY_SIZE | E_REMEMBER_APPLY_BORDER;

        e_remember_use(rem);
        ec->remember = rem;
        e_remember_update(ec);
     }
}

static void
_e_remember_cb_hook_pre_post_fetch(void *data EINA_UNUSED, E_Client *ec)
{
   E_Remember *rem = NULL;

   if ((!ec->new_client) || ec->internal_no_remember || e_client_util_ignored_get(ec)) return;

   if (!ec->remember)
     {
        rem = e_remember_find_usable(ec);
        if (rem)
          {
             ec->remember = rem;
             e_remember_use(rem);
          }
     }

   e_remember_apply(rem, ec);
}

static void
_e_remember_init_edd(void)
{
   e_remember_list_edd = E_CONFIG_DD_NEW("E_Remember_List", E_Remember_List);
#undef T
#undef D
#define T E_Remember_List
#define D e_remember_list_edd
   E_CONFIG_LIST(D, T, list, e_remember_edd);
#undef T
#undef D
}

#include "e.h"

typedef struct _E_Resist_Rect E_Resist_Rect;

struct _E_Resist_Rect
{
   int x, y, w, h;
   int v1;
   int resist_out;
};

static void _e_resist_rects(Eina_List *rects, int px, int py, int pw, int ph, int x, int y, int w, int h, int *rx, int *ry, int *rw, int *rh);

E_API int
e_resist_client_position(Eina_List *skiplist,
                                   int px, int py, int pw, int ph,
                                   int x, int y, int w, int h,
                                   int *rx, int *ry, int *rw, int *rh)
{
   Eina_List *l, *rects = NULL;
   E_Resist_Rect *r;
   E_Client *ec;
   E_Desk *desk;
   E_Zone *zone;

   if (!e_config->use_resist)
     {
        *rx = x;
        *ry = y;
        *rw = w;
        *rh = h;
        return 0;
     }

   /* edges of screen */
#define OBSTACLE(_x, _y, _w, _h, _resist)                         \
  {                                                               \
     r = E_NEW(E_Resist_Rect, 1);                                 \
     r->x = _x; r->y = _y; r->w = _w; r->h = _h; r->v1 = _resist; \
     r->resist_out = 0;                                           \
     rects = eina_list_append(rects, r);                          \
  }
#define HOLDER(_x, _y, _w, _h, _resist)                           \
  {                                                               \
     r = E_NEW(E_Resist_Rect, 1);                                 \
     r->x = _x; r->y = _y; r->w = _w; r->h = _h; r->v1 = _resist; \
     r->resist_out = 1;                                           \
     rects = eina_list_append(rects, r);                          \
  }

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        HOLDER(zone->x, zone->y, zone->w, zone->h, e_config->desk_resist);
     }
   /* FIXME: need to add resist or complete BLOCKS for things like ibar */
   /* can add code here to add more fake obstacles with custom resist values */
   /* here if need be - ie xinerama middle between screens and panels etc. */
   E_CLIENT_FOREACH(ec)
     {
        if (e_client_util_ignored_get(ec) || (!evas_object_visible_get(ec->frame))) continue;
        if (ec->offer_resistance && (!eina_list_data_find(skiplist, ec)))
          {
             OBSTACLE(ec->x, ec->y, ec->w, ec->h, e_config->window_resist);
          }
     }

   desk = e_desk_current_get(e_zone_current_get());
   {
      E_Zone_Obstacle *obs;

      EINA_INLIST_FOREACH(desk->obstacles, obs)
        OBSTACLE(obs->x, obs->y, obs->w, obs->h, e_config->gadget_resist);
      EINA_INLIST_FOREACH(desk->zone->obstacles, obs)
        OBSTACLE(obs->x, obs->y, obs->w, obs->h, e_config->gadget_resist);
   }
   if (rects)
     {
        _e_resist_rects(rects,
                        px, py, pw, ph,
                        x, y, w, h,
                        rx, ry, rw, rh);

        E_FREE_LIST(rects, E_FREE);
     }
   return 1;
}

E_API int
e_resist_gadman_position(Eina_List *skiplist EINA_UNUSED,
                                   int px, int py, int pw, int ph,
                                   int x, int y, int w, int h,
                                   int *rx, int *ry)
{
   Eina_List *l, *rects = NULL;
   E_Resist_Rect *r;
   E_Shelf *es;

   if (!e_config->use_resist)
     {
        *rx = x;
        *ry = y;
        return 0;
     }

   l = e_shelf_list_all();
   EINA_LIST_FREE(l, es)
     {
        if (e_shelf_desk_visible(es, e_desk_current_get(es->zone)))
          {
             OBSTACLE(es->x + es->zone->x, es->y + es->zone->y, es->w, es->h,
                      e_config->gadget_resist);
          }
     }

   if (rects)
     {
        _e_resist_rects(rects,
                        px, py, pw, ph,
                        x, y, w, h,
                        rx, ry, NULL, NULL);

        E_FREE_LIST(rects, E_FREE);
     }
   return 1;
}

static void
_e_resist_rects(Eina_List *rects,
                int px, int py, int pw, int ph,
                int x, int y, int w, int h,
                int *rx, int *ry, int *rw, int *rh)
{
   int dx, dy, dw, dh, d, pd;
   int resist_x = 0, resist_y = 0;
   int resist_w = 0, resist_h = 0;
   Eina_List *l;
   E_Resist_Rect *r;

   dx = x - px;
   dy = y - py;
   dw = w - pw;
   dh = h - ph;

   EINA_LIST_FOREACH(rects, l, r)
     {
        if (E_SPANS_COMMON(r->y, r->h, y, h))
          {
             if (dx > 0)
               {
                  /* moving right */
                  if (r->resist_out)
                    {
                       /* check right edge of windows against left */
                       d = x + w - (r->x + r->w);
                       pd = px + pw - (r->x + r->w);
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (-resist_x < d)
                              resist_x = -d;
                         }
                    }
                  else
                    {
                       /* check left edge of windows against right */
                       d = r->x - (x + w);
                       pd = r->x - (px + pw);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (resist_x > d)
                              resist_x = d;
                         }
                    }
               }
             else if (dx < 0)
               {
                  /* moving left */
                  if (r->resist_out)
                    {
                       /* check left edge of windows against right */
                       d = r->x - x;
                       pd = r->x - px;
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (resist_x < d)
                              {
                                 resist_x = d;
                                 resist_w = -d;
                              }
                         }
                    }
                  else
                    {
                       /* check right edge of windows against left */
                       d = x - (r->x + r->w);
                       pd = px - (r->x + r->w);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (-resist_x > d)
                              {
                                 resist_x = -d;
                                 resist_w = d;
                              }
                         }
                    }
               }
             if ((dw > 0) && (dx == 0))
               {
                  /* enlarging window by moving lower corner */
                  if (r->resist_out)
                    {
                       /* check right edge of windows against left */
                       d = x + w - (r->x + r->w);
                       pd = px + pw - (r->x + r->w);
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (-resist_w < d)
                              resist_w = -d;
                         }
                    }
                  else
                    {
                       /* check left edge of windows against right */
                       d = r->x - (x + w);
                       pd = r->x - (px + pw);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (resist_w > d)
                              resist_w = d;
                         }
                    }
               }
          }
        if (E_SPANS_COMMON(r->x, r->w, x, w))
          {
             if (dy > 0)
               {
                  /* moving down */
                  if (r->resist_out)
                    {
                       /* check bottom edge of windows against top */
                       d = y + h - (r->y + r->h);
                       pd = py + ph - (r->y + r->h);
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (-resist_y < d)
                              resist_y = -d;
                         }
                    }
                  else
                    {
                       /* check top edge of windows against bottom */
                       d = r->y - (y + h);
                       pd = r->y - (py + ph);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (resist_y > d)
                              resist_y = d;
                         }
                    }
               }
             else if (dy < 0)
               {
                  /* moving up */
                  if (r->resist_out)
                    {
                       /* check top edge of windows against bottom */
                       d = r->y - y;
                       pd = r->y - py;
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (resist_y < d)
                              {
                                 resist_y = d;
                                 resist_h = -d;
                              }
                         }
                    }
                  else
                    {
                       /* moving up - check bottom edge of windows against top */
                       d = y - (r->y + r->h);
                       pd = py - (r->y + r->h);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (-resist_y > d)
                              {
                                 resist_y = -d;
                                 resist_h = d;
                              }
                         }
                    }
               }
             if ((dh > 0) && (dy == 0))
               {
                  /* enlarging window by moving lower corner */
                  if (r->resist_out)
                    {
                       /* check bottom edge of windows against top */
                       d = y + h - (r->y + r->h);
                       pd = py + ph - (r->y + r->h);
                       if ((d > 0) && (pd <= 0) && (d <= r->v1))
                         {
                            if (-resist_h < d)
                              resist_h = -d;
                         }
                    }
                  else
                    {
                       /* check top edge of windows against bottom */
                       d = r->y - (y + h);
                       pd = r->y - (py + ph);
                       if ((d < 0) && (pd >= 0) && (d >= -r->v1))
                         {
                            if (resist_h > d)
                              resist_h = d;
                         }
                    }
               }
          }
     }
   if (rx)
     {
        if (dx != 0)
          *rx = x + resist_x;
        else
          *rx = x;
     }
   if (ry)
     {
        if (dy != 0)
          *ry = y + resist_y;
        else
          *ry = y;
     }
   if (rw)
     {
        if (dw != 0)
          *rw = w + resist_w;
        else
          *rw = w;
     }
   if (rh)
     {
        if (dh != 0)
          *rh = h + resist_h;
        else
          *rh = h;
     }
}

#include "e.h"

E_API double e_scale = 1.0;

EINTERN int
e_scale_init(void)
{
   e_scale_update();
   return 1;
}

EINTERN int
e_scale_shutdown(void)
{
   return 1;
}

E_API double
e_scale_dpi_get(void)
{
   // this is a general dpi across all screens thing
   if ((e_randr2) && (e_randr2->screens))
     {
        Eina_List *l;
        E_Randr2_Screen *sc;
        double total_dpi = 0.0;
        int total_screens = 0;

        EINA_LIST_FOREACH(e_randr2->screens, l, sc)
          {
             if ((sc->config.enabled) &&
                 (sc->config.mode.w > 0) && (sc->config.mode.h > 0) &&
                 (sc->info.size.w > 0) && (sc->info.size.h > 0))
               {
                  double dpi = ((((double)sc->config.mode.w * 25.4) /
                                 (double)sc->info.size.w) +
                                (((double)sc->config.mode.h * 25.4) /
                                 (double)sc->info.size.h)) / 2.0;
                  total_dpi += dpi;
                  total_screens++;
               }
          }
        if ((total_dpi > 0.0) && (total_screens > 0))
          return total_dpi / (double)total_screens;
     }
   // fall back to old way
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        int x_core_dpi = ecore_x_dpi_get();
        return x_core_dpi;
     }
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        double dpi;
        int xdpi = 0, ydpi = 0;

        ecore_evas_screen_dpi_get(e_comp->ee, &xdpi, &ydpi);
        if (xdpi == 0) xdpi = 75;
        if (ydpi == 0) ydpi = 75;
        dpi = ((double)(xdpi + ydpi) / 2.0);
        return dpi;
     }
#endif
   return 75.0;
}

E_API void
e_scale_update(void)
{
   char buf[128];

   if (e_config->scale.use_dpi)
     {
        e_scale = e_scale_dpi_get() / (double)e_config->scale.base_dpi;
        if      (e_scale > e_config->scale.max) e_scale = e_config->scale.max;
        else if (e_scale < e_config->scale.min) e_scale = e_config->scale.min;
     }
   else if (e_config->scale.use_custom)
     {
        e_scale = e_config->scale.factor;
        if      (e_scale > e_config->scale.max) e_scale = e_config->scale.max;
        else if (e_scale < e_config->scale.min) e_scale = e_config->scale.min;
     }
   elm_config_scale_set(e_scale);
   elm_config_all_flush();
   edje_scale_set(e_scale);
   snprintf(buf, sizeof(buf), "%1.3f", e_scale);
   e_util_env_set("E_SCALE", buf);
   e_hints_scale_update();
   e_pointers_size_set(e_config->cursor_size);
#ifndef HAVE_WAYLAND_ONLY
   e_xsettings_config_update();
   if (e_config->scale.set_xapp_dpi)
     {
        snprintf(buf, sizeof(buf), "%i",
                 (int)((double)e_config->scale.xapp_base_dpi * e_scale));
        ecore_x_resource_db_string_set("Xft.dpi", buf);
        ecore_x_resource_db_string_set("Xft.hinting", "1");
        ecore_x_resource_db_string_set("Xft.antialias", "1");
        ecore_x_resource_db_string_set("Xft.autohint", "0");
        ecore_x_resource_db_string_set("Xft.hintstyle", "hintfull");
        ecore_x_resource_db_string_set("Xft.rgba", "none");
        ecore_x_resource_db_flush();
     }
#endif
}
#include "e.h"

static Ecore_Event_Handler *_e_screensaver_handler_on = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_off = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_config_mode = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_fullscreen = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_unfullscreen = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_remove = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_iconify = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_uniconify = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_border_desk_set = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_desk_show = NULL;
static Ecore_Event_Handler *_e_screensaver_handler_powersave = NULL;

static int _e_screensaver_cfg_timeout = -1;
static int _e_screensaver_cfg_dim = -1;

static int _e_screensaver_timeout = 0;
static int _e_screensaver_blanking = 0;
static int _e_screensaver_expose = 0;

static Ecore_Timer *_e_screensaver_suspend_timer = NULL;
static Eina_Bool _e_screensaver_on = EINA_FALSE;

static Ecore_Timer *screensaver_idle_timer = NULL;
static Eina_Bool screensaver_dimmed = EINA_FALSE;

static Eina_Bool _screensaver_ignore = EINA_FALSE;
static Eina_Bool _screensaver_now = EINA_FALSE;

E_API int E_EVENT_SCREENSAVER_ON = -1;
E_API int E_EVENT_SCREENSAVER_OFF = -1;
E_API int E_EVENT_SCREENSAVER_OFF_PRE = -1;

E_API int
e_screensaver_timeout_get(Eina_Bool use_idle)
{
   int timeout = 0;
   Eina_Bool use_special_instead_of_dim = EINA_FALSE;

   if (_screensaver_now) return 1;
   if (e_config->screensaver_enable)
     {
        if ((e_desklock_state_get()) &&
            (e_config->screensaver_desklock_timeout > 0))
          {
             timeout = e_config->screensaver_desklock_timeout;
             use_special_instead_of_dim = EINA_TRUE;
          }
        else
          timeout = e_config->screensaver_timeout;
     }

   if ((use_idle) && (!use_special_instead_of_dim))
     {
        if (e_config->backlight.idle_dim)
          {
             double t2 = e_config->backlight.timer;

             if ((e_powersave_mode_get() > E_POWERSAVE_MODE_LOW) &&
                 (e_config->backlight.battery_timer > 0.0))
               t2 = e_config->backlight.battery_timer;
             if (timeout > 0)
               {
                  if (t2 < timeout) timeout = t2;
               }
             else timeout = t2;
          }
     }
   return timeout;
}

E_API void
e_screensaver_ignore(void)
{
   _screensaver_ignore = EINA_TRUE;
}

E_API void
e_screensaver_unignore(void)
{
   _screensaver_ignore = EINA_FALSE;
}

E_API Eina_Bool
e_screensaver_ignore_get(void)
{
   return _screensaver_ignore;
}

E_API void
e_screensaver_update(void)
{
   int timeout, interval = 0, blanking = 0, expose = 0;
   Eina_Bool changed = EINA_FALSE;
   Eina_Bool real_changed = EINA_FALSE;
   int dim_timeout = -1;
   E_Powersave_Mode pm = e_powersave_mode_get();

   if (pm > E_POWERSAVE_MODE_LOW)
     dim_timeout = e_config->backlight.battery_timer;
   else
     dim_timeout = e_config->backlight.timer;

   if (_e_screensaver_cfg_timeout != e_config->screensaver_timeout)
     real_changed = EINA_TRUE;
   else if (_e_screensaver_cfg_dim != dim_timeout)
     real_changed = EINA_TRUE;
   _e_screensaver_cfg_timeout = e_config->screensaver_timeout;
   _e_screensaver_cfg_dim = dim_timeout;

   timeout = e_screensaver_timeout_get(EINA_TRUE);
   if (!((e_config->screensaver_enable) &&
         (!((e_util_fullscreen_current_any()) &&
            (e_config->no_dpms_on_fullscreen)))))
     timeout = 0;

   if (_e_screensaver_timeout != timeout)
     {
        _e_screensaver_timeout = timeout;
        changed = EINA_TRUE;
     }

   interval = e_config->screensaver_interval;
   blanking = e_config->screensaver_blanking;
   expose = e_config->screensaver_expose;

   if (_e_screensaver_blanking != blanking)
     {
        _e_screensaver_blanking = blanking;
        changed = EINA_TRUE;
     }
   if (_e_screensaver_expose != expose)
     {
        _e_screensaver_expose = expose;
        changed = EINA_TRUE;
     }

   if (changed)
     {
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
          {
             // this toggling of dpms is a bug workaround in x that i found
             // where if we change screensaver timeouts and force a manual
             // wake of the screen e.g. on lid open/close we have to toggle
             // it for x to stop thinking the monitor is off when it's
             // actually on and this causes later dpms issues where the
             // screen doesn't turn off at all because x thinks internally
             // that the monitor is still off... so this is odd, but it's
             // necessary on some hardware.
             if ((real_changed) && (!e_config->screensaver_dpms_off))
               {
                  ecore_x_dpms_enabled_set(!e_config->screensaver_enable);
                  ecore_x_dpms_enabled_set(e_config->screensaver_enable);
               }
             ecore_x_screensaver_set(timeout, interval, blanking, expose);
          }
#endif
     }
}

static Eina_Bool
_e_screensaver_handler_config_mode_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_screensaver_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_suspend_cb(void *data EINA_UNUSED)
{
   _e_screensaver_suspend_timer = NULL;
   if (e_config->screensaver_suspend)
     {
        if ((e_config->screensaver_suspend_on_ac) ||
            (e_powersave_mode_get() > E_POWERSAVE_MODE_LOW))
          {
             if (e_config->screensaver_hibernate)
               e_sys_action_do(E_SYS_HIBERNATE, NULL);
             else
               e_sys_action_do(E_SYS_SUSPEND, NULL);
          }
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_screensaver_handler_powersave_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_screensaver_update();
   if ((e_config->screensaver_suspend) && (_e_screensaver_on))
     {
        if (_e_screensaver_suspend_timer)
          ecore_timer_del(_e_screensaver_suspend_timer);
        _e_screensaver_suspend_timer =
          ecore_timer_loop_add(e_config->screensaver_suspend_delay,
                          _e_screensaver_suspend_cb, NULL);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_handler_screensaver_on_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   _e_screensaver_on = EINA_TRUE;
   if (_e_screensaver_suspend_timer)
     {
        ecore_timer_del(_e_screensaver_suspend_timer);
        _e_screensaver_suspend_timer = NULL;
     }
   if (e_config->screensaver_suspend)
     _e_screensaver_suspend_timer =
       ecore_timer_loop_add(e_config->screensaver_suspend_delay,
                       _e_screensaver_suspend_cb, NULL);
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_handler_screensaver_off_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
//   e_dpms_force_update();

   _e_screensaver_on = EINA_FALSE;
   if (_e_screensaver_suspend_timer)
     {
        ecore_timer_del(_e_screensaver_suspend_timer);
        _e_screensaver_suspend_timer = NULL;
     }

   if (!e_desklock_state_get())
     e_pointer_reset(e_comp->pointer);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_handler_border_fullscreen_check_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_screensaver_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_handler_border_desk_set_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_screensaver_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_handler_desk_show_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   e_screensaver_update();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_screensaver_idle_timer_cb(void *d EINA_UNUSED)
{
   e_pointer_reset(e_comp->pointer);
   e_powersave_mode_screen_set(E_POWERSAVE_MODE_FREEZE);
   ecore_event_add(E_EVENT_SCREENSAVER_ON, NULL, NULL, NULL);
   screensaver_idle_timer = NULL;
   return EINA_FALSE;
}

EINTERN void
e_screensaver_preinit(void)
{
   E_EVENT_SCREENSAVER_ON = ecore_event_type_new();
   E_EVENT_SCREENSAVER_OFF = ecore_event_type_new();
   E_EVENT_SCREENSAVER_OFF_PRE = ecore_event_type_new();
}

EINTERN int
e_screensaver_init(void)
{
   _e_screensaver_handler_on = ecore_event_handler_add
       (E_EVENT_SCREENSAVER_ON, _e_screensaver_handler_screensaver_on_cb, NULL);
   _e_screensaver_handler_off = ecore_event_handler_add
       (E_EVENT_SCREENSAVER_OFF, _e_screensaver_handler_screensaver_off_cb, NULL);
   _e_screensaver_handler_config_mode = ecore_event_handler_add
       (E_EVENT_CONFIG_MODE_CHANGED, _e_screensaver_handler_config_mode_cb, NULL);

   _e_screensaver_handler_border_fullscreen = ecore_event_handler_add
       (E_EVENT_CLIENT_FULLSCREEN, _e_screensaver_handler_border_fullscreen_check_cb, NULL);
   _e_screensaver_handler_border_unfullscreen = ecore_event_handler_add
       (E_EVENT_CLIENT_UNFULLSCREEN, _e_screensaver_handler_border_fullscreen_check_cb, NULL);
   _e_screensaver_handler_border_remove = ecore_event_handler_add
       (E_EVENT_CLIENT_REMOVE, _e_screensaver_handler_border_fullscreen_check_cb, NULL);
   _e_screensaver_handler_border_iconify = ecore_event_handler_add
       (E_EVENT_CLIENT_ICONIFY, _e_screensaver_handler_border_fullscreen_check_cb, NULL);
   _e_screensaver_handler_border_uniconify = ecore_event_handler_add
       (E_EVENT_CLIENT_UNICONIFY, _e_screensaver_handler_border_fullscreen_check_cb, NULL);
   _e_screensaver_handler_border_desk_set = ecore_event_handler_add
       (E_EVENT_CLIENT_DESK_SET, _e_screensaver_handler_border_desk_set_cb, NULL);
   _e_screensaver_handler_desk_show = ecore_event_handler_add
       (E_EVENT_DESK_SHOW, _e_screensaver_handler_desk_show_cb, NULL);

   _e_screensaver_handler_powersave = ecore_event_handler_add
       (E_EVENT_POWERSAVE_UPDATE, _e_screensaver_handler_powersave_cb, NULL);

   return 1;
}

EINTERN int
e_screensaver_shutdown(void)
{
   if (_e_screensaver_handler_on)
     {
        ecore_event_handler_del(_e_screensaver_handler_on);
        _e_screensaver_handler_on = NULL;
     }

   if (_e_screensaver_handler_off)
     {
        ecore_event_handler_del(_e_screensaver_handler_off);
        _e_screensaver_handler_off = NULL;
     }

   if (_e_screensaver_suspend_timer)
     {
        ecore_timer_del(_e_screensaver_suspend_timer);
        _e_screensaver_suspend_timer = NULL;
     }

   if (_e_screensaver_handler_powersave)
     {
        ecore_event_handler_del(_e_screensaver_handler_powersave);
        _e_screensaver_handler_powersave = NULL;
     }

   if (_e_screensaver_handler_config_mode)
     {
        ecore_event_handler_del(_e_screensaver_handler_config_mode);
        _e_screensaver_handler_config_mode = NULL;
     }

   if (_e_screensaver_handler_border_fullscreen)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_fullscreen);
        _e_screensaver_handler_border_fullscreen = NULL;
     }

   if (_e_screensaver_handler_border_unfullscreen)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_unfullscreen);
        _e_screensaver_handler_border_unfullscreen = NULL;
     }

   if (_e_screensaver_handler_border_remove)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_remove);
        _e_screensaver_handler_border_remove = NULL;
     }

   if (_e_screensaver_handler_border_iconify)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_iconify);
        _e_screensaver_handler_border_iconify = NULL;
     }

   if (_e_screensaver_handler_border_uniconify)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_uniconify);
        _e_screensaver_handler_border_uniconify = NULL;
     }

   if (_e_screensaver_handler_border_desk_set)
     {
        ecore_event_handler_del(_e_screensaver_handler_border_desk_set);
        _e_screensaver_handler_border_desk_set = NULL;
     }

   if (_e_screensaver_handler_desk_show)
     {
        ecore_event_handler_del(_e_screensaver_handler_desk_show);
        _e_screensaver_handler_desk_show = NULL;
     }

   return 1;
}

E_API void
e_screensaver_attrs_set(int timeout, int blanking, int expose)
{
   _e_screensaver_timeout = timeout;
//   _e_screensaver_interval = ecore_x_screensaver_interval_get();
   _e_screensaver_blanking = blanking;
   _e_screensaver_expose = expose;
}

E_API Eina_Bool
e_screensaver_on_get(void)
{
   return _e_screensaver_on;
}

E_API void
e_screensaver_activate(void)
{
   if (e_screensaver_on_get()) return;

   E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
     ecore_x_screensaver_activate();
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     e_comp_wl_screensaver_activate();
#endif
}

E_API void
e_screensaver_deactivate(void)
{
   if (!e_screensaver_on_get()) return;

   E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type != E_PIXMAP_TYPE_WL)
     ecore_x_screensaver_reset();
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     e_comp_canvas_notidle();
#endif
}

E_API void
e_screensaver_now_set(Eina_Bool now)
{
   _screensaver_now = now;
}

E_API void
e_screensaver_eval(Eina_Bool saver_on)
{
   Eina_Bool use_special_instead_of_dim = EINA_FALSE;

   if ((e_desklock_state_get()) &&
       (e_config->screensaver_desklock_timeout > 0))
     use_special_instead_of_dim = EINA_TRUE;
   if (saver_on)
     {
        if ((e_config->backlight.idle_dim) &&
            (!use_special_instead_of_dim))
          {
             double t2 = e_config->backlight.timer;

             if ((e_powersave_mode_get() > E_POWERSAVE_MODE_LOW) &&
                 (e_config->backlight.battery_timer > 0.0))
               t2 = e_config->backlight.battery_timer;
             double t = e_config->screensaver_timeout - t2;

             if (t < 1.0) t = 1.0;
             if (_screensaver_now) t = 1.0;
             E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
             if (!_screensaver_ignore)
               {
                  if (e_config->screensaver_enable)
                    screensaver_idle_timer = ecore_timer_loop_add
                      (t, _e_screensaver_idle_timer_cb, NULL);
                  if (e_backlight_mode_get(NULL) != E_BACKLIGHT_MODE_DIM)
                    {
                       e_backlight_mode_set(NULL, E_BACKLIGHT_MODE_DIM);
                       screensaver_dimmed = EINA_TRUE;
                    }
               }
          }
        else
          {
             if (!_screensaver_ignore)
               {
                  if (!e_screensaver_on_get())
                    {
                       e_powersave_mode_screen_set(E_POWERSAVE_MODE_FREEZE);
                       ecore_event_add(E_EVENT_SCREENSAVER_ON, NULL, NULL, NULL);
                    }
               }
          }
        return;
     }
   else
     {
#ifdef HAVE_WAYLAND
        if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
          ecore_event_add(E_EVENT_SCREENSAVER_OFF_PRE, NULL, NULL, NULL);
#endif
     }
   if (screensaver_idle_timer)
     {
        E_FREE_FUNC(screensaver_idle_timer, ecore_timer_del);
        if ((e_config->backlight.idle_dim) &&
            (!use_special_instead_of_dim))
          {
             if (e_backlight_mode_get(NULL) != E_BACKLIGHT_MODE_NORMAL)
               e_backlight_mode_set(NULL, E_BACKLIGHT_MODE_NORMAL);
          }
        return;
     }
   if (screensaver_dimmed)
     {
        if (e_backlight_mode_get(NULL) != E_BACKLIGHT_MODE_NORMAL)
          e_backlight_mode_set(NULL, E_BACKLIGHT_MODE_NORMAL);
        screensaver_dimmed = EINA_FALSE;
     }
   if (!_screensaver_ignore)
     {
        if (e_screensaver_on_get())
          {
             e_powersave_mode_screen_unset();
             e_screensaver_update();
             ecore_event_add(E_EVENT_SCREENSAVER_OFF, NULL, NULL, NULL);
          }
     }
}

E_API void
e_screensaver_inhibit_toggle(Eina_Bool inhibit)
{
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type != E_PIXMAP_TYPE_WL) return;
   e_comp_wl_screensaver_inhibit(inhibit);
#else
   (void)inhibit;
#endif
}

#include "e.h"

#define SMART_NAME     "e_scrollframe"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;

struct _E_Smart_Data
{
   Evas_Coord           x, y, w, h;

   Evas_Object         *smart_obj;
   Evas_Object         *child_obj;
   Evas_Object         *pan_obj;
   Evas_Object         *edje_obj;
   Evas_Object         *event_obj;

   E_Scrollframe_Policy hbar_flags, vbar_flags;

   struct
   {
      Evas_Coord      x, y;
      Evas_Coord      sx, sy;
      Evas_Coord      dx, dy;
      struct
      {
         Evas_Coord x, y;
         double     timestamp;
      } history[20];
      double          anim_start;
      Ecore_Animator *momentum_animator;
      Evas_Coord      locked_x, locked_y;
      unsigned char   now E_BITFIELD;
      unsigned char   dragged E_BITFIELD;
      unsigned char   dir_x E_BITFIELD;
      unsigned char   dir_y E_BITFIELD;
      unsigned char   locked E_BITFIELD;
   } down;

   struct
   {
      Evas_Coord w, h;
   } child;
   struct
   {
      Evas_Coord x, y;
   } step, page;

   struct
   {
      void (*set)(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
      void (*get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y);
      void (*max_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y);
      void (*child_size_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y);
   } pan_func;
   struct
   {
      Eina_Bool forced E_BITFIELD;
   } thumbscroll;

   unsigned char hbar_visible E_BITFIELD;
   unsigned char vbar_visible E_BITFIELD;
   unsigned char extern_pan E_BITFIELD;
   unsigned char one_dir_at_a_time E_BITFIELD;
   Eina_Bool     key_nav E_BITFIELD;
};

/* local subsystem functions */
static void      _e_smart_child_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_pan_changed_hook(void *data, Evas_Object *obj, void *event_info);
static void      _e_smart_pan_pan_changed_hook(void *data, Evas_Object *obj, void *event_info);
static void      _e_smart_event_wheel(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static Eina_Bool _e_smart_momentum_animator(void *data);
static void      _e_smart_event_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_edje_drag_v(void *data, Evas_Object *obj, const char *emission, const char *source);
static void      _e_smart_edje_drag_h(void *data, Evas_Object *obj, const char *emission, const char *source);
static void      _e_smart_scrollbar_read(E_Smart_Data *sd);
static void      _e_smart_scrollbar_reset(E_Smart_Data *sd);
static int       _e_smart_scrollbar_bar_h_visibility_adjust(E_Smart_Data *sd);
static int       _e_smart_scrollbar_bar_v_visibility_adjust(E_Smart_Data *sd);
static void      _e_smart_scrollbar_bar_visibility_adjust(E_Smart_Data *sd);
static void      _e_smart_scrollbar_size_adjust(E_Smart_Data *sd);
static void      _e_smart_reconfigure(E_Smart_Data *sd);
static void      _e_smart_add(Evas_Object *obj);
static void      _e_smart_del(Evas_Object *obj);
static void      _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void      _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void      _e_smart_show(Evas_Object *obj);
static void      _e_smart_hide(Evas_Object *obj);
static void      _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void      _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void      _e_smart_clip_unset(Evas_Object *obj);
static void      _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_scrollframe_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_scrollframe_child_set(Evas_Object *obj, Evas_Object *child)
{
   Evas_Object *o;

   API_ENTRY return;
   if (sd->child_obj)
     {
        if (sd->pan_obj)
          {
             e_pan_child_set(sd->pan_obj, NULL);
             evas_object_event_callback_del(sd->child_obj, EVAS_CALLBACK_FREE,
                                            _e_smart_child_del_hook);
          }
     }

   sd->child_obj = child;
   if (!child) return;

   if (!sd->pan_obj)
     {
        o = e_pan_add(evas_object_evas_get(obj));
        sd->pan_obj = o;
        evas_object_smart_callback_add(o, "changed",
                                       _e_smart_pan_changed_hook, sd);
        evas_object_smart_callback_add(o, "pan_changed",
                                       _e_smart_pan_pan_changed_hook, sd);
        evas_object_show(o);
        edje_object_part_swallow(sd->edje_obj, "e.swallow.content", o);
     }

   sd->pan_func.set = e_pan_set;
   sd->pan_func.get = e_pan_get;
   sd->pan_func.max_get = e_pan_max_get;
   sd->pan_func.child_size_get = e_pan_child_size_get;

   evas_object_event_callback_add(child, EVAS_CALLBACK_FREE,
                                  _e_smart_child_del_hook, sd);
   e_pan_child_set(sd->pan_obj, sd->child_obj);
   sd->pan_func.child_size_get(sd->pan_obj, &sd->child.w, &sd->child.h);
   _e_smart_scrollbar_size_adjust(sd);
   _e_smart_scrollbar_reset(sd);
}

E_API void
e_scrollframe_extern_pan_set(Evas_Object *obj, Evas_Object *pan,
                             void (*pan_set)(Evas_Object *obj, Evas_Coord x, Evas_Coord y),
                             void (*pan_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y),
                             void (*pan_max_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y),
                             void (*pan_child_size_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y))
{
   API_ENTRY return;

   e_scrollframe_child_set(obj, NULL);
   if (sd->extern_pan)
     {
        if (sd->pan_obj)
          {
             edje_object_part_unswallow(sd->edje_obj, sd->pan_obj);
             sd->pan_obj = NULL;
          }
     }
   else
     {
        if (sd->pan_obj)
          {
             evas_object_del(sd->pan_obj);
             sd->pan_obj = NULL;
          }
     }
   if (!pan)
     {
        sd->extern_pan = 0;
        return;
     }

   sd->pan_obj = pan;
   sd->pan_func.set = pan_set;
   sd->pan_func.get = pan_get;
   sd->pan_func.max_get = pan_max_get;
   sd->pan_func.child_size_get = pan_child_size_get;
   sd->extern_pan = 1;
   evas_object_smart_callback_add(sd->pan_obj, "changed",
                                  _e_smart_pan_changed_hook, sd);
   evas_object_smart_callback_add(sd->pan_obj, "pan_changed",
                                  _e_smart_pan_pan_changed_hook, sd);
   edje_object_part_swallow(sd->edje_obj, "e.swallow.content", sd->pan_obj);
   evas_object_show(sd->pan_obj);
}

E_API int
e_scrollframe_custom_theme_set(Evas_Object *obj, const char *custom_category, const char *custom_group)
{
   API_ENTRY return 0;

   if (!e_theme_edje_object_set(sd->edje_obj, custom_category, custom_group))
     return 0;
   if (sd->pan_obj)
     edje_object_part_swallow(sd->edje_obj, "e.swallow.content", sd->pan_obj);
   sd->vbar_visible = !sd->vbar_visible;
   sd->hbar_visible = !sd->hbar_visible;
   _e_smart_scrollbar_bar_visibility_adjust(sd);
   return 1;
}

E_API int
e_scrollframe_custom_edje_file_set(Evas_Object *obj, const char *file, const char *group)
{
   API_ENTRY return 0;

   if (!edje_object_file_set(sd->edje_obj, file, group)) return 0;
   if (sd->pan_obj)
     edje_object_part_swallow(sd->edje_obj, "e.swallow.content", sd->pan_obj);
   sd->vbar_visible = !sd->vbar_visible;
   sd->hbar_visible = !sd->hbar_visible;
   _e_smart_scrollbar_bar_visibility_adjust(sd);
   return 1;
}

E_API void
e_scrollframe_child_pos_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   Evas_Coord mx = 0, my = 0;
   double vx, vy;

   API_ENTRY return;
   if (!sd->pan_obj) return;
   sd->pan_func.max_get(sd->pan_obj, &mx, &my);
   if (mx > 0) vx = (double)x / (double)mx;
   else vx = 0.0;
   if (vx < 0.0) vx = 0.0;
   else if (vx > 1.0)
     vx = 1.0;
   if (my > 0) vy = (double)y / (double)my;
   else vy = 0.0;
   if (vy < 0.0) vy = 0.0;
   else if (vy > 1.0)
     vy = 1.0;
   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.vbar", 0.0, vy);
   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.hbar", vx, 0.0);
   sd->pan_func.set(sd->pan_obj, x, y);
}

E_API void
e_scrollframe_child_pos_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   API_ENTRY return;
   if (sd->pan_obj)
     sd->pan_func.get(sd->pan_obj, x, y);
   else
     {
        *x = 0;
        *y = 0;
     }
}

E_API void
e_scrollframe_child_region_show(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Evas_Coord mx = 0, my = 0, cw = 0, ch = 0, px = 0, py = 0, nx, ny, vw, vh;

   API_ENTRY return;
   if (!sd->pan_obj) return;
   sd->pan_func.max_get(sd->pan_obj, &mx, &my);
   sd->pan_func.child_size_get(sd->pan_obj, &cw, &ch);
   sd->pan_func.get(sd->pan_obj, &px, &py);
   e_scrollframe_child_viewport_size_get(obj, &vw, &vh);

   /* Abort if the request region is already shown */
   if ((px <= x) && (py <= y) && (x + w <= px + vw) && (y + h <= py + vh))
     return;

   nx = px;
   if (x < px) nx = x;
   else if ((x + w) > (px + (cw - mx)))
     {
        nx = x + w - (cw - mx);
        if (nx > x) nx = x;
     }
   ny = py;
   if (y < py) ny = y;
   else if ((y + h) > (py + (ch - my)))
     {
        ny = y + h - (ch - my);
        if (ny > y) ny = y;
     }
   if ((nx == px) && (ny == py)) return;
   e_scrollframe_child_pos_set(obj, nx, ny);
}

E_API void
e_scrollframe_child_viewport_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   API_ENTRY return;
   edje_object_calc_force(sd->edje_obj);
   edje_object_part_geometry_get(sd->edje_obj,
                                 "e.swallow.content", NULL, NULL, w, h);
}

E_API void
e_scrollframe_step_size_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   API_ENTRY return;
   if (x < 1) x = 1;
   if (y < 1) y = 1;
   sd->step.x = x;
   sd->step.y = y;
   _e_smart_scrollbar_size_adjust(sd);
}

E_API void
e_scrollframe_step_size_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   API_ENTRY return;
   if (x) *x = sd->step.x;
   if (y) *y = sd->step.y;
}

E_API void
e_scrollframe_page_size_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   API_ENTRY return;
   sd->page.x = x;
   sd->page.y = y;
   _e_smart_scrollbar_size_adjust(sd);
}

E_API void
e_scrollframe_page_size_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   API_ENTRY return;
   if (x) *x = sd->page.x;
   if (y) *y = sd->page.y;
}

E_API void
e_scrollframe_policy_set(Evas_Object *obj, E_Scrollframe_Policy hbar, E_Scrollframe_Policy vbar)
{
   API_ENTRY return;
   if ((sd->hbar_flags == hbar) && (sd->vbar_flags == vbar)) return;
   sd->hbar_flags = hbar;
   sd->vbar_flags = vbar;
   sd->vbar_visible = !sd->vbar_visible;
   sd->hbar_visible = !sd->hbar_visible;
   _e_smart_scrollbar_size_adjust(sd);
}

E_API void
e_scrollframe_policy_get(Evas_Object *obj, E_Scrollframe_Policy *hbar, E_Scrollframe_Policy *vbar)
{
   API_ENTRY return;
   if (hbar) *hbar = sd->hbar_flags;
   if (vbar) *vbar = sd->vbar_flags;
}

E_API Evas_Object *
e_scrollframe_edje_object_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->edje_obj;
}

E_API void
e_scrollframe_single_dir_set(Evas_Object *obj, Eina_Bool single_dir)
{
   API_ENTRY return;
   sd->one_dir_at_a_time = single_dir;
}

E_API Eina_Bool
e_scrollframe_single_dir_get(Evas_Object *obj)
{
   API_ENTRY return EINA_FALSE;
   return sd->one_dir_at_a_time;
}

E_API void
e_scrollframe_thumbscroll_force(Evas_Object *obj, Eina_Bool forced)
{
   API_ENTRY return;
   sd->thumbscroll.forced = forced;
}

E_API void
e_scrollframe_key_navigation_set(Evas_Object *obj, Eina_Bool enabled)
{
   API_ENTRY return;
   if (enabled == sd->key_nav)
     return;

   sd->key_nav = enabled;
   if (sd->key_nav)
     evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN,
                                    _e_smart_event_key_down, sd);
   else
     evas_object_event_callback_del_full(obj, EVAS_CALLBACK_KEY_DOWN,
                                         _e_smart_event_key_down, sd);
}

/* local subsystem functions */
static void
_e_smart_edje_drag_v(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd;

   sd = data;
   _e_smart_scrollbar_read(sd);
}

static void
_e_smart_edje_drag_h(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd;

   sd = data;
   _e_smart_scrollbar_read(sd);
}

static void
_e_smart_child_del_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;

   sd = data;
   sd->child_obj = NULL;
   _e_smart_scrollbar_size_adjust(sd);
   _e_smart_scrollbar_reset(sd);
}

static void
_e_smart_pan_changed_hook(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Coord w, h;
   E_Smart_Data *sd;

   sd = data;
   if (!sd->pan_obj) return;
   sd->pan_func.child_size_get(sd->pan_obj, &w, &h);
   if ((w != sd->child.w) || (h != sd->child.h))
     {
        sd->child.w = w;
        sd->child.h = h;
        _e_smart_scrollbar_size_adjust(sd);
     }
}

static void
_e_smart_pan_pan_changed_hook(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Coord x, y;
   E_Smart_Data *sd;

   sd = data;
   if (!sd->pan_obj) return;
   sd->pan_func.get(sd->pan_obj, &x, &y);
   e_scrollframe_child_pos_set(sd->smart_obj, x, y);
}

static void
_e_smart_event_wheel(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Wheel *ev;
   E_Smart_Data *sd;
   Evas_Coord x = 0, y = 0;

   sd = data;
   ev = event_info;
   e_scrollframe_child_pos_get(sd->smart_obj, &x, &y);

   if ((sd->vbar_visible) && ((ev->direction % 2 == 0) || (!sd->hbar_visible)))
     y += ev->z * sd->step.y;
   else if ((sd->hbar_visible) &&
            ((ev->direction % 2 == 1) || (!sd->vbar_visible)))
     x += ev->z * sd->step.x;

   e_scrollframe_child_pos_set(sd->smart_obj, x, y);
}

static void
_e_smart_event_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if ((e_config->thumbscroll_enable) || (sd->thumbscroll.forced))
     {
        if (sd->down.momentum_animator)
          {
             ecore_animator_del(sd->down.momentum_animator);
             sd->down.momentum_animator = NULL;
          }
        if (ev->button == 1)
          {
             sd->down.now = 1;
             sd->down.dragged = 0;
             sd->down.dir_x = 0;
             sd->down.dir_y = 0;
             sd->down.x = ev->canvas.x;
             sd->down.y = ev->canvas.y;
             e_scrollframe_child_pos_get(sd->smart_obj,
                                         &sd->down.sx, &sd->down.sy);
             sd->down.locked = 0;
             memset(&(sd->down.history[0]), 0, sizeof(sd->down.history[0]) * 20);
             sd->down.history[0].timestamp = ecore_loop_time_get();
             sd->down.history[0].x = ev->canvas.x;
             sd->down.history[0].y = ev->canvas.y;
          }
     }
}

static Eina_Bool
_e_smart_momentum_animator(void *data)
{
   E_Smart_Data *sd;
   double t, dt, p;
   Evas_Coord x, y, dx, dy;

   sd = data;
   t = ecore_loop_time_get();
   dt = t - sd->down.anim_start;
   if (dt >= 0.0)
     {
        dt = dt / e_config->thumbscroll_friction;
        if (dt > 1.0) dt = 1.0;
        p = 1.0 - ((1.0 - dt) * (1.0 - dt) * (1.0 - dt));
        dx = (sd->down.dx * e_config->thumbscroll_friction * p);
        dy = (sd->down.dy * e_config->thumbscroll_friction * p);
        x = sd->down.sx - dx;
        y = sd->down.sy - dy;
        e_scrollframe_child_pos_set(sd->smart_obj, x, y);
        if (dt >= 1.0)
          {
             sd->down.momentum_animator = NULL;
             return ECORE_CALLBACK_CANCEL;
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_smart_event_mouse_up(void *data, Evas *e, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;
   Evas_Coord x = 0, y = 0;

   sd = data;
   ev = event_info;
   if ((e_config->thumbscroll_enable) || (sd->thumbscroll.forced))
     {
        if (ev->button == 1)
          {
             x = ev->canvas.x - sd->down.x;
             y = ev->canvas.y - sd->down.y;
             if (sd->down.dragged)
               {
                  double t, at, dt;
                  int i;
                  Evas_Coord ax, ay, dx, dy, vel;

                  t = ecore_loop_time_get();
                  ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
                  ax = ev->canvas.x;
                  ay = ev->canvas.y;
                  at = 0.0;
                  for (i = 0; i < 20; i++)
                    {
                       dt = t - sd->down.history[i].timestamp;
                       if (dt > 0.2) break;
                       at += dt;
                       ax += sd->down.history[i].x;
                       ay += sd->down.history[i].y;
                    }
                  ax /= (i + 1);
                  ay /= (i + 1);
                  at /= (i + 1);
                  at *= 4.0;
                  dx = ev->canvas.x - ax;
                  dy = ev->canvas.y - ay;
                  if (at > 0)
                    {
                       vel = sqrt((dx * dx) + (dy * dy)) / at;
                       if ((e_config->thumbscroll_friction > 0.0) &&
                           (vel > e_config->thumbscroll_momentum_threshhold))
                         {
                            if (!sd->down.momentum_animator)
                              sd->down.momentum_animator =
                                ecore_animator_add(_e_smart_momentum_animator, sd);
                            sd->down.dx = ((double)dx / at);
                            sd->down.dy = ((double)dy / at);
                            sd->down.anim_start = t;
                            e_scrollframe_child_pos_get(sd->smart_obj, &x, &y);
                            sd->down.sx = x;
                            sd->down.sy = y;
                         }
                    }
                  evas_event_feed_hold(e, 0, ev->timestamp, ev->data);
               }
             sd->down.dragged = 0;
             sd->down.now = 0;
          }
     }
}

static void
_e_smart_event_mouse_move(void *data, Evas *e, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Smart_Data *sd;
   Evas_Coord x = 0, y = 0;

   sd = data;
   ev = event_info;
   if ((e_config->thumbscroll_enable) || (sd->thumbscroll.forced))
     {
        if (sd->down.now)
          {
             memmove(&(sd->down.history[1]), &(sd->down.history[0]),
                     sizeof(sd->down.history[0]) * 19);
             sd->down.history[0].timestamp = ecore_loop_time_get();
             sd->down.history[0].x = ev->cur.canvas.x;
             sd->down.history[0].y = ev->cur.canvas.y;

             x = ev->cur.canvas.x - sd->down.x;
             if (x < 0) x = -x;
             y = ev->cur.canvas.y - sd->down.y;
             if (y < 0) y = -y;
             if ((sd->one_dir_at_a_time) &&
                 (!sd->down.dir_x) && (!sd->down.dir_y))
               {
                  if (x > y)
                    {
                       if (x > e_config->thumbscroll_threshhold)
                         {
                            sd->down.dir_x = 1;
                            sd->down.dir_y = 0;
                         }
                    }
                  else
                    {
                       if (y > e_config->thumbscroll_threshhold)
                         {
                            sd->down.dir_x = 0;
                            sd->down.dir_y = 1;
                         }
                    }
               }
             if ((sd->down.dragged) ||
                 (((x * x) + (y * y)) >
                  (e_config->thumbscroll_threshhold *
                   e_config->thumbscroll_threshhold)))
               {
                  if ((e_config->thumbscroll_enable) ||
                      (sd->thumbscroll.forced))
                    {
                       if (!sd->down.dragged)
                         evas_event_feed_hold(e, 1, ev->timestamp, ev->data);
                       ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
                       sd->down.dragged = 1;
                       x = sd->down.sx - (ev->cur.canvas.x - sd->down.x);
                       y = sd->down.sy - (ev->cur.canvas.y - sd->down.y);
                       if ((sd->down.dir_x) || (sd->down.dir_y))
                         {
                            if (!sd->down.locked)
                              {
                                 sd->down.locked_x = x;
                                 sd->down.locked_y = y;
                                 sd->down.locked = 1;
                              }
                            if (sd->down.dir_x) y = sd->down.locked_y;
                            else x = sd->down.locked_x;
                         }
                       e_scrollframe_child_pos_set(sd->smart_obj, x, y);
                    }
               }
          }
     }
}

static void
_e_smart_event_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Key_Down *ev;
   E_Smart_Data *sd;
   Evas_Coord x = 0, y = 0, vw = 0, vh = 0, mx = 0, my = 0;

   sd = data;
   ev = event_info;
   e_scrollframe_child_pos_get(sd->smart_obj, &x, &y);
   if (!sd->pan_obj) return;
   sd->pan_func.max_get(sd->pan_obj, &mx, &my);
   edje_object_part_geometry_get(sd->edje_obj, "e.swallow.content", NULL, NULL, &vw, &vh);
   if (!strcmp(ev->key, "Left"))
     x -= sd->step.x;
   else if (!strcmp(ev->key, "Right"))
     x += sd->step.x;
   else if (!strcmp(ev->key, "Up"))
     y -= sd->step.y;
   else if (!strcmp(ev->key, "Home"))
     y = 0;
   else if (!strcmp(ev->key, "End"))
     y = my;
   else if (!strcmp(ev->key, "Down"))
     y += sd->step.y;
   else if (!strcmp(ev->key, "Prior"))
     {
        if (sd->page.y < 0)
          y -= -(sd->page.y * vh) / 100;
        else
          y -= sd->page.y;
     }
   else if (!strcmp(ev->key, "Next"))
     {
        if (sd->page.y < 0)
          y += -(sd->page.y * vh) / 100;
        else
          y += sd->page.y;
     }
   e_scrollframe_child_pos_set(sd->smart_obj, x, y);
}

static void
_e_smart_scrollbar_read(E_Smart_Data *sd)
{
   Evas_Coord x, y, mx = 0, my = 0;
   double vx = 0.0, vy = 0.0;

   edje_object_part_drag_value_get(sd->edje_obj, "e.dragable.vbar", NULL, &vy);
   edje_object_part_drag_value_get(sd->edje_obj, "e.dragable.hbar", &vx, NULL);
   if (!sd->pan_obj) return;
   sd->pan_func.max_get(sd->pan_obj, &mx, &my);
   x = vx * (double)mx;
   y = vy * (double)my;
   if (sd->pan_obj)
     sd->pan_func.set(sd->pan_obj, x, y);
   if (((e_config->thumbscroll_enable) || (sd->thumbscroll.forced))
       && (sd->down.now) && (!sd->down.dragged))
     sd->down.now = 0;
}

static void
_e_smart_scrollbar_reset(E_Smart_Data *sd)
{
   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.vbar", 0.0, 0.0);
   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.hbar", 0.0, 0.0);
   if ((!sd->child_obj) && (!sd->extern_pan))
     {
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.vbar", 1.0, 1.0);
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.hbar", 1.0, 1.0);
     }
   if (sd->pan_obj)
     sd->pan_func.set(sd->pan_obj, 0, 0);
}

static int
_e_smart_scrollbar_bar_v_visibility_adjust(E_Smart_Data *sd)
{
   int scroll_v_vis_change = 0;
   Evas_Coord h, vh;

   h = sd->child.h;
   edje_object_part_geometry_get(sd->edje_obj, "e.swallow.content",
                                 NULL, NULL, NULL, &vh);
   if (sd->vbar_visible)
     {
        if (sd->vbar_flags == E_SCROLLFRAME_POLICY_AUTO)
          {
             if ((sd->child_obj) || (sd->extern_pan))
               {
                  if (h <= vh)
                    {
                       scroll_v_vis_change = 1;
                       sd->vbar_visible = 0;
                    }
               }
             else
               {
                  scroll_v_vis_change = 1;
                  sd->vbar_visible = 0;
               }
          }
        else if (sd->vbar_flags == E_SCROLLFRAME_POLICY_OFF)
          {
             scroll_v_vis_change = 1;
             sd->vbar_visible = 0;
          }
     }
   else
     {
        if (sd->vbar_flags == E_SCROLLFRAME_POLICY_AUTO)
          {
             if ((sd->child_obj) || (sd->extern_pan))
               {
                  if (h > vh)
                    {
                       scroll_v_vis_change = 1;
                       sd->vbar_visible = 1;
                    }
               }
          }
        else if (sd->vbar_flags == E_SCROLLFRAME_POLICY_ON)
          {
             scroll_v_vis_change = 1;
             sd->vbar_visible = 1;
          }
     }
   if (scroll_v_vis_change)
     {
        if (sd->vbar_visible)
          edje_object_signal_emit(sd->edje_obj, "e,action,show,vbar", "e");
        else
          edje_object_signal_emit(sd->edje_obj, "e,action,hide,vbar", "e");
        edje_object_message_signal_process(sd->edje_obj);
        _e_smart_scrollbar_size_adjust(sd);
     }
   return scroll_v_vis_change;
}

static int
_e_smart_scrollbar_bar_h_visibility_adjust(E_Smart_Data *sd)
{
   int scroll_h_vis_change = 0;
   Evas_Coord w, vw;

   w = sd->child.w;
   edje_object_part_geometry_get(sd->edje_obj, "e.swallow.content",
                                 NULL, NULL, &vw, NULL);
   if (sd->hbar_visible)
     {
        if (sd->hbar_flags == E_SCROLLFRAME_POLICY_AUTO)
          {
             if ((sd->child_obj) || (sd->extern_pan))
               {
                  if (w <= vw)
                    {
                       scroll_h_vis_change = 1;
                       sd->hbar_visible = 0;
                    }
               }
             else
               {
                  scroll_h_vis_change = 1;
                  sd->hbar_visible = 0;
               }
          }
        else if (sd->hbar_flags == E_SCROLLFRAME_POLICY_OFF)
          {
             scroll_h_vis_change = 1;
             sd->hbar_visible = 0;
          }
     }
   else
     {
        if (sd->hbar_flags == E_SCROLLFRAME_POLICY_AUTO)
          {
             if ((sd->child_obj) || (sd->extern_pan))
               {
                  if (w > vw)
                    {
                       scroll_h_vis_change = 1;
                       sd->hbar_visible = 1;
                    }
               }
          }
        else if (sd->hbar_flags == E_SCROLLFRAME_POLICY_ON)
          {
             scroll_h_vis_change = 1;
             sd->hbar_visible = 1;
          }
     }
   if (scroll_h_vis_change)
     {
        if (sd->hbar_visible)
          edje_object_signal_emit(sd->edje_obj, "e,action,show,hbar", "e");
        else
          edje_object_signal_emit(sd->edje_obj, "e,action,hide,hbar", "e");
        edje_object_message_signal_process(sd->edje_obj);
        _e_smart_scrollbar_size_adjust(sd);
     }
   return scroll_h_vis_change;
}

static void
_e_smart_scrollbar_bar_visibility_adjust(E_Smart_Data *sd)
{
   int changed = 0;

   changed |= _e_smart_scrollbar_bar_h_visibility_adjust(sd);
   changed |= _e_smart_scrollbar_bar_v_visibility_adjust(sd);
   if (changed)
     {
        changed |= _e_smart_scrollbar_bar_h_visibility_adjust(sd);
        changed |= _e_smart_scrollbar_bar_v_visibility_adjust(sd);
     }
   if (changed)
     {
        _e_smart_scrollbar_bar_h_visibility_adjust(sd);
        _e_smart_scrollbar_bar_v_visibility_adjust(sd);
     }
}

static void
_e_smart_scrollbar_size_adjust(E_Smart_Data *sd)
{
   if ((sd->child_obj) || (sd->extern_pan))
     {
        Evas_Coord x = 0, y = 0, w = 0, h = 0, mx = 0, my = 0, vw = 0, vh = 0;
        double vx = 0.0, vy = 0.0, size = 0.0;

        edje_object_calc_force(sd->edje_obj);
        edje_object_part_geometry_get(sd->edje_obj, "e.swallow.content",
                                      NULL, NULL, &vw, &vh);
        w = sd->child.w;
        if (w < 1) w = 1;
        size = (double)vw / (double)w;
        if (size > 1.0)
          {
             size = 1.0;
             edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.hbar",
                                             0.0, 0.0);
          }
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.hbar",
                                       size, 1.0);

        h = sd->child.h;
        if (h < 1) h = 1;
        size = (double)vh / (double)h;
        if (size > 1.0)
          {
             size = 1.0;
             edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.vbar",
                                             0.0, 0.0);
          }
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.vbar",
                                       1.0, size);

        edje_object_part_drag_value_get(sd->edje_obj, "e.dragable.hbar",
                                        &vx, NULL);
        edje_object_part_drag_value_get(sd->edje_obj, "e.dragable.vbar",
                                        NULL, &vy);
        if (sd->pan_obj)
          {
             sd->pan_func.max_get(sd->pan_obj, &mx, &my);
             x = vx * mx;
             y = vy * my;
          }

        edje_object_part_drag_step_set(sd->edje_obj, "e.dragable.hbar",
                                       (double)sd->step.x / (double)w, 0.0);
        edje_object_part_drag_step_set(sd->edje_obj, "e.dragable.vbar",
                                       0.0, (double)sd->step.y / (double)h);
        if (sd->page.x > 0)
          edje_object_part_drag_page_set(sd->edje_obj, "e.dragable.hbar",
                                         (double)sd->page.x / (double)w, 0.0);
        else
          edje_object_part_drag_page_set(sd->edje_obj, "e.dragable.hbar",
                                         -((double)sd->page.x * ((double)vw / (double)w)) / 100.0, 0.0);
        if (sd->page.y > 0)
          edje_object_part_drag_page_set(sd->edje_obj, "e.dragable.vbar",
                                         0.0, (double)sd->page.y / (double)h);
        else
          edje_object_part_drag_page_set(sd->edje_obj, "e.dragable.vbar",
                                         0.0, -((double)sd->page.y * ((double)vh / (double)h)) / 100.0);

        if (sd->pan_obj)
          sd->pan_func.set(sd->pan_obj, x, y);
     }
   else
     {
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.vbar",
                                       1.0, 1.0);
        edje_object_part_drag_size_set(sd->edje_obj, "e.dragable.hbar",
                                       1.0, 1.0);
        if (sd->pan_obj)
          sd->pan_func.set(sd->pan_obj, 0, 0);
     }
   _e_smart_scrollbar_bar_visibility_adjust(sd);
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   evas_object_move(sd->edje_obj, sd->x, sd->y);
   evas_object_resize(sd->edje_obj, sd->w, sd->h);
   evas_object_move(sd->event_obj, sd->x, sd->y);
   evas_object_resize(sd->event_obj, sd->w, sd->h);
   _e_smart_scrollbar_size_adjust(sd);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Evas_Object *o;

   sd = E_NEW(E_Smart_Data, 1);
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->step.x = 32;
   sd->step.y = 32;
   sd->page.x = -50;
   sd->page.y = -50;
   sd->hbar_flags = E_SCROLLFRAME_POLICY_AUTO;
   sd->vbar_flags = E_SCROLLFRAME_POLICY_AUTO;
   sd->key_nav = EINA_TRUE;

   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN,
                                  _e_smart_event_key_down, sd);
   evas_object_propagate_events_set(obj, 0);

   o = edje_object_add(evas_object_evas_get(obj));
   sd->edje_obj = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/scrollframe");
   edje_object_signal_callback_add(o, "drag*", "e.dragable.vbar",
                                   _e_smart_edje_drag_v, sd);
   edje_object_signal_callback_add(o, "drag*", "e.dragable.hbar",
                                   _e_smart_edje_drag_h, sd);
   evas_object_smart_member_add(o, obj);

   o = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   sd->event_obj = o;
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_WHEEL,
                                  _e_smart_event_wheel, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_smart_event_mouse_down, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP,
                                  _e_smart_event_mouse_up, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE,
                                  _e_smart_event_mouse_move, sd);
   evas_object_smart_member_add(o, obj);
   evas_object_repeat_events_set(o, 1);

   sd->pan_func.set = e_pan_set;
   sd->pan_func.get = e_pan_get;
   sd->pan_func.max_get = e_pan_max_get;
   sd->pan_func.child_size_get = e_pan_child_size_get;

   _e_smart_scrollbar_reset(sd);
   edje_object_signal_emit(sd->edje_obj, "e,action,hide,hbar", "e");
   edje_object_signal_emit(sd->edje_obj, "e,action,hide,vbar", "e");
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   e_scrollframe_child_set(obj, NULL);
   if (sd->pan_obj)
     {
        if (!sd->extern_pan) evas_object_del(sd->pan_obj);
     }
   evas_object_del(sd->edje_obj);
   evas_object_del(sd->event_obj);
   if (sd->down.momentum_animator)
     ecore_animator_del(sd->down.momentum_animator);
   E_FREE(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->edje_obj);
   evas_object_show(sd->event_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->edje_obj);
   evas_object_hide(sd->event_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->edje_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->edje_obj, clip);
   evas_object_clip_set(sd->event_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->edje_obj);
   evas_object_clip_unset(sd->event_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL, NULL, NULL, NULL, NULL, NULL, NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>

#include "e_macros.h"

#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

/* externally accessible functions */

#define SHSH(n, v) ((((v) << (n)) & 0xffffffff) | ((v) >> (32 - (n))))

/* this is ALMOST canonical sha1 - i took some shortcuts to make it smaller
 * when handling the last 64 bytes in a buffer to just keep it short. it
 * still generates decent hashes either way...
 */
E_API int
e_sha1_sum(unsigned char *data, int size, unsigned char *dst)
{
   unsigned int digest[5], word[80], wa, wb, wc, wd, we, t;
   unsigned char buf[64], *d;
   int idx, left, i;
   const unsigned int magic[4] =
   {
      0x5a827999,
      0x6ed9eba1,
      0x8f1bbcdc,
      0xca62c1d6
   };

   idx = 0;
   digest[0] = 0x67452301;
   digest[1] = 0xefcdab89;
   digest[2] = 0x98badcfe;
   digest[3] = 0x10325476;
   digest[4] = 0xc3d2e1f0;

   memset(buf, 0, sizeof(buf));
   for (left = size, d = data; left > 0; left--, d++)
     {
        if ((idx == 0) && (left < 64))
          {
             memset(buf, 0, 60);
             buf[60] = (size >> 24) & 0xff;
             buf[61] = (size >> 16) & 0xff;
             buf[62] = (size >> 8) & 0xff;
             buf[63] = (size) & 0xff;
          }
        buf[idx] = *d;
        idx++;
        if ((idx == 64) || (left == 1))
          {
             if ((left == 1) && (idx < 64)) buf[idx] = 0x80;
             for (i = 0; i < 16; i++)
               {
                  word[i] = (unsigned int)buf[(i * 4)    ] << 24;
                  word[i] |= (unsigned int)buf[(i * 4) + 1] << 16;
                  word[i] |= (unsigned int)buf[(i * 4) + 2] << 8;
                  word[i] |= (unsigned int)buf[(i * 4) + 3];
               }
             for (i = 16; i < 80; i++)
               word[i] = SHSH(1,
                              word[i - 3 ] ^ word[i - 8 ] ^
                              word[i - 14] ^ word[i - 16]);
             wa = digest[0];
             wb = digest[1];
             wc = digest[2];
             wd = digest[3];
             we = digest[4];
             for (i = 0; i < 80; i++)
               {
                  if (i < 20)
                    t = SHSH(5, wa) + ((wb & wc) | ((~wb) & wd)) +
                      we + word[i] + magic[0];
                  else if (i < 40)
                    t = SHSH(5, wa) + (wb ^ wc ^ wd) +
                      we + word[i] + magic[1];
                  else if (i < 60)
                    t = SHSH(5, wa) + ((wb & wc) | (wb & wd) | (wc & wd)) +
                      we + word[i] + magic[2];
                  else if (i < 80)
                    t = SHSH(5, wa) + (wb ^ wc ^ wd) +
                      we + word[i] + magic[3];
                  we = wd;
                  wd = wc;
                  wc = SHSH(30, wb);
                  wb = wa;
                  wa = t;
               }
             digest[0] += wa;
             digest[1] += wb;
             digest[2] += wc;
             digest[3] += wd;
             digest[4] += we;
             idx = 0;
          }
     }

   t = htonl(digest[0]); digest[0] = t;
   t = htonl(digest[1]); digest[1] = t;
   t = htonl(digest[2]); digest[2] = t;
   t = htonl(digest[3]); digest[3] = t;
   t = htonl(digest[4]); digest[4] = t;

   memcpy(dst, digest, 5 * 4);
   return 1;
}

/* local subsystem functions */
#include "e.h"

static void         _e_shelf_new_dialog_ok(void *data, char *text);
static void         _e_shelf_del_cb(void *d);
static void         _e_shelf_free(E_Shelf *es);
static void         _e_shelf_gadcon_min_size_request(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h);
static void         _e_shelf_gadcon_size_request(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h);
static Evas_Object *_e_shelf_gadcon_frame_request(void *data, E_Gadcon_Client *gcc, const char *style);
static void         _e_shelf_toggle_client_fix(E_Shelf *es);
static void         _e_shelf_cb_menu_config(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_shelf_cb_menu_edit(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_shelf_cb_menu_contents(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_shelf_cb_confirm_dialog_yes(void *data);
static void         _e_shelf_cb_menu_delete(void *data, E_Menu *m, E_Menu_Item *mi);
static void         _e_shelf_menu_append(E_Shelf *es, E_Menu *mn);
static void         _e_shelf_cb_menu_items_append(void *data, E_Gadcon_Client *gcc, E_Menu *mn);
static void         _e_shelf_cb_locked_set(void *data, int lock);
static void         _e_shelf_cb_urgent_show(void *data);
static void         _e_shelf_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static Eina_Bool    _e_shelf_cb_mouse_in(void *data, int type, void *event);
//static Eina_Bool    _e_shelf_cb_mouse_out(void *data, int type, void *event);
//static void          _e_shelf_cb_mouse_out2(E_Shelf *es, Evas *e, Evas_Object *obj, Evas_Event_Mouse_Out *ev);
static int          _e_shelf_cb_id_sort(const void *data1, const void *data2);
static void         _e_shelf_cb_menu_rename(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED);
static Eina_Bool    _e_shelf_cb_hide_animator(void *data);
static Eina_Bool    _e_shelf_cb_hide_animator_timer(void *data);
static Eina_Bool    _e_shelf_cb_hide_urgent_timer(void *data);
static Eina_Bool    _e_shelf_cb_instant_hide_timer(void *data);
static void         _e_shelf_menu_pre_cb(void *data, E_Menu *m);
static void         _e_shelf_gadcon_client_remove(void *data, E_Gadcon_Client *gcc);
static int          _e_shelf_gadcon_client_add(void *data, E_Gadcon_Client *gcc, const E_Gadcon_Client_Class *cc);
static const char  *_e_shelf_orient_icon_name_get(E_Shelf *s);
static void         _e_shelf_bindings_add(E_Shelf *es);
static void         _e_shelf_bindings_del(E_Shelf *es);
static Eina_Bool    _e_shelf_on_current_desk(E_Shelf *es, E_Event_Zone_Edge *ev);
static void         _e_shelf_cb_dummy_del(E_Shelf *, Evas *e, Evas_Object *obj, void *event_info);
static void         _e_shelf_cb_dummy_moveresize(E_Shelf *, Evas *e, Evas_Object *obj, void *event_info);
static Eina_Bool    _e_shelf_gadcon_populate_handler_cb(void *, int, void *);
static void         _e_shelf_event_rename_end_cb(void *data, E_Event_Shelf *ev);

static Eina_List *shelves = NULL;
static Eina_List *dummies = NULL;

static int orientations[] =
{
   [E_GADCON_ORIENT_FLOAT] = 2,
   [E_GADCON_ORIENT_HORIZ] = 2,
   [E_GADCON_ORIENT_VERT] = 2,
   [E_GADCON_ORIENT_LEFT] = 37,
   [E_GADCON_ORIENT_RIGHT] = 31,
   [E_GADCON_ORIENT_TOP] = 29,
   [E_GADCON_ORIENT_BOTTOM] = 23,
   [E_GADCON_ORIENT_CORNER_TL] = 19,
   [E_GADCON_ORIENT_CORNER_TR] = 17,
   [E_GADCON_ORIENT_CORNER_BL] = 13,
   [E_GADCON_ORIENT_CORNER_BR] = 11,
   [E_GADCON_ORIENT_CORNER_LT] = 7,
   [E_GADCON_ORIENT_CORNER_RT] = 5,
   [E_GADCON_ORIENT_CORNER_LB] = 3,
   [E_GADCON_ORIENT_CORNER_RB] = 2
};

static const char *orient_names[] =
{
   [E_GADCON_ORIENT_FLOAT] = N_("Float"),
   [E_GADCON_ORIENT_HORIZ] = N_("Horizontal"),
   [E_GADCON_ORIENT_VERT] = N_("Vertical"),
   [E_GADCON_ORIENT_LEFT] = N_("Left"),
   [E_GADCON_ORIENT_RIGHT] = N_("Right"),
   [E_GADCON_ORIENT_TOP] = N_("Top"),
   [E_GADCON_ORIENT_BOTTOM] = N_("Bottom"),
   [E_GADCON_ORIENT_CORNER_TL] = N_("Top-left Corner"),
   [E_GADCON_ORIENT_CORNER_TR] = N_("Top-right Corner"),
   [E_GADCON_ORIENT_CORNER_BL] = N_("Bottom-left Corner"),
   [E_GADCON_ORIENT_CORNER_BR] = N_("Bottom-right Corner"),
   [E_GADCON_ORIENT_CORNER_LT] = N_("Left-top Corner"),
   [E_GADCON_ORIENT_CORNER_RT] = N_("Right-top Corner"),
   [E_GADCON_ORIENT_CORNER_LB] = N_("Left-bottom Corner"),
   [E_GADCON_ORIENT_CORNER_RB] = N_("Right-bottom Corner")
};

E_API int E_EVENT_SHELF_RENAME = -1;
E_API int E_EVENT_SHELF_ADD = -1;
E_API int E_EVENT_SHELF_DEL = -1;
static Eina_List *handlers;

static inline Eina_Bool
_e_shelf_is_horizontal(const E_Shelf *es)
{
   Eina_Bool horiz = EINA_FALSE;
   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        horiz = 1;
        break;

      case E_GADCON_ORIENT_VERT:
      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_CORNER_RT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_RB:
        horiz = 0;
        break;

      default:
        break;
     }
   return horiz;
}

static void
_e_shelf_remaximize(E_Shelf *es)
{
   E_Client *ec;

   if ((!e_config->border_fix_on_shelf_toggle) || es->cfg->overlap) return;
   E_CLIENT_FOREACH(ec)
     {
        E_Maximize max = ec->maximized;

        if (!ec->maximized) continue;
        if ((!ec->sticky) && (!e_shelf_desk_visible(es, ec->desk ?: e_desk_current_get(es->zone))))
          continue;
        e_client_unmaximize(ec, ec->maximized);
        e_client_maximize(ec, max);
     }
}

static void
_e_shelf_obstacles_update(E_Shelf *es)
{
   Eina_List *l;
   E_Zone_Obstacle *obs;

   EINA_LIST_FOREACH(es->zone_obstacles, l, obs)
     e_zone_obstacle_modify(obs,
       &(Eina_Rectangle){es->x + es->zone->x, es->y + es->zone->y, es->w, es->h},
       !_e_shelf_is_horizontal(es));
}

static Eina_Bool
_e_shelf_desk_count_handler(void *d EINA_UNUSED, int t EINA_UNUSED, E_Event_Zone_Desk_Count_Set *ev)
{
   Eina_List *l;
   E_Shelf *es;

   EINA_LIST_FOREACH(shelves, l, es)
     if (es->cfg->desk_show_mode && (ev->zone == es->zone))
       e_shelf_obstacles_update(es);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_shelf_autohide_timer_extend(E_Shelf *es)
{
   if (es->autohide_timer && es->hide_timer)
     {
        ecore_timer_loop_reset(es->autohide_timer);
        ecore_timer_loop_reset(es->hide_timer);
        return EINA_TRUE;
     }
   ecore_timer_del(es->autohide_timer);
   es->autohide_timer = NULL;
   return EINA_FALSE;
}

/* externally accessible functions */
EINTERN int
e_shelf_init(void)
{
   E_EVENT_SHELF_RENAME = ecore_event_type_new();
   E_EVENT_SHELF_ADD = ecore_event_type_new();
   E_EVENT_SHELF_DEL = ecore_event_type_new();
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_GADCON_POPULATE, _e_shelf_gadcon_populate_handler_cb, NULL);
   E_LIST_HANDLER_APPEND(handlers, E_EVENT_ZONE_DESK_COUNT_SET, _e_shelf_desk_count_handler, NULL);
   return 1;
}

EINTERN int
e_shelf_shutdown(void)
{
   if (x_fatal) return 1;
   while (shelves)
     {
        E_Shelf *es;

        es = eina_list_data_get(shelves);
        e_object_del(E_OBJECT(es));
     }
   E_FREE_LIST(handlers, ecore_event_handler_del);

   return 1;
}

E_API void
e_shelf_config_update(void)
{
   Eina_List *l;
   E_Config_Shelf *cf_es;
   int id = 0;

   while (shelves)
     {
        E_Shelf *es;

        es = eina_list_data_get(shelves);
        e_object_del(E_OBJECT(es));
     }

   EINA_LIST_FOREACH(e_config->shelves, l, cf_es)
     {
        E_Zone *zone;

        if (cf_es->id <= 0) cf_es->id = id + 1;
        zone = e_comp_zone_number_get(cf_es->zone);
        if (zone)
          e_shelf_config_new(zone, cf_es);
        id = cf_es->id;
     }
}

E_API Eina_List *
e_shelf_list_all(void)
{
   Eina_List *d = NULL, *s = NULL, *ret = NULL;

   if (shelves)
     s = eina_list_clone(shelves);
   if (dummies)
     d = eina_list_clone(dummies);
   if (s && d)
     ret = eina_list_merge(s, d);
   else
     ret = d ? : s;
   return ret;
}

E_API Eina_List *
e_shelf_list(void)
{
   shelves = eina_list_sort(shelves, -1, _e_shelf_cb_id_sort);
   return shelves;
}

E_API E_Shelf *
e_shelf_zone_dummy_new(E_Zone *zone, Evas_Object *obj, int id)
{
   E_Shelf *es;

   es = E_OBJECT_ALLOC(E_Shelf, E_SHELF_DUMMY_TYPE, _e_shelf_free);
   if (!es) return NULL;
   es->id = id;
   evas_object_geometry_get(obj, &es->x, &es->y, &es->w, &es->h);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
                                  (Evas_Object_Event_Cb)_e_shelf_cb_dummy_del, es);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE,
                                  (Evas_Object_Event_Cb)_e_shelf_cb_dummy_moveresize, es);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE,
                                  (Evas_Object_Event_Cb)_e_shelf_cb_dummy_moveresize, es);
   es->zone = zone;
   es->dummy = 1;
   es->o_base = obj;
   es->cfg = E_NEW(E_Config_Shelf, 1);

   e_object_del_attach_func_set(E_OBJECT(es), _e_shelf_del_cb);
   dummies = eina_list_append(dummies, es);

   return es;
}

static void
_e_shelf_hidden(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Shelf *es = data;
   es->hiding = 0;
}

static Eina_Bool
_shelf_content_fix(void *data)
{
   // I really don't know how it gets here but objects and their geometry are
   // wrong - something to do woith zoomap as well as geometry changing
   // at the same time so things going weird with child size and thus
   // position etc.
   E_Shelf *es = data;
   int w, h;

   es->fix_timer = NULL;
   w = es->w;
   h = es->h;
   e_shelf_resize(es, w + 1, h + 1);
   e_shelf_resize(es, w, h);
   return EINA_FALSE;
}

E_API E_Shelf *
e_shelf_zone_new(E_Zone *zone, const char *name, const char *style, E_Layer layer, int id)
{
   E_Shelf *es;
   char buf[1024];
   E_Comp_Object_Type type = E_COMP_OBJECT_TYPE_POPUP;

   es = E_OBJECT_ALLOC(E_Shelf, E_SHELF_TYPE, _e_shelf_free);
   if (!es) return NULL;
   es->id = id;
   es->x = 0;
   es->y = 0;
   es->w = 32;
   es->h = 32;
   es->zone = zone;
   e_object_del_attach_func_set(E_OBJECT(es), _e_shelf_del_cb);

   es->ee = e_comp->ee;
   es->evas = e_comp->evas;
   es->fit_along = 1;
   es->layer = layer;

   E_LIST_HANDLER_APPEND(es->handlers, E_EVENT_ZONE_EDGE_MOVE, _e_shelf_cb_mouse_in, es);
   es->o_base = edje_object_add(es->evas);
   evas_object_name_set(es->o_base, "shelf");
   evas_object_event_callback_add(es->o_base, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_shelf_cb_mouse_down, es);
   es->name = eina_stringshare_add(name);
   evas_object_resize(es->o_base, es->w, es->h);

   e_shelf_style_set(es, style);
   evas_object_move(es->o_base, es->zone->x + es->x, es->zone->y + es->y);
   if ((layer == E_LAYER_DESKTOP) || edje_object_data_get(es->o_base, "noshadow"))
     type = E_COMP_OBJECT_TYPE_NONE;
   es->comp_object = e_comp_object_util_add(es->o_base, type);
   evas_object_event_callback_add(es->comp_object, EVAS_CALLBACK_HIDE, _e_shelf_hidden, es);
   evas_object_data_set(es->comp_object, "E_Shelf", es);
   evas_object_data_set(es->comp_object, "comp_skip", (void*)1);
   evas_object_name_set(es->comp_object, es->name);
   evas_object_layer_set(es->comp_object, layer);
   evas_object_lower(es->comp_object);

   es->gadcon =
     e_gadcon_swallowed_new(es->name, es->id, es->o_base, "e.swallow.content");
   if (es->name)
     snprintf(buf, sizeof(buf), "%s", es->name);
   else
     snprintf(buf, sizeof(buf), _("Shelf #%d"), es->id);
   es->gadcon->location =
     e_gadcon_location_new(buf, E_GADCON_SITE_SHELF,
                           _e_shelf_gadcon_client_add, es,
                           _e_shelf_gadcon_client_remove, es);
   e_gadcon_location_register(es->gadcon->location);
// hmm dnd in ibar and ibox kill this. ok. need to look into this more
//   es->gadcon->instant_edit = 1;
   e_gadcon_min_size_request_callback_set(es->gadcon,
                                          _e_shelf_gadcon_min_size_request, es);

   e_gadcon_size_request_callback_set(es->gadcon,
                                      _e_shelf_gadcon_size_request, es);
   e_gadcon_frame_request_callback_set(es->gadcon,
                                       _e_shelf_gadcon_frame_request, es);
   e_gadcon_orient(es->gadcon, E_GADCON_ORIENT_TOP);
   snprintf(buf, sizeof(buf), "e,state,orientation,%s",
            e_shelf_orient_string_get(es));
   edje_object_signal_emit(es->o_base, buf, "e");
   edje_object_message_signal_process(es->o_base);
   e_gadcon_zone_set(es->gadcon, zone);
   e_gadcon_ecore_evas_set(es->gadcon, es->ee);
   e_gadcon_shelf_set(es->gadcon, es);
   e_gadcon_xdnd_window_set(es->gadcon, e_comp->ee_win);
   e_gadcon_dnd_window_set(es->gadcon, e_comp->ee_win);
   evas_object_clip_set(es->comp_object, es->zone->bg_clip_object);
   e_gadcon_util_menu_attach_func_set(es->gadcon,
                                      _e_shelf_cb_menu_items_append, es);

   e_gadcon_util_lock_func_set(es->gadcon,
                               _e_shelf_cb_locked_set, es);
   e_gadcon_util_urgent_show_func_set(es->gadcon,
                                      _e_shelf_cb_urgent_show, es);

   shelves = eina_list_append(shelves, es);

   es->hidden = 0;
   es->locked = 0;

   es->hide_origin = -1;

   {
      E_Event_Shelf *ev;

      ev = E_NEW(E_Event_Shelf, 1);
      ev->shelf = es;
      ecore_event_add(E_EVENT_SHELF_ADD, ev, NULL, NULL);
   }

   es->fix_timer = ecore_timer_add(0.1, _shelf_content_fix, es);
   return es;
}

E_API void
e_shelf_rename_dialog(E_Shelf *es)
{
   if (!es) return;
   if (es->rename_dialog) return;
   _e_shelf_cb_menu_rename(es, NULL, NULL);
}

E_API void
e_shelf_zone_move_resize_handle(E_Zone *zone)
{
   Eina_List *l;
   E_Shelf *es;
   Evas_Coord w, h;

   EINA_LIST_FOREACH(shelves, l, es)
     {
        if (es->zone == zone)
          {
             E_Gadcon *gc;

             gc = es->gadcon;
             if (gc->min_size_request.func)
               {
                  /* let gadcon container decrease to any size */
                  evas_object_size_hint_min_set(gc->o_container, 0, 0);
               }
             evas_object_smart_callback_call(gc->o_container, "min_size_request", NULL);
             e_shelf_position_calc(es);
             if (gc->min_size_request.func)
               {
                  evas_object_geometry_get(gc->o_container, NULL, NULL, &w, &h);
                  /* fix gadcon container min size to current geometry */
                  evas_object_size_hint_min_set(gc->o_container, w, h);
               }
          }
     }
}

E_API void
e_shelf_populate(E_Shelf *es)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   e_gadcon_populate(es->gadcon);
}

E_API void
e_shelf_show(E_Shelf *es)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   if (es->hiding) evas_object_hide(es->comp_object);
   evas_object_show(es->comp_object);
}

E_API void
e_shelf_hide(E_Shelf *es)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   evas_object_hide(es->comp_object);
   es->hiding = evas_object_visible_get(es->comp_object);
}

E_API void
e_shelf_locked_set(E_Shelf *es, int lock)
{
   if (lock)
     {
        e_shelf_toggle(es, 1);
        es->locked++;
     }
   else
     {
        if (es->locked > 0)
          es->locked--;
        if (!es->locked)
          e_shelf_toggle(es, es->toggle);
     }
}

E_API void
e_shelf_name_set(E_Shelf *es, const char *name)
{
   E_Event_Shelf *ev;

   if (!es) return;
   if (!name) return;
   if (es->name == name) return;
   eina_stringshare_replace(&es->name, name);
   eina_stringshare_replace(&es->cfg->name, name);
   ev = E_NEW(E_Event_Shelf, 1);
   ev->shelf = es;
   e_object_ref(E_OBJECT(es));
   ecore_event_add(E_EVENT_SHELF_RENAME, ev, (Ecore_End_Cb)_e_shelf_event_rename_end_cb, NULL);
   if (es->dummy) return;
   e_gadcon_name_set(es->gadcon, name);
}

E_API void
e_shelf_toggle(E_Shelf *es, int show)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);

   es->toggle = show;
   if (!es->hidden && _e_shelf_autohide_timer_extend(es)) return;
   if (es->locked) return;
   es->interrupted = -1;
   es->urgent_show = 0;
   if ((show) && (es->hidden))
     {
        es->hidden = 0;
        edje_object_signal_emit(es->o_base, "e,state,visible", "e");
        if (es->instant_delay >= 0.0)
          {
             _e_shelf_cb_instant_hide_timer(es);
             es->hide_timer =
               ecore_timer_loop_add(es->cfg->hide_timeout,
                               _e_shelf_cb_hide_urgent_timer, es);
          }
        else
          {
             if (es->hide_timer)
               {
                  ecore_timer_del(es->hide_timer);
                  es->hide_timer = NULL;
                  return; //we should not add a animator here, the shelf cannot have moved yet.
               }

             if (!es->hide_animator)
               {
                  es->hide_begin = ecore_loop_time_get();
                  es->hide_animator =
                    ecore_animator_add(_e_shelf_cb_hide_animator, es);
               }
             else
               {
                  double time_elapsed = ecore_loop_time_get() - es->hide_begin;
                  es->hide_begin = ecore_loop_time_get() - (es->cfg->hide_duration - time_elapsed);
               }

          }
     }
   else if ((!show) && (!es->hidden) && ((!es->gadcon) || (!es->gadcon->editing)) &&
            (es->cfg->autohide))
     {
        if (es->instant_delay >= 0.0)
          {
             if (es->hide_timer)
               {
                  ecore_timer_del(es->hide_timer);
                  es->hide_timer = NULL;
               }
             es->hidden = 1;
             if (!es->instant_timer)
               es->instant_timer =
                 ecore_timer_loop_add(es->instant_delay,
                                 _e_shelf_cb_instant_hide_timer, es);
          }
        else
          {
             if (es->hide_animator)
               {
                  es->interrupted = show;
                  return;
               }
             es->hidden = 1;
             if (es->hide_timer) ecore_timer_del(es->hide_timer);
             es->hide_timer =
               ecore_timer_loop_add(es->cfg->hide_timeout,
                               _e_shelf_cb_hide_animator_timer, es);
          }
     }
}

E_API void
e_shelf_urgent_show(E_Shelf *es)
{
   e_shelf_toggle(es, 1);
   es->urgent_show = 1;
}

E_API void
e_shelf_move(E_Shelf *es, int x, int y)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   if ((es->x == x) && (es->y == y)) return;
   es->x = x;
   es->y = y;
   evas_object_move(es->comp_object, es->zone->x + es->x, es->zone->y + es->y);
   _e_shelf_obstacles_update(es);
   if (!es->hide_animator)
     _e_shelf_remaximize(es);
}

E_API void
e_shelf_resize(E_Shelf *es, int w, int h)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   if ((es->w == w) && (es->h == h)) return;
   es->w = w;
   es->h = h;
   evas_object_resize(es->comp_object, es->w, es->h);
   _e_shelf_obstacles_update(es);
   if (!es->hide_animator)
     _e_shelf_remaximize(es);
}

E_API void
e_shelf_move_resize(E_Shelf *es, int x, int y, int w, int h)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   if ((es->x == x) && (es->y == y) && (es->w == w) && (es->h == h)) return;
   es->x = x;
   es->y = y;
   es->w = w;
   es->h = h;
   evas_object_geometry_set(es->comp_object,
                            es->zone->x + es->x, es->zone->y + es->y,
                            es->w, es->h);
   _e_shelf_obstacles_update(es);
   if (!es->hide_animator)
     _e_shelf_remaximize(es);
}

E_API void
e_shelf_save(E_Shelf *es)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   if (es->cfg)
     {
        es->cfg->orient = es->gadcon->orient;
        if (es->cfg->style) eina_stringshare_del(es->cfg->style);
        es->cfg->style = eina_stringshare_add(es->style);
     }
   else
     {
        E_Config_Shelf *cf_es;

        cf_es = E_NEW(E_Config_Shelf, 1);
        cf_es->name = eina_stringshare_add(es->name);
        cf_es->zone = es->zone->num;
        cf_es->layer = es->layer;
        e_config->shelves = eina_list_append(e_config->shelves, cf_es);
        cf_es->orient = es->gadcon->orient;
        cf_es->style = eina_stringshare_add(es->style);
        cf_es->fit_along = es->fit_along;
        cf_es->fit_size = es->fit_size;
        cf_es->overlap = 0;
        cf_es->autohide = 0;
        cf_es->hide_timeout = 1.0;
        cf_es->hide_duration = 1.0;
        es->cfg = cf_es;
     }
   e_config_save_queue();
}

E_API void
e_shelf_unsave(E_Shelf *es)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
   es->cfg_delete = 1;
}

E_API void
e_shelf_orient(E_Shelf *es, E_Gadcon_Orient orient)
{
   char buf[4096];

   E_OBJECT_CHECK(es);
   E_OBJECT_IF_NOT_TYPE(es, E_SHELF_DUMMY_TYPE)
     {
        E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);
     }

   if (!es->dummy)
     {
        e_gadcon_orient(es->gadcon, orient);
        snprintf(buf, sizeof(buf), "e,state,orientation,%s",
                 e_shelf_orient_string_get(es));
        edje_object_signal_emit(es->o_base, buf, "e");
        edje_object_message_signal_process(es->o_base);
        e_gadcon_location_set_icon_name(es->gadcon->location,
                                        _e_shelf_orient_icon_name_get(es));
     }
   e_zone_useful_geometry_dirty(es->zone);
}

E_API const char *
e_shelf_orient_string_get(E_Shelf *es)
{
   const char *sig = "";

   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
        sig = "float";
        break;

      case E_GADCON_ORIENT_HORIZ:
        sig = "horizontal";
        break;

      case E_GADCON_ORIENT_VERT:
        sig = "vertical";
        break;

      case E_GADCON_ORIENT_LEFT:
        sig = "left";
        break;

      case E_GADCON_ORIENT_RIGHT:
        sig = "right";
        break;

      case E_GADCON_ORIENT_TOP:
        sig = "top";
        break;

      case E_GADCON_ORIENT_BOTTOM:
        sig = "bottom";
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        sig = "top_left";
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        sig = "top_right";
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        sig = "bottom_left";
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        sig = "bottom_right";
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        sig = "left_top";
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        sig = "right_top";
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        sig = "left_bottom";
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        sig = "right_bottom";
        break;

      default:
        break;
     }
   return sig;
}

E_API void
e_shelf_position_calc(E_Shelf *es)
{
   E_Gadcon_Orient orient = E_GADCON_ORIENT_FLOAT;
   int size = (40 * e_scale);
   int x, y, w, h;

   x = es->x, y = es->y, w = es->w, h = es->h;
   if (es->cfg)
     {
        orient = es->cfg->orient;
        size = es->cfg->size * e_scale;
     }
   else
     orient = es->gadcon->orient;
   switch (orient)
     {
      case E_GADCON_ORIENT_FLOAT:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        break;

      case E_GADCON_ORIENT_HORIZ:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = (es->zone->w - w) / 2;
        break;

      case E_GADCON_ORIENT_VERT:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        y = (es->zone->h - h) / 2;
        break;

      case E_GADCON_ORIENT_LEFT:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = 0;
        y = (es->zone->h - h) / 2;
        break;

      case E_GADCON_ORIENT_RIGHT:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = es->zone->w - w;
        y = (es->zone->h - h) / 2;
        break;

      case E_GADCON_ORIENT_TOP:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = (es->zone->w - w) / 2;
        y = 0;
        break;

      case E_GADCON_ORIENT_BOTTOM:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = (es->zone->w - w) / 2;
        y = es->zone->h - h;
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = 0;
        y = 0;
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = es->zone->w - w;
        y = 0;
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = 0;
        y = es->zone->h - h;
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        if (!es->fit_along) w = es->zone->w;
        if (!es->fit_size) h = size;
        x = es->zone->w - w;
        y = es->zone->h - h;
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = 0;
        y = 0;
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = es->zone->w - w;
        y = 0;
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = 0;
        y = es->zone->h - h;
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        if (!es->fit_along) h = es->zone->h;
        if (!es->fit_size) w = size;
        x = es->zone->w - w;
        y = es->zone->h - h;
        break;

      default:
        break;
     }
   es->hide_origin = -1;

   if ((es->x == x) && (es->y == y) && (es->w == w) && (es->h == h)) return;

   e_shelf_move_resize(es, x, y, w, h);
   if (es->hidden)
     {
        es->hidden = 0;
        e_shelf_toggle(es, 0);
     }
   e_zone_useful_geometry_dirty(es->zone);
   _e_shelf_bindings_add(es);

   do
     {
        Eina_Bool err = EINA_FALSE;

        if (!es->cfg) break;
        if (!es->zone) break;
        if (!es->cfg->autohide) break;
        switch (es->cfg->orient)
          {
           case E_GADCON_ORIENT_LEFT:
           case E_GADCON_ORIENT_CORNER_LT:
           case E_GADCON_ORIENT_CORNER_LB:
             if (!e_zone_exists_direction(es->zone, E_ZONE_EDGE_LEFT)) break;
             err = EINA_TRUE;
             break;

           case E_GADCON_ORIENT_RIGHT:
           case E_GADCON_ORIENT_CORNER_RT:
           case E_GADCON_ORIENT_CORNER_RB:
             if (!e_zone_exists_direction(es->zone, E_ZONE_EDGE_RIGHT)) break;
             err = EINA_TRUE;
             break;

           case E_GADCON_ORIENT_TOP:
           case E_GADCON_ORIENT_CORNER_TL:
           case E_GADCON_ORIENT_CORNER_TR:
             if (!e_zone_exists_direction(es->zone, E_ZONE_EDGE_TOP)) break;
             err = EINA_TRUE;
             break;

           case E_GADCON_ORIENT_BOTTOM:
           case E_GADCON_ORIENT_CORNER_BL:
           case E_GADCON_ORIENT_CORNER_BR:
             if (!e_zone_exists_direction(es->zone, E_ZONE_EDGE_BOTTOM)) break;
             err = EINA_TRUE;
             break;
          }
        if (err)
          e_util_dialog_show(_("Shelf Autohide Error"), _("Shelf autohiding will not work properly<ps/>"
                                                          "with the current configuration; set your shelf to<ps/>"
                                                          "\"Below Everything\" or disable autohiding."));
        break;
     } while (0);
}

E_API Eina_Bool
e_shelf_desk_visible(const E_Shelf *es, const E_Desk *desk)
{
   Eina_List *ll;
   E_Config_Shelf *cf_es;
   E_Zone *zone;
   E_Config_Shelf_Desk *sd;

   EINA_SAFETY_ON_NULL_RETURN_VAL(es, EINA_FALSE);
   if (!desk)
     {
        EINA_LIST_FOREACH(e_comp->zones, ll, zone)
          {
             desk = e_desk_current_get(zone);
             if (e_shelf_desk_visible(es, desk)) return EINA_TRUE;
          }
        return EINA_FALSE;
     }
   cf_es = es->cfg;
   if (!cf_es) return EINA_FALSE;

   zone = desk->zone;
   if (cf_es->zone != (int)zone->num) return EINA_FALSE;
   if (!es->cfg->desk_show_mode) return EINA_TRUE;

   EINA_LIST_FOREACH(es->cfg->desk_list, ll, sd)
     {
        if (!sd) continue;
        if ((desk->x == sd->x) && (desk->y == sd->y))
          return EINA_TRUE;
     }
   return EINA_FALSE;
}

E_API void
e_shelf_style_set(E_Shelf *es, const char *style)
{
   const char *option;
   char buf[1024];

   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);

   if (!es->o_base) return;
   if (style != es->style)
     eina_stringshare_replace(&es->style, style);

   if (style)
     snprintf(buf, sizeof(buf), "e/shelf/%s/base", style);
   else
     snprintf(buf, sizeof(buf), "e/shelf/%s/base", "default");

   if (!e_theme_edje_object_set(es->o_base, "base/theme/shelf", buf))
     e_theme_edje_object_set(es->o_base, "base/theme/shelf",
                             "e/shelf/default/base");

   option = edje_object_data_get(es->o_base, "hidden_state_size");
   if (option)
     es->hidden_state_size = atoi(option);
   else
     es->hidden_state_size = 4;
   option = edje_object_data_get(es->o_base, "instant_delay");
   if (option)
     es->instant_delay = atof(option);
   else
     es->instant_delay = -1.0;
}

E_API void
e_shelf_autohide_set(E_Shelf *es, int autohide_type)
{
   E_OBJECT_CHECK(es);
   E_OBJECT_TYPE_CHECK(es, E_SHELF_TYPE);

   if ((es->cfg->autohide == !!autohide_type) && ((!!es->autohide) == !!autohide_type))
     {
        if ((!es->autohide) || (es->cfg->autohide_show_action == autohide_type - 1))
          return;
     }
   es->cfg->autohide = !!autohide_type;
   if (!es->cfg->autohide)
     {
        if (!es->autohide) return;
        ecore_event_handler_del(es->autohide);
        es->autohide = NULL;
        return;
     }
   es->cfg->autohide_show_action = autohide_type - 1;
   if (es->autohide) ecore_event_handler_del(es->autohide);
   es->autohide = NULL;
/*
 * see FIXME in _e_shelf_cb_mouse_in() for why these are commented out
   es->handlers =
     eina_list_append(es->handlers,
                      ecore_event_handler_add(ECORE_X_EVENT_MOUSE_IN,
                                              _e_shelf_cb_mouse_in, es));
 */
   if (!es->cfg->autohide_show_action)
     es->autohide = ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE,
                                            _e_shelf_cb_mouse_in, es);
/*
   es->handlers =
     eina_list_append(es->handlers,
                      ecore_event_handler_add(ECORE_X_EVENT_MOUSE_OUT,
                                              _e_shelf_cb_mouse_out, es));
   if (!popup)
     evas_object_event_callback_add(es->o_event, EVAS_CALLBACK_MOUSE_OUT,
                                    (Evas_Object_Event_Cb)_e_shelf_cb_mouse_out2, es);
 */
}

E_API void
e_shelf_obstacles_update(E_Shelf *es)
{
   E_FREE_LIST(es->zone_obstacles, e_object_del);
   if ((es->cfg->overlap && e_config->border_fix_on_shelf_toggle) || es->cfg->autohide) return;
   if (es->cfg->desk_show_mode)
     {
        Eina_List *l;
        E_Config_Shelf_Desk *sd;

        EINA_LIST_FOREACH(es->cfg->desk_list, l, sd)
          {
             E_Desk *desk;

             desk = e_desk_at_xy_get(es->zone, sd->x, sd->y);
             if (!desk) continue;
             es->zone_obstacles = eina_list_append(es->zone_obstacles,
               e_zone_obstacle_add(es->zone, desk,
                 &(Eina_Rectangle){es->x + es->zone->x, es->y + es->zone->y, es->w, es->h},
                 !_e_shelf_is_horizontal(es)));
          }
     }
   else
     es->zone_obstacles = eina_list_append(es->zone_obstacles,
       e_zone_obstacle_add(es->zone, NULL,
         &(Eina_Rectangle){es->x + es->zone->x, es->y + es->zone->y, es->w, es->h},
         !_e_shelf_is_horizontal(es)));
}

E_API E_Shelf *
e_shelf_config_new(E_Zone *zone, E_Config_Shelf *cf_es)
{
   E_Shelf *es;

   es = e_shelf_zone_new(zone, cf_es->name, cf_es->style, cf_es->layer, cf_es->id);
   if (!es) return NULL;

   if (!EINA_DBL_NONZERO(cf_es->hide_timeout)) cf_es->hide_timeout = 1.0;
   if (!EINA_DBL_NONZERO(cf_es->hide_duration)) cf_es->hide_duration = 1.0;
   es->cfg = cf_es;
   es->fit_along = cf_es->fit_along;
   es->fit_size = cf_es->fit_size;

   e_shelf_autohide_set(es, cf_es->autohide + (cf_es->autohide * cf_es->autohide_show_action));
   e_shelf_orient(es, cf_es->orient);
   e_shelf_populate(es);
   e_shelf_position_calc(es);

   if (cf_es->desk_show_mode)
     {
        if (e_shelf_desk_visible(es, NULL))
          e_shelf_show(es);
        else
          e_shelf_hide(es);
     }
   else
     e_shelf_show(es);

   e_shelf_toggle(es, 0);
   e_shelf_obstacles_update(es);
   return es;
}

E_API E_Entry_Dialog *
e_shelf_new_dialog(Evas_Object *parent, E_Zone *zone)
{
   char buf[256];

   snprintf(buf, sizeof(buf), _("Shelf #%d"),
            eina_list_count(e_config->shelves));
   return e_entry_dialog_show(parent, _("Add New Shelf"),
                              "preferences-desktop-shelf",
                              _("Name:"), buf, NULL, NULL,
                              _e_shelf_new_dialog_ok, NULL, zone);
}

/* local subsystem functions */

static void
_e_shelf_new_dialog_ok(void *data, char *text)
{
   E_Config_Shelf *cfg, *es_cf;
   E_Zone *zone = data;
   Eina_List *l;
   unsigned int x;
   unsigned long orient = 1;
   E_Shelf *es;

   if ((!text) || (!text[0])) return;
   EINA_LIST_FOREACH(e_config->shelves, l, es_cf)
     {
        if (strcmp(es_cf->name, text)) continue;
        e_util_dialog_internal(_("Shelf Error"), _("A shelf with that name already exists!"));
        return;
     }

   cfg = E_NEW(E_Config_Shelf, 1);
   cfg->name = eina_stringshare_add(text);
   cfg->zone = zone->num;
   cfg->layer = E_LAYER_CLIENT_ABOVE;
   EINA_LIST_FOREACH(e_config->shelves, l, es_cf)
     orient *= orientations[es_cf->orient];
   for (x = 3; x < (sizeof(orientations) / sizeof(orientations[0])); x++)
     if (orient % orientations[x])
       {
          cfg->orient = x;
          break;
       }
   cfg->fit_along = 1;
   cfg->fit_size = 0;
   cfg->style = eina_stringshare_add("default");
   cfg->size = 40;
   cfg->overlap = 0;
   cfg->autohide = 0;
   e_config->shelves = eina_list_append(e_config->shelves, cfg);
   e_config_save_queue();

   es_cf = eina_list_last_data_get(e_config->shelves);
   cfg->id = es_cf->id + 1;
   es = e_shelf_config_new(zone, cfg);
   if (es && es->gadcon)
     e_int_gadcon_config_shelf(es->gadcon);
}

static void
_e_shelf_del_cb(void *d)
{
   E_Shelf *es;

   es = d;
   if (es->dummy)
     {
        dummies = eina_list_remove(dummies, es);
        E_FREE(es->cfg);
     }
   else
     shelves = eina_list_remove(shelves, es);
}

static void
_e_shelf_event_rename_end_cb(void *data EINA_UNUSED, E_Event_Shelf *ev)
{
   e_object_unref(E_OBJECT(ev->shelf));
   free(ev);
}

static void
_e_shelf_free_cb(void *data EINA_UNUSED, void *event)
{
   E_Event_Shelf *ev = event;
   E_Shelf *es = ev->shelf;

   eina_stringshare_del(es->name);
   eina_stringshare_del(es->style);

   if (es->cfg_delete)
     {
        if (es->cfg)
          {
             e_config->shelves = eina_list_remove(e_config->shelves, es->cfg);
             eina_stringshare_del(es->cfg->name);
             eina_stringshare_del(es->cfg->style);
             free(es->cfg);
          }
        e_config_save_queue();
     }
   free(es);
   free(ev);
}

static void
_e_shelf_free(E_Shelf *es)
{
   E_Event_Shelf *ev;

   if (!es->dummy)
     _e_shelf_bindings_del(es);

   evas_object_event_callback_del(es->comp_object, EVAS_CALLBACK_HIDE, _e_shelf_hidden);

   E_FREE_LIST(es->handlers, ecore_event_handler_del);
   E_FREE_LIST(es->zone_obstacles, e_object_del);

   E_FREE_FUNC(es->autohide, ecore_event_handler_del);
   E_FREE_FUNC(es->hide_animator, ecore_animator_del);
   E_FREE_FUNC(es->autohide_timer, ecore_timer_del);
   E_FREE_FUNC(es->hide_timer, ecore_timer_del);
   E_FREE_FUNC(es->instant_timer, ecore_timer_del);
   E_FREE_FUNC(es->module_init_end_timer, ecore_timer_del);
   E_FREE_FUNC(es->fix_timer, ecore_timer_del);
   if (es->menu)
     {
        e_menu_post_deactivate_callback_set(es->menu, NULL, NULL);
        e_object_del(E_OBJECT(es->menu));
        es->menu = NULL;
     }
   if (es->dummy)
     {
        evas_object_event_callback_del_full(es->o_base, EVAS_CALLBACK_DEL,
                                            (Evas_Object_Event_Cb)_e_shelf_cb_dummy_del, es);
        evas_object_event_callback_del_full(es->o_base, EVAS_CALLBACK_MOVE,
                                            (Evas_Object_Event_Cb)_e_shelf_cb_dummy_moveresize, es);
        evas_object_event_callback_del_full(es->o_base, EVAS_CALLBACK_RESIZE,
                                            (Evas_Object_Event_Cb)_e_shelf_cb_dummy_moveresize, es);
     }
   else
     {
        e_gadcon_location_unregister(es->gadcon->location);
        e_gadcon_location_free(es->gadcon->location);
        if (es->cfg_delete) e_gadcon_config_del(es->gadcon);
        e_object_del(E_OBJECT(es->gadcon));
        es->gadcon = NULL;
     }
   if (es->config_dialog) e_object_del(E_OBJECT(es->config_dialog));
   es->config_dialog = NULL;
   if (stopping)
     evas_object_unref(es->comp_object);
   else
     evas_object_hide(es->comp_object);
   E_FREE_FUNC(es->comp_object, evas_object_del);
   E_FREE_FUNC(es->o_base, evas_object_del);

   ev = E_NEW(E_Event_Shelf, 1);
   ev->shelf = es;
   ecore_event_add(E_EVENT_SHELF_DEL, ev, _e_shelf_free_cb, NULL);
}

static void
_e_shelf_gadcon_min_size_request(void *data EINA_UNUSED, E_Gadcon *gc EINA_UNUSED, Evas_Coord w EINA_UNUSED, Evas_Coord h EINA_UNUSED)
{
   return;
}

static void
_e_shelf_gadcon_size_request(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h)
{
   E_Shelf *es;
   Evas_Coord nx, ny, nw, nh, ww, hh, wantw, wanth;

   es = data;
   nx = es->x;
   ny = es->y;
   nw = es->w;
   nh = es->h;
   ww = hh = 0;
   w = MAX(w, 16);
   h = MAX(h, 16);
   evas_object_geometry_get(gc->o_container, NULL, NULL, &ww, &hh);
   switch (gc->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        if (!es->fit_along) w = ww;
        if (!es->fit_size) h = hh;
        break;

      case E_GADCON_ORIENT_VERT:
      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_CORNER_RT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_RB:
        if (!es->fit_along) h = hh;
        if (!es->fit_size) w = ww;
        break;

      default:
        break;
     }
   e_gadcon_swallowed_min_size_set(gc, w, h);
   edje_object_size_min_calc(es->o_base, &nw, &nh);
   wantw = nw;
   wanth = nh;
   switch (gc->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = es->x + ((es->w - nw) / 2);
        /*if (nh != es->h)*/ ny = es->y + ((es->h - nh) / 2);
        break;

      case E_GADCON_ORIENT_HORIZ:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = es->x + ((es->w - nw) / 2);
        break;

      case E_GADCON_ORIENT_VERT:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = es->y + ((es->h - nh) / 2);
        break;

      case E_GADCON_ORIENT_LEFT:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = (es->zone->h - nh) / 2;
        //nx = 0;
        break;

      case E_GADCON_ORIENT_RIGHT:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = (es->zone->h - nh) / 2;
        //nx = es->zone->w - nw;
        break;

      case E_GADCON_ORIENT_TOP:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = (es->zone->w - nw) / 2;
        //ny = 0;
        break;

      case E_GADCON_ORIENT_BOTTOM:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = (es->zone->w - nw) / 2;
        //ny = es->zone->h - nh;
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = 0;
        //ny = 0;
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = es->zone->w - nw;
        //ny = 0;
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = 0;
        //ny = es->zone->h - nh;
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        if (!es->fit_along) nw = es->w;
        if (!es->fit_size) nh = es->h;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nw != es->w)*/ nx = es->zone->w - nw;
        //ny = es->zone->h - nh;
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = 0;
        //nx = 0;
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = 0;
        //nx = es->zone->w - nw;
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = es->zone->h - nh;
        //nx = 0;
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        if (!es->fit_along) nh = es->h;
        if (!es->fit_size) nw = es->w;
        if (nw > es->zone->w) nw = es->zone->w;
        if (nh > es->zone->h) nh = es->zone->h;
        /*if (nh != es->h)*/ ny = es->zone->h - nh;
        //nx = es->zone->w - nw;
        break;

      default:
        break;
     }
   w -= (wantw - nw);
   h -= (wanth - nh);
   e_gadcon_swallowed_min_size_set(gc, w, h);
   e_shelf_move_resize(es, nx, ny, nw, nh);
   e_zone_useful_geometry_dirty(es->zone);
}

static Evas_Object *
_e_shelf_gadcon_frame_request(void *data, E_Gadcon_Client *gcc, const char *style)
{
   E_Shelf *es;
   Evas_Object *o;
   char buf[4096];

   es = data;
   o = edje_object_add(gcc->gadcon->evas);

   snprintf(buf, sizeof(buf), "e/shelf/%s/%s", es->style, style);
   if (!e_theme_edje_object_set(o, "base/theme/shelf", buf))
     {
        /* if an inset style (e.g. plain) isn't implemented for a given
         * shelf style, fall back to the default one. no need for every
         * theme to implement the plain style */
        snprintf(buf, sizeof(buf), "e/shelf/default/%s", style);
        if (!e_theme_edje_object_set(o, "base/theme/shelf", buf))
          {
             evas_object_del(o);
             return NULL;
          }
     }
   snprintf(buf, sizeof(buf), "e,state,orientation,%s",
            e_shelf_orient_string_get(es));
   edje_object_signal_emit(o, buf, "e");
   edje_object_message_signal_process(o);
   return o;
}

static void
_e_shelf_toggle_client_fix(E_Shelf *es)
{
   E_Client *ec;

   if (!e_config->border_fix_on_shelf_toggle)
     return;
   if (es->cfg->overlap)
     return;

   E_CLIENT_FOREACH(ec)
     {
        if ((!ec->sticky) && (!e_shelf_desk_visible(es, ec->desk ?: e_desk_current_get(es->zone))))
          continue;
        if ((ec->maximized & E_MAXIMIZE_TYPE) == E_MAXIMIZE_NONE)
          {
             if (ec->lock_client_location) continue;
             if (es->hidden)
               {
                  if (!ec->shelf_fix.modified) continue;
                  if (!--ec->shelf_fix.modified)
                    {
                       evas_object_move(ec->frame, ec->shelf_fix.x, ec->shelf_fix.y);
                       continue;
                    }
               }

             if (!E_INTERSECTS(ec->x, ec->y, ec->w, ec->h,
                               es->x, es->y, es->w, es->h))
               continue;

             if (!es->hidden)
               {
                  if (!ec->shelf_fix.modified++)
                    ec->shelf_fix.x = ec->x;
                  ec->shelf_fix.y = ec->y;
               }

             switch (es->gadcon->orient)
               {
                case E_GADCON_ORIENT_TOP:
                case E_GADCON_ORIENT_CORNER_TL:
                case E_GADCON_ORIENT_CORNER_TR:
                  if (!es->hidden)
                    evas_object_move(ec->frame, ec->x, ec->y + es->h);
                  break;

                case E_GADCON_ORIENT_BOTTOM:
                case E_GADCON_ORIENT_CORNER_BL:
                case E_GADCON_ORIENT_CORNER_BR:
                  if (!es->hidden)
                    evas_object_move(ec->frame, ec->x, ec->y - es->h);
                  break;

                case E_GADCON_ORIENT_LEFT:
                case E_GADCON_ORIENT_CORNER_LB:
                case E_GADCON_ORIENT_CORNER_LT:
                  if (!es->hidden)
                    evas_object_move(ec->frame, ec->x + es->w, ec->y);
                  break;

                case E_GADCON_ORIENT_RIGHT:
                case E_GADCON_ORIENT_CORNER_RB:
                case E_GADCON_ORIENT_CORNER_RT:
                  if (!es->hidden)
                    evas_object_move(ec->frame, ec->x - es->w, ec->y);
                  break;

                default:
                  break;
               }
          }
        else
          {
             E_Maximize max;

             max = ec->maximized;
             e_client_unmaximize(ec, E_MAXIMIZE_BOTH);
             e_client_maximize(ec, max);
          }
     }
}

static void
_e_shelf_menu_item_free(void *data)
{
   E_Shelf *es;

   es = e_object_data_get(data);
   e_shelf_locked_set(es, 0);
   e_object_unref(E_OBJECT(es));
}

static void
_e_shelf_menu_append(E_Shelf *es, E_Menu *mn)
{
   E_Menu_Item *mi;
   E_Menu *subm;
   char buf[256];

   if (es->name)
     snprintf(buf, sizeof(buf), "%s", es->name);
   else
     snprintf(buf, sizeof(buf), _("Shelf %s"),
              e_shelf_orient_string_get(es));

   e_shelf_locked_set(es, 1);

   subm = e_menu_new();
   mi = e_menu_item_new(mn);
   e_menu_item_label_set(mi, buf);
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-shelf");
   e_menu_pre_activate_callback_set(subm, _e_shelf_menu_pre_cb, es);
   e_object_ref(E_OBJECT(es));
   e_object_free_attach_func_set(E_OBJECT(mi), _e_shelf_menu_item_free);
   e_object_data_set(E_OBJECT(mi), es);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   mi = e_menu_item_new(mn);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(mn);
   if (es->gadcon->editing)
     e_menu_item_label_set(mi, _("Stop Moving Gadgets"));
   else
     e_menu_item_label_set(mi, _("Begin Moving Gadgets"));
   e_util_menu_item_theme_icon_set(mi, "transform-move");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_edit, es);
}

static void
_e_shelf_cb_menu_items_append(void *data, E_Gadcon_Client *gcc EINA_UNUSED, E_Menu *mn)
{
   E_Shelf *es;

   es = data;
   _e_shelf_menu_append(es, mn);
}

static void
_e_shelf_cb_locked_set(void *data, int lock)
{
   E_Shelf *es;

   es = data;
   e_shelf_locked_set(es, lock);
}

static void
_e_shelf_cb_urgent_show(void *data)
{
   E_Shelf *es;

   es = data;
   e_shelf_urgent_show(es);
}

static void
_e_shelf_cb_menu_autohide(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es = data;

   e_shelf_autohide_set(es, (!es->cfg->autohide) + (!es->cfg->autohide * es->cfg->autohide_show_action));
   if ((es->cfg->autohide) && (!es->hidden))
     e_shelf_toggle(es, 0);
   else if ((!es->cfg->autohide) && (es->hidden))
     e_shelf_toggle(es, 1);
   e_zone_useful_geometry_dirty(es->zone);
   e_config_save_queue();
}

static void
_e_shelf_cb_menu_refresh(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es = data;

   e_gadcon_unpopulate(es->gadcon);
   e_gadcon_populate(es->gadcon);
}

static void
_e_shelf_cb_menu_config(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es;

   es = data;
   if (!es->config_dialog) e_int_shelf_config(es);
}

static void
_e_shelf_cb_menu_edit(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es;

   es = data;
   if (es->gadcon->editing)
     {
        e_gadcon_edit_end(es->gadcon);
        e_shelf_toggle(es, 0);
     }
   else
     {
        e_shelf_toggle(es, 1);
        e_gadcon_edit_begin(es->gadcon);
     }
}

static void
_e_shelf_cb_menu_contents(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es;

   es = data;
   if (!es->gadcon->config_dialog) e_int_gadcon_config_shelf(es->gadcon);
}

static void
_e_shelf_cb_confirm_dialog_destroy(void *data)
{
   E_Shelf *es;

   es = data;
   e_object_unref(E_OBJECT(es));
}

static void
_e_shelf_cb_confirm_dialog_yes(void *data)
{
   E_Shelf *es;

   es = data;
   if (e_object_is_del(E_OBJECT(es))) return;
   es->cfg_delete = 1;
   e_object_del(E_OBJECT(es));
}

static void
_e_shelf_cb_menu_delete(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es;

   es = data;
   if (e_config->cnfmdlg_disabled)
     {
        if (e_object_is_del(E_OBJECT(es))) return;
        es->cfg_delete = 1;
        e_object_del(E_OBJECT(es));

        e_config_save_queue();
        return;
     }

   e_object_ref(E_OBJECT(es));
   e_confirm_dialog_show(_("Are you sure you want to delete this shelf?"),
                         "enlightenment",
                         _("You requested to delete this shelf.<ps/>"
                           "<ps/>"
                           "Are you sure you want to delete it?"),
                         _("Delete"), _("Keep"),
                         _e_shelf_cb_confirm_dialog_yes, NULL, data, NULL,
                         _e_shelf_cb_confirm_dialog_destroy, data);
}

static void
_e_shelf_cb_menu_post(void *data, E_Menu *m)
{
   E_Shelf *es;

   es = data;
   e_object_del(E_OBJECT(m));
   if (m != es->menu) return;
   es->menu = NULL;
}

static void
_e_shelf_cb_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Shelf *es;
   E_Menu *mn;
   int cx, cy;

   es = data;
   ev = event_info;
   switch (ev->button)
     {
      case 1:
        if (es->cfg->autohide_show_action) e_shelf_toggle(es, 1);
        break;

      case 3:
        if (es->menu) return;
        mn = e_menu_new();
        e_menu_post_deactivate_callback_set(mn, _e_shelf_cb_menu_post, es);
        es->menu = mn;

        _e_shelf_menu_pre_cb(es, mn);

        e_gadcon_canvas_zone_geometry_get(es->gadcon, &cx, &cy, NULL, NULL);
        e_menu_activate_mouse(mn,
                              e_zone_current_get(),
                              cx + ev->output.x,
                              cy + ev->output.y, 1, 1,
                              E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
        break;
     }
}

static Eina_Bool
_e_shelf_cb_mouse_in(void *data, int type, void *event)
{
   E_Shelf *es;

   es = data;
   if (es->cfg->autohide_show_action) return ECORE_CALLBACK_PASS_ON;

   if (type == E_EVENT_ZONE_EDGE_MOVE)
     {
        E_Event_Zone_Edge *ev;
        E_Desk *desk;
        Eina_List *l;
        E_Client *ec;
        int show = 0;

        ev = event;
        if (es->zone != ev->zone) return ECORE_CALLBACK_PASS_ON;
        if (!_e_shelf_on_current_desk(es, ev)) return ECORE_CALLBACK_PASS_ON;
        desk = e_desk_current_get(ev->zone);
        EINA_LIST_FOREACH(desk->fullscreen_clients, l, ec)
          if (evas_object_visible_get(ec->frame)) return ECORE_CALLBACK_RENEW;

        ev->x -= es->zone->x, ev->y -= es->zone->y;
        switch (es->gadcon->orient)
          {
           case E_GADCON_ORIENT_LEFT:
           case E_GADCON_ORIENT_CORNER_LT:
           case E_GADCON_ORIENT_CORNER_LB:
             if (((ev->edge == E_ZONE_EDGE_LEFT) ||
                  (ev->edge == E_ZONE_EDGE_TOP_LEFT) ||
                  (ev->edge == E_ZONE_EDGE_BOTTOM_LEFT)) &&
                 (ev->y >= es->y) && (ev->y <= (es->y + es->h)))
               show = 1;
             break;

           case E_GADCON_ORIENT_RIGHT:
           case E_GADCON_ORIENT_CORNER_RT:
           case E_GADCON_ORIENT_CORNER_RB:
             if (((ev->edge == E_ZONE_EDGE_RIGHT) ||
                  (ev->edge == E_ZONE_EDGE_TOP_RIGHT) ||
                  (ev->edge == E_ZONE_EDGE_BOTTOM_RIGHT)) &&
                 (ev->y >= es->y) && (ev->y <= (es->y + es->h)))
               show = 1;
             break;

           case E_GADCON_ORIENT_TOP:
           case E_GADCON_ORIENT_CORNER_TL:
           case E_GADCON_ORIENT_CORNER_TR:
             if (((ev->edge == E_ZONE_EDGE_TOP) ||
                  (ev->edge == E_ZONE_EDGE_TOP_LEFT) ||
                  (ev->edge == E_ZONE_EDGE_TOP_RIGHT)) &&
                 (ev->x >= es->x) && (ev->x <= (es->x + es->w)))
               show = 1;
             break;

           case E_GADCON_ORIENT_BOTTOM:
           case E_GADCON_ORIENT_CORNER_BL:
           case E_GADCON_ORIENT_CORNER_BR:
             if (((ev->edge == E_ZONE_EDGE_BOTTOM) ||
                  (ev->edge == E_ZONE_EDGE_BOTTOM_LEFT) ||
                  (ev->edge == E_ZONE_EDGE_BOTTOM_RIGHT)) &&
                 (ev->x >= es->x) && (ev->x <= (es->x + es->w)))
               show = 1;
             break;

           case E_GADCON_ORIENT_FLOAT:
           case E_GADCON_ORIENT_HORIZ:
           case E_GADCON_ORIENT_VERT:
           default:
             /* noop */
             break;
          }

        if (show)
          {
             edje_object_signal_emit(es->o_base, "e,state,focused", "e");
             e_shelf_toggle(es, 1);
          }
        else
          e_shelf_toggle(es, 0);
     }
   /*
      else if (type == ECORE_X_EVENT_MOUSE_IN)
      {
      Ecore_X_Event_Mouse_In *ev;

      ev = event;
      if ((!es->hidden) && (!es->menu))
        {
           int x, y, w, h;
           evas_object_geometry_get(es->o_event, &x, &y, &w, &h);
           if (!E_INSIDE(ev->x, ev->y, x, y, w, h))
             e_shelf_toggle(es, 0);
        }
      if (!es->popup) return ECORE_CALLBACK_PASS_ON;
      if (ev->win == es->popup->evas_win)
        {
           if (es->hidden || (!es->toggle))
             {
                edje_object_signal_emit(es->o_base, "e,state,focused", "e");
                e_shelf_toggle(es, 1);
             }
        }
      }
    */
   else if (type == ECORE_EVENT_MOUSE_MOVE)
     {
        Ecore_Event_Mouse_Move *ev;
        int x, y;
        Eina_Bool inside = EINA_FALSE;

        ev = event;
        inside = E_INSIDE(e_comp_canvas_x_root_adjust(ev->root.x),
                          e_comp_canvas_y_root_adjust(ev->root.y),
                          es->zone->x, es->zone->y, es->zone->w + 4, es->zone->h + 4);
        x = e_comp_canvas_x_root_adjust(ev->root.x) - es->zone->x;
        y = e_comp_canvas_y_root_adjust(ev->root.y) - es->zone->y;
        if (inside)
          inside = (
              ((E_INSIDE(x, y, es->x, es->y, es->w, es->h)) ||
               (E_INSIDE(x, y, es->x - 2, es->y - 2, es->w + 4, es->h + 4)) ||
               (E_INSIDE(x, y, es->x + 2, es->y + 2, es->w + 4, es->h + 4)))
              );
        if (inside)
          {
             if (es->autohide_timer)
               ecore_timer_loop_reset(es->autohide_timer);
          }
        if (inside)
          {
             if (es->hidden || (!es->toggle))
               {
                  edje_object_signal_emit(es->o_base, "e,state,focused", "e");
                  e_shelf_toggle(es, 1);
               }
          }
        else
          {
             if ((!es->hidden) && (es->toggle))
               e_shelf_toggle(es, 0);
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}

#if 0
static void
_e_shelf_cb_mouse_out2(E_Shelf *es, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, Evas_Event_Mouse_Out *ev)
{
   int x, y, w, h;

   evas_object_geometry_get(es->o_event, &x, &y, &w, &h);
   if (!E_INSIDE(ev->output.x, ev->output.y, x, y, w, h))
     e_shelf_toggle(es, 0);
}

static Eina_Bool
_e_shelf_cb_mouse_out(void *data, int type, void *event)
{
   E_Shelf *es;
   Ecore_X_Window win;

   es = data;

   if (type == ECORE_X_EVENT_MOUSE_OUT)
     {
        Ecore_X_Event_Mouse_Out *ev = event;
        int x, y, w, h;

        if (ev->win != e_comp->ee_win) return ECORE_CALLBACK_PASS_ON;

        evas_object_geometry_get(es->o_event, &x, &y, &w, &h);
        if (!E_INSIDE(ev->x, ev->y, x, y, w, h))
          {
             //fprintf(stderr, "EVENT: %d,%d %dx%d || MOUSE: %d,%d\n", x, y, w, h, ev->x, ev->y);
             e_shelf_toggle(es, 0);
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}

#endif
static void
_e_shelf_cb_dummy_moveresize(E_Shelf *es, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   int x, y, w, h;

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   if ((x != es->x) || (y != es->y) || (w != es->w) || (h != es->h))
     {
        es->x = x, es->y = y, es->w = w, es->h = h;
        e_zone_useful_geometry_dirty(es->zone);
     }
}

static void
_e_shelf_cb_dummy_del(E_Shelf *es, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_object_free(E_OBJECT(es));
}

static int
_e_shelf_cb_id_sort(const void *data1, const void *data2)
{
   const E_Shelf *es1, *es2;

   es1 = data1;
   es2 = data2;
   if ((es1->id) < (es2->id)) return -1;
   else if (es1->id > es2->id)
     return 1;
   return 0;
}

static Eina_Bool
_e_shelf_cb_hide_animator(void *data)
{
   E_Shelf *es;
   int hide_max = 0;
   double pos;

   es = data;
   if (!es->gadcon)
     {
        es->hide_animator = NULL;
        return EINA_FALSE;
     }
   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        hide_max = es->h - es->hidden_state_size - 1;
        if (es->hide_origin == -1) es->hide_origin = es->y;
        break;

      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_LT:
      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_RB:
      case E_GADCON_ORIENT_CORNER_RT:
        hide_max = es->w - es->hidden_state_size;
        if (es->hide_origin == -1) es->hide_origin = es->x;
        break;

      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_VERT:
      default:
        break;
     }

   pos = (ecore_loop_time_get() - es->hide_begin) / es->cfg->hide_duration;
   if (es->hidden)
     {
        es->hide_step = hide_max * pos;
        if (es->hide_step > hide_max) es->hide_step = hide_max;
     }
   else
     {
        es->hide_step = hide_max * (1.0 - pos);
        if (es->hide_step <= 0) es->hide_step = 0;
     }

   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
        e_shelf_move(es, es->x, es->hide_origin - es->hide_step);
        break;

      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        e_shelf_move(es, es->x, es->hide_origin + es->hide_step);
        break;

      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_LT:
        e_shelf_move(es, es->hide_origin - es->hide_step, es->y);
        break;

      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_RB:
      case E_GADCON_ORIENT_CORNER_RT:
        e_shelf_move(es, es->hide_origin + es->hide_step, es->y);
        break;

      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_VERT:
      default:
        break;
     }

   if (es->hidden)
     {
        if (es->hide_step == hide_max) goto end;
     }
   else
     {
        if (es->hide_step == 0) goto end;
     }
   return ECORE_CALLBACK_RENEW;

end:
   es->hide_animator = NULL;
   if (es->interrupted > -1)
     e_shelf_toggle(es, es->interrupted);
   else if (es->urgent_show)
     e_shelf_toggle(es, 0);
   else
     _e_shelf_toggle_client_fix(es);
   if ((!es->hidden) && es->cfg->autohide_show_action)
     {
        es->hidden = 1;
        if (!es->hide_timer)
          es->hide_timer = ecore_timer_loop_add(es->cfg->hide_timeout, _e_shelf_cb_hide_animator_timer, es);
     }
   if (es->hidden && (!es->hide_timer))
     edje_object_signal_emit(es->o_base, "e,state,hidden", "e");
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_shelf_cb_hide_animator_timer(void *data)
{
   E_Shelf *es;

   es = data;
   if (!es->hide_animator)
     {
        es->hide_begin = ecore_loop_time_get();
        es->hide_animator = ecore_animator_add(_e_shelf_cb_hide_animator, es);
     }
   es->hide_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_shelf_cb_hide_urgent_timer(void *data)
{
   E_Shelf *es;

   es = data;
   es->hide_timer = NULL;
   if (es->urgent_show) e_shelf_toggle(es, 0);
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_shelf_cb_instant_hide_timer(void *data)
{
   E_Shelf *es;

   es = data;
   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_TR:
        if (es->hidden)
          e_shelf_move(es, es->x, es->y - es->h + es->hidden_state_size);
        else
          e_shelf_move(es, es->x, es->y + es->h - es->hidden_state_size);
        break;

      case E_GADCON_ORIENT_BOTTOM:
      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_BR:
        if (es->hidden)
          e_shelf_move(es, es->x, es->y + es->h - es->hidden_state_size);
        else
          e_shelf_move(es, es->x, es->y - es->h + es->hidden_state_size);
        break;

      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_CORNER_LB:
      case E_GADCON_ORIENT_CORNER_LT:
        if (es->hidden)
          e_shelf_move(es, es->x - es->w + es->hidden_state_size, es->y);
        else
          e_shelf_move(es, es->x + es->w - es->hidden_state_size, es->y);
        break;

      case E_GADCON_ORIENT_RIGHT:
      case E_GADCON_ORIENT_CORNER_RB:
      case E_GADCON_ORIENT_CORNER_RT:
        if (es->hidden)
          e_shelf_move(es, es->x + es->w - es->hidden_state_size, es->y);
        else
          e_shelf_move(es, es->x - es->w + es->hidden_state_size, es->y);
        break;

      default:
        break;
     }
   es->instant_timer = NULL;
   _e_shelf_toggle_client_fix(es);
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_shelf_gadcon_populate_handler_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Gadcon_Populate *ev = event;
   Eina_List *l;
   E_Shelf *es;

   EINA_LIST_FOREACH(shelves, l, es)
     if (es->gadcon == ev->gc)
       {
          /* any shelf that's not already shown at this point will be
           * waiting for this event to show it so that we don't ever resize the shelf
           * object
           */
          if (e_shelf_desk_visible(es, NULL)) e_shelf_show(es);
          break;
       }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_shelf_cb_menu_rename_yes_cb(void *data, char *text)
{
   E_Shelf *es = data;
   Eina_List *l;
   E_Config_Shelf *cf_es;

   if ((!text) || (!text[0])) return;
   if (!strcmp(text, es->cfg->name)) return;
   EINA_LIST_FOREACH(e_config->shelves, l, cf_es)
     if (!strcmp(cf_es->name, text))
       {
          e_util_dialog_internal(_("Error"), _("A shelf with that name and id already exists!"));
          return;
       }
   e_shelf_name_set(es, text);
   e_config_save_queue();
}

static void
_e_shelf_cb_menu_rename_cb(void *data)
{
   E_Shelf *es = e_object_data_get(data);
   es->rename_dialog = NULL;
}

static void
_e_shelf_cb_menu_rename(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Shelf *es = data;
   if (es->rename_dialog) return;
   es->rename_dialog =
     e_entry_dialog_show(NULL, _("Rename Shelf"), "edit-rename",
                         _("Name:"), es->name, NULL, NULL,
                         _e_shelf_cb_menu_rename_yes_cb,
                         NULL, es);
   e_object_data_set(E_OBJECT(es->rename_dialog), es);
   e_object_del_attach_func_set(E_OBJECT(es->rename_dialog),
                                _e_shelf_cb_menu_rename_cb);
}

static void
_e_shelf_cb_menu_orient(void *data, E_Menu *m, E_Menu_Item *mi)
{
   E_Shelf *es = data;
   E_Menu_Item *mii;
   int orient = E_GADCON_ORIENT_LEFT;
   Eina_List *l;

   EINA_LIST_FOREACH(m->items, l, mii)
     {
        if (mi == mii)
          {
             E_Zone *zone;
             E_Config_Shelf *cf_es;
             void *cfd;
             if (es->cfg->orient == orient) return;
             es->cfg->orient = orient;
             zone = es->zone;
             cf_es = es->cfg;
             cfd = es->config_dialog;
             es->config_dialog = NULL;
             e_gadcon_unpopulate(es->gadcon);
             e_object_del(E_OBJECT(es));
             es = e_shelf_config_new(zone, cf_es);
             es->config_dialog = cfd;
             return;
          }
        orient++;
     }
}

static void
_e_shelf_menu_orientation_pre_cb(void *data, E_Menu *m)
{
   E_Menu_Item *mi;
   E_Shelf *es = data;
   int orient;

   if (m->items) return;

   for (orient = E_GADCON_ORIENT_LEFT; orient < E_GADCON_ORIENT_LAST; orient++)
     {
        mi = e_menu_item_new(m);
        e_util_gadcon_orient_menu_item_icon_set(orient, mi);
        e_menu_item_radio_set(mi, 1);
        e_menu_item_radio_group_set(mi, 1);
        e_menu_item_label_set(mi, _(orient_names[orient]));
        e_menu_item_callback_set(mi, _e_shelf_cb_menu_orient, es);
        if (es->cfg->orient == orient)
          e_menu_item_toggle_set(mi, 1);
        e_menu_item_disabled_set(mi, es->cfg->orient == orient);
     }
}

static void
_e_shelf_menu_pre_cb(void *data, E_Menu *m)
{
   E_Shelf *es;
   E_Menu_Item *mi;
   E_Menu *subm;

   es = data;
   e_menu_pre_activate_callback_set(m, NULL, NULL);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Contents"));
   e_util_menu_item_theme_icon_set(mi, "preferences-desktop-shelf");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_contents, es);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Settings"));
   e_util_menu_item_theme_icon_set(mi, "configure");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_config, es);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Orientation"));
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_config, es);
   subm = e_menu_new();
   e_menu_pre_activate_callback_set(subm, _e_shelf_menu_orientation_pre_cb, es);
   e_object_data_set(E_OBJECT(subm), es);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Autohide"));
   e_menu_item_check_set(mi, 1);
   e_menu_item_toggle_set(mi, es->cfg->autohide);
   //e_util_menu_item_theme_icon_set(mi, ""); FIXME
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_autohide, es);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Refresh"));
   e_util_menu_item_theme_icon_set(mi, "view-refresh");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_refresh, es);

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Rename"));
   e_util_menu_item_theme_icon_set(mi, "edit-rename");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_rename, es);

   mi = e_menu_item_new(m);
   e_menu_item_label_set(mi, _("Delete"));
   e_util_menu_item_theme_icon_set(mi, "list-remove");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_delete, es);

   if (m->parent_item) return;

   mi = e_menu_item_new(m);
   e_menu_item_separator_set(mi, 1);

   mi = e_menu_item_new(m);
   if (es->gadcon->editing)
     e_menu_item_label_set(mi, _("Stop Moving Gadgets"));
   else
     e_menu_item_label_set(mi, _("Begin Moving Gadgets"));
   e_util_menu_item_theme_icon_set(mi, "transform-scale");
   e_menu_item_callback_set(mi, _e_shelf_cb_menu_edit, es);
}

static void
_e_shelf_gadcon_client_remove(void *data, E_Gadcon_Client *gcc)
{
   E_Shelf *s;
   E_Gadcon *gc;

   s = data;
   gc = s->gadcon;
   if (gcc->cf) e_gadcon_client_config_del(gc->cf, gcc->cf);
   e_gadcon_unpopulate(gc);
   e_gadcon_populate(gc);
   e_config_save_queue();
}

static int
_e_shelf_gadcon_client_add(void *data, E_Gadcon_Client *gcc, const E_Gadcon_Client_Class *cc)
{
   E_Shelf *s;
   E_Gadcon *gc;

   s = data;
   gc = s->gadcon;
   if (gcc)
     {
        E_Config_Gadcon_Client *cf_gcc = gcc->cf;
        gcc->gadcon->cf->clients = eina_list_remove(gcc->gadcon->cf->clients, cf_gcc);
        if (gc->zone)
          cf_gcc->geom.res = gc->zone->w;
        else if (gc->o_container)
          {
             int w, h;
             evas_object_geometry_get(gc->o_container, NULL, NULL, &w, &h);
             switch (gc->orient)
               {
                case E_GADCON_ORIENT_VERT:
                case E_GADCON_ORIENT_LEFT:
                case E_GADCON_ORIENT_RIGHT:
                case E_GADCON_ORIENT_CORNER_LT:
                case E_GADCON_ORIENT_CORNER_RT:
                case E_GADCON_ORIENT_CORNER_LB:
                case E_GADCON_ORIENT_CORNER_RB:
                  cf_gcc->geom.res = h;
                  break;

                default:
                  cf_gcc->geom.res = w;
               }
          }
        else
          cf_gcc->geom.res = 800;
        cf_gcc->geom.size = 80;
        cf_gcc->geom.pos = cf_gcc->geom.res - cf_gcc->geom.size;
        gc->cf->clients = eina_list_append(gc->cf->clients, cf_gcc);
     }
   else if (!e_gadcon_client_config_new(gc, cc->name))
     return 0;
   e_gadcon_unpopulate(gc);
   e_gadcon_populate(gc);
   e_config_save_queue();
   return 1;
}

static const char *
_e_shelf_orient_icon_name_get(E_Shelf *s)
{
   const char *name;

   name = NULL;
   switch (s->cfg->orient)
     {
      case E_GADCON_ORIENT_LEFT:
        name = "preferences-position-left";
        break;

      case E_GADCON_ORIENT_RIGHT:
        name = "preferences-position-right";
        break;

      case E_GADCON_ORIENT_TOP:
        name = "preferences-position-top";
        break;

      case E_GADCON_ORIENT_BOTTOM:
        name = "preferences-position-bottom";
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        name = "preferences-position-top-left";
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        name = "preferences-position-top-right";
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        name = "preferences-position-bottom-left";
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        name = "preferences-position-bottom-right";
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        name = "preferences-position-left-top";
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        name = "preferences-position-right-top";
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        name = "preferences-position-left-bottom";
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        name = "preferences-position-right-bottom";
        break;

      default:
        name = "preferences-desktop-shelf";
        break;
     }
   return name;
}

static void
_e_shelf_bindings_add(E_Shelf *es)
{
   char buf[1024];

   /* TODO: This might delete edge windows, and then add them again further down. Should prevent this. */
   _e_shelf_bindings_del(es);

   /* Don't need edge binding if we don't hide shelf */
   if ((es->cfg) && (!es->cfg->autohide) &&
       (!es->cfg->autohide_show_action))
     return;

   snprintf(buf, sizeof(buf), "shelf.%d", es->id);
   switch (es->gadcon->orient)
     {
      case E_GADCON_ORIENT_FLOAT:
      case E_GADCON_ORIENT_HORIZ:
      case E_GADCON_ORIENT_VERT:
      default:
        /* noop */
        break;

      case E_GADCON_ORIENT_LEFT:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_LEFT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_RIGHT:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_RIGHT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_TOP:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_TOP,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_BOTTOM:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_BOTTOM,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_CORNER_TL:
      case E_GADCON_ORIENT_CORNER_LT:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_TOP,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_LEFT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_TOP_LEFT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_CORNER_TR:
      case E_GADCON_ORIENT_CORNER_RT:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_TOP,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_RIGHT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_TOP_RIGHT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_CORNER_BL:
      case E_GADCON_ORIENT_CORNER_LB:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_BOTTOM,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_LEFT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_BOTTOM_LEFT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;

      case E_GADCON_ORIENT_CORNER_BR:
      case E_GADCON_ORIENT_CORNER_RB:
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_BOTTOM,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_RIGHT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        e_bindings_edge_add(E_BINDING_CONTEXT_ZONE, E_ZONE_EDGE_BOTTOM_RIGHT,
                            0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
        break;
     }
}

static void
_e_shelf_bindings_del(E_Shelf *es)
{
   char buf[1024];
   E_Zone_Edge edge;

   snprintf(buf, sizeof(buf), "shelf.%d", es->id);
   for (edge = E_ZONE_EDGE_LEFT; edge <= E_ZONE_EDGE_BOTTOM_LEFT; edge++)
     e_bindings_edge_del(E_BINDING_CONTEXT_ZONE, edge,
                         0, E_BINDING_MODIFIER_NONE, 1, buf, NULL, 0);
}

static Eina_Bool
_e_shelf_on_current_desk(E_Shelf *es, E_Event_Zone_Edge *ev)
{
   E_Config_Shelf_Desk *sd;
   Eina_List *ll;
   int on_current_desk = 0;
   int on_all_desks = 1;

   if (ev->zone != es->zone) return EINA_FALSE;
   EINA_LIST_FOREACH(es->cfg->desk_list, ll, sd)
     {
        if (!sd) continue;
        on_all_desks = 0;
        if ((sd->x == ev->zone->desk_x_current) &&
            (sd->y == ev->zone->desk_y_current))
          {
             on_current_desk = 1;
             break;
          }
     }
   if ((!on_all_desks) && (!on_current_desk))
     return EINA_FALSE;

   return EINA_TRUE;
}

/*
 * NOTE TO FreeBSD users. Install libexecinfo from
 * ports/devel/libexecinfo and add -lexecinfo to LDFLAGS
 * to add backtrace support.
 */
#include "e.h"

#ifdef HAVE_WAYLAND
# ifdef USE_MODULE_WL_DRM
#  include <Ecore_Drm2.h>
# endif
#endif

#ifdef HAVE_EXECINFO_H
# include <execinfo.h>
#endif

static volatile Eina_Bool _e_x_composite_shutdown_try = 0;

#ifndef HAVE_WAYLAND_ONLY
static void
_e_x_composite_shutdown(void)
{
//   Ecore_X_Display *dpy;
   Ecore_X_Window root;

   if (_e_x_composite_shutdown_try) return;  /* we failed :-( */
   _e_x_composite_shutdown_try = 1;

//   dpy = ecore_x_display_get();
   root = ecore_x_window_root_first_get();

   /* ignore errors, we really don't care at this point */
   ecore_x_composite_unredirect_subwindows(root, ECORE_X_COMPOSITE_UPDATE_MANUAL);
   _e_x_composite_shutdown_try = 0;
}
#endif

#if 0
#define _e_write_safe(fd, buf) _e_write_safe_int(fd, buf, sizeof(buf))

static void
_e_write_safe_int(int fd, const char *buf, size_t size)
{
   while (size > 0)
     {
        ssize_t done = write(fd, buf, size);
        if (done >= 0)
          {
             buf += done;
             size -= done;
          }
        else
          {
             if ((errno == EAGAIN) || (errno == EINTR))
               continue;
             else
               {
                  perror("write");
                  return;
               }
          }
     }
}

#endif

static void
_e_crash(void)
{
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
        return;
#endif
#ifndef HAVE_WAYLAND_ONLY
   _e_x_composite_shutdown();
   ecore_x_pointer_ungrab();
   ecore_x_keyboard_ungrab();
   ecore_x_ungrab();
   ecore_x_sync();
   e_alert_show();
#endif
}

/* a tricky little devil, requires e and it's libs to be built
 * with the -rdynamic flag to GCC for any sort of decent output.
 */
E_API void
e_sigseg_act(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   _e_crash();
}

E_API void
e_sigill_act(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   // In case of a SIGILL in Enlightenment, Enlightenment start will catch the SIGILL and continue,
   // because evas cpu detection use that behaviour. But if we get a SIGILL after that, we end up in
   // this sig handler. So E start remember the SIGILL, and we will commit suicide with a USR1, followed
   // by a SEGV.
   kill(getpid(), SIGUSR1);
   kill(getpid(), SIGSEGV);
   pause();
   /* _e_x_composite_shutdown(); */
   /* ecore_x_pointer_ungrab(); */
   /* ecore_x_keyboard_ungrab(); */
   /* ecore_x_ungrab(); */
   /* ecore_x_sync(); */
   /* e_alert_show(); */
}

E_API void
e_sigfpe_act(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   _e_crash();
}

E_API void
e_sigbus_act(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   _e_crash();
}

E_API void
e_sigabrt_act(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   _e_crash();
}
#include "e.h"

#define SMART_NAME     "e_slidecore"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;
typedef struct _E_Smart_Item E_Smart_Item;

struct _E_Smart_Data
{
   Evas_Coord      x, y, w, h;

   Evas_Object    *smart_obj;
   Evas_Object    *event_obj;
   Evas_Object    *o1, *o2;
   Eina_List      *items;
   double          slide_time, slide_start;
   Ecore_Animator *slide_animator;
   Evas_Coord      dist, pos, slide_pos, slide_start_pos;
   int             p1, p2, pn;
   unsigned char   down E_BITFIELD;
};

struct _E_Smart_Item
{
   const char *label;
   const char *icon;
   void        (*func)(void *data);
   void       *data;
};

/* local subsystem functions */
static Eina_Bool _e_smart_cb_slide_animator(void *data);
static void      _e_smart_event_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_reconfigure(E_Smart_Data *sd);
static void      _e_smart_add(Evas_Object *obj);
static void      _e_smart_del(Evas_Object *obj);
static void      _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void      _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void      _e_smart_show(Evas_Object *obj);
static void      _e_smart_hide(Evas_Object *obj);
static void      _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void      _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void      _e_smart_clip_unset(Evas_Object *obj);
static void      _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_slidecore_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_slidecore_item_distance_set(Evas_Object *obj, Evas_Coord dist)
{
   API_ENTRY return;
   if (dist < 1) dist = 1;
   sd->dist = dist;
   _e_smart_reconfigure(sd);
}

E_API void
e_slidecore_item_add(Evas_Object *obj, const char *label, const char *icon, void (*func)(void *data), void *data)
{
   E_Smart_Item *it;

   API_ENTRY return;
   it = calloc(1, sizeof(E_Smart_Item));
   if (!it) return;
   if (label) it->label = eina_stringshare_add(label);
   if (icon) it->icon = eina_stringshare_add(icon);
   it->func = func;
   it->data = data;
   sd->items = eina_list_append(sd->items, it);
   _e_smart_reconfigure(sd);
}

E_API void
e_slidecore_jump(Evas_Object *obj, int num)
{
   API_ENTRY return;

   sd->slide_start_pos = sd->pos;
   sd->slide_pos = num * sd->dist;
   if (!sd->slide_animator)
     sd->slide_animator = ecore_animator_add(_e_smart_cb_slide_animator,
                                             sd);
   sd->slide_start = ecore_loop_time_get();
}

E_API void
e_slidecore_slide_time_set(Evas_Object *obj, double t)
{
   API_ENTRY return;
   sd->slide_time = t;
}

/* local subsystem functions */

static Eina_Bool
_e_smart_cb_slide_animator(void *data)
{
   E_Smart_Data *sd;
   double t;

   sd = data;
   t = (ecore_loop_time_get() - sd->slide_start) / sd->slide_time;
   if (t > 1.0) t = 1.0;
   t = 1.0 - t;
   t = 1.0 - (t * t * t * t); /* more t's - more curve */
   if (t > 1.0) t = 1.0;
   sd->pos = sd->slide_start_pos + (t * (sd->slide_pos - sd->slide_start_pos));
   _e_smart_reconfigure(sd);

   if (t >= 1.0)
     {
        sd->slide_animator = NULL;
        return ECORE_CALLBACK_CANCEL;
     }
   return ECORE_CALLBACK_RENEW;
}

static void
_e_smart_event_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (ev->button == 1) sd->down = 1;
   if (sd->slide_animator)
     {
        ecore_animator_del(sd->slide_animator);
        sd->slide_animator = NULL;
     }
}

static void
_e_smart_event_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (ev->button == 1)
     {
        int gop = 0;

        sd->down = 0;

        gop = sd->pos - (sd->p1 * sd->dist);
        gop = ((gop / sd->dist) * sd->dist) + (sd->p1 * sd->dist);
        sd->slide_start_pos = sd->pos;
        sd->slide_pos = gop;
        if (!sd->slide_animator)
          sd->slide_animator = ecore_animator_add(_e_smart_cb_slide_animator,
                                                  sd);
        sd->slide_start = ecore_loop_time_get();
     }
}

static void
_e_smart_event_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (!sd->down) return;
   sd->pos += ev->cur.canvas.x - ev->prev.canvas.x;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   Evas_Coord dp, pos;
   int p1, p2, at, pl1, pl2, n;
   int r, g, b, a;

   evas_object_move(sd->event_obj, sd->x, sd->y);
   evas_object_resize(sd->event_obj, sd->w, sd->h);

   pos = sd->pos;
   n = eina_list_count(sd->items);
   while (pos < 0)
     pos += (sd->dist * n);
   p1 = pos / sd->dist;
   p2 = (pos + sd->dist) / sd->dist;
   dp = pos - (p1 * sd->dist);
   at = (dp * 255) / (sd->dist - 1);

   while ((p1 < 0) || (p2 < 0))
     {
        p1 += n;
        p2 += n;
        at += 255;
        dp += sd->dist;
     }
   if ((sd->p1 != p1) || (sd->p2 != p2) || (sd->pn != n))
     {
        E_Smart_Item *it1, *it2;

        sd->pn = n;
        if (n > 0)
          {
             sd->p1 = p1;
             sd->p2 = p2;
             if (sd->o1) evas_object_del(sd->o1);
             if (sd->o2) evas_object_del(sd->o2);
             sd->o1 = NULL;
             sd->o2 = NULL;
             pl1 = sd->p1 % n;
             pl2 = sd->p2 % n;
             it1 = eina_list_nth(sd->items, pl1);
             it2 = eina_list_nth(sd->items, pl2);
             if (it1 && it2)
               {
                  sd->o1 = e_util_icon_theme_icon_add(it1->icon, 512,
                                                      evas_object_evas_get(sd->smart_obj));
                  if (sd->o1)
                    {
                       e_icon_scale_size_set(sd->o1, 0);
                       evas_object_stack_below(sd->o1, sd->event_obj);
                       evas_object_pass_events_set(sd->o1, 1);
                       evas_object_smart_member_add(sd->o1, sd->smart_obj);
                       e_icon_fill_inside_set(sd->o1, 0);
                       evas_object_clip_set(sd->o1, evas_object_clip_get(sd->smart_obj));
                       evas_object_show(sd->o1);
                    }
                  sd->o2 = e_util_icon_theme_icon_add(it2->icon, 512,
                                                      evas_object_evas_get(sd->smart_obj));
                  if (sd->o2)
                    {
                       e_icon_scale_size_set(sd->o2, 0);
                       evas_object_stack_below(sd->o2, sd->event_obj);
                       evas_object_pass_events_set(sd->o2, 1);
                       evas_object_smart_member_add(sd->o2, sd->smart_obj);
                       e_icon_fill_inside_set(sd->o2, 0);
                       evas_object_clip_set(sd->o2, evas_object_clip_get(sd->smart_obj));
                       evas_object_show(sd->o2);
                    }
                  if (at < 128)
                    {
                       if (it1->func) it1->func(it1->data);
                    }
                  else
                    {
                       if (it2->func) it2->func(it2->data);
                    }
               }
          }
     }
   evas_object_color_get(sd->smart_obj, &r, &g, &b, &a);

   evas_object_move(sd->o1, sd->x - sd->dist + dp, sd->y);
//   printf("SZ: %ix%i\n", sd->w + sd->dist + sd->dist, sd->h);
   evas_object_resize(sd->o1, sd->w + sd->dist + sd->dist, sd->h);
   evas_object_color_set(sd->o1, r, g, b, a);

   evas_object_move(sd->o2, sd->x - sd->dist - sd->dist + dp, sd->y);
   evas_object_resize(sd->o2, sd->w + sd->dist + sd->dist, sd->h);
   evas_object_color_set(sd->o2, (r * at) / 255, (g * at) / 255, (b * at) / 255, (a * at) / 255);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Evas_Object *o;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;

   evas_object_propagate_events_set(obj, 0);

   sd->dist = 48;
   sd->pos = 0;
   sd->p1 = -1;
   sd->p2 = -1;

   o = evas_object_rectangle_add(evas_object_evas_get(obj));
   sd->event_obj = o;
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_smart_event_mouse_down, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, _e_smart_event_mouse_up, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, _e_smart_event_mouse_move, sd);
   evas_object_smart_member_add(o, obj);
   evas_object_repeat_events_set(o, 1);

   sd->slide_time = 0.5;
}

static void
_e_smart_del(Evas_Object *obj)
{
   E_Smart_Item *it;

   INTERNAL_ENTRY;
   if (sd->slide_animator) ecore_animator_del(sd->slide_animator);
   EINA_LIST_FREE(sd->items, it)
     {
        if (it->label) eina_stringshare_del(it->label);
        if (it->icon) eina_stringshare_del(it->icon);
        free(it);
     }
   evas_object_del(sd->event_obj);
   if (sd->o1) evas_object_del(sd->o1);
   if (sd->o2) evas_object_del(sd->o2);
   free(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->event_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->event_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   if (sd->o1) evas_object_color_set(sd->o1, r, g, b, a);
   if (sd->o2) evas_object_color_set(sd->o2, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->event_obj, clip);
   if (sd->o1) evas_object_clip_set(sd->o1, clip);
   if (sd->o2) evas_object_clip_set(sd->o2, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->event_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#define _XOPEN_SOURCE  600
#include "e.h"

#define SMART_NAME     "e_slider"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data           E_Smart_Data;

typedef struct _E_Slider_Special_Value E_Slider_Special_Value;

struct _E_Smart_Data
{
   Evas_Coord   x, y, w, h;

   Evas_Object *smart_obj;
   Evas_Object *edje_obj;
   Evas_Object *event;
   double       val, val_min, val_max, val_range, step_size;
   int          reversed, step_count, horizontal;
   int          direction;
   int          changing;
   const char  *format;
   Evas_Coord   minw, minh;
   Ecore_Timer *set_timer;
   Eina_List   *special_values;
   Eina_Bool    disabled E_BITFIELD;
};

struct _E_Slider_Special_Value
{
   double      value;
   double      error;
   const char *label;
};

/* local subsystem functions */
static Eina_Bool _e_smart_set_timer(void *data);
static void      _e_smart_value_update(E_Smart_Data *sd);
static void      _e_smart_value_update_now(E_Smart_Data *sd);
static void      _e_smart_value_fetch(E_Smart_Data *sd);
static void      _e_smart_value_limit(E_Smart_Data *sd);
static void      _e_smart_format_update(E_Smart_Data *sd);
static void      _e_smart_signal_cb_drag(void *data, Evas_Object *obj, const char *emission, const char *source);
static void      _e_smart_signal_cb_drag_start(void *data, Evas_Object *obj, const char *emission, const char *source);
static void      _e_smart_signal_cb_drag_stop(void *data, Evas_Object *obj, const char *emission, const char *source);
static void      _e_smart_event_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_smart_event_mouse_down(void *data, Evas *e, Evas_Object *obj EINA_UNUSED, void *event_info);
static void      _e_smart_reconfigure(E_Smart_Data *sd);
static void      _e_smart_add(Evas_Object *obj);
static void      _e_smart_del(Evas_Object *obj);
static void      _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void      _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void      _e_smart_show(Evas_Object *obj);
static void      _e_smart_hide(Evas_Object *obj);
static void      _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void      _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void      _e_smart_clip_unset(Evas_Object *obj);
static void      _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_slider_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_slider_orientation_set(Evas_Object *obj, int horizontal)
{
   API_ENTRY return;
   if (sd->horizontal == horizontal) return;
   sd->horizontal = horizontal;
   if (sd->horizontal)
     e_theme_edje_object_set(sd->edje_obj, "base/theme/widgets",
                             "e/widgets/slider_horizontal");
   else
     e_theme_edje_object_set(sd->edje_obj, "base/theme/widgets",
                             "e/widgets/slider_vertical");
   edje_object_size_min_calc(sd->edje_obj, &(sd->minw), &(sd->minh));
   _e_smart_value_update(sd);
}

E_API int
e_slider_orientation_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->horizontal;
}

E_API void
e_slider_value_set(Evas_Object *obj, double val)
{
   API_ENTRY return;
   sd->val = val;
   _e_smart_value_update_now(sd);
}

E_API double
e_slider_value_get(Evas_Object *obj)
{
   API_ENTRY return 0.0;
   return sd->val;
}

E_API void
e_slider_value_range_set(Evas_Object *obj, double min, double max)
{
   API_ENTRY return;
   sd->val_min = min;
   sd->val_max = max;
   if (sd->val_max < sd->val_min) sd->val_min = sd->val_max;
   sd->val_range = sd->val_max - sd->val_min;
   sd->direction = 0;
   _e_smart_value_limit(sd);
   _e_smart_value_update_now(sd);
}

E_API void
e_slider_value_range_get(Evas_Object *obj, double *min, double *max)
{
   API_ENTRY return;
   if (min) *min = sd->val_min;
   if (max) *max = sd->val_max;
}

E_API void
e_slider_value_step_size_set(Evas_Object *obj, double step_size)
{
   double step;

   API_ENTRY return;
   if (step_size < 0.0) step_size = 0.0;
   sd->step_size = step_size;
   step = 0.0;
   if (sd->val_range > 0.0)
     step = step_size / sd->val_range;
   edje_object_part_drag_step_set
     (sd->edje_obj, "e.dragable.slider", step, step);
   sd->direction = 0;
   _e_smart_value_limit(sd);
   _e_smart_value_update_now(sd);
}

E_API double
e_slider_value_step_size_get(Evas_Object *obj)
{
   API_ENTRY return 0.0;
   return sd->step_size;
}

E_API void
e_slider_value_step_count_set(Evas_Object *obj, int step_count)
{
   API_ENTRY return;
   sd->step_count = step_count;
   sd->direction = 0;
   _e_smart_value_limit(sd);
   _e_smart_value_update_now(sd);
}

E_API int
e_slider_value_step_count_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->step_count;
}

E_API void
e_slider_value_format_display_set(Evas_Object *obj, const char *format)
{
   int changed = 0;

   API_ENTRY return;
   if (((sd->format) && (!format)) || ((!sd->format) && (format))) changed = 1;
   if (sd->format) eina_stringshare_del(sd->format);
   sd->format = NULL;
   if (format) sd->format = eina_stringshare_add(format);
   if (changed)
     {
        if (sd->format)
          edje_object_signal_emit(sd->edje_obj, "e,action,show,label", "e");
        else
          edje_object_signal_emit(sd->edje_obj, "e,action,hide,label", "e");
     }
   _e_smart_format_update(sd);
   edje_object_message_signal_process(sd->edje_obj);
   edje_object_size_min_calc(sd->edje_obj, &(sd->minw), &(sd->minh));
}

E_API const char *
e_slider_value_format_display_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->format;
}

E_API void
e_slider_direction_set(Evas_Object *obj, int reversed)
{
   API_ENTRY return;
   sd->reversed = reversed;
   _e_smart_value_update_now(sd);
}

E_API void
e_slider_disabled_set(Evas_Object *obj, Eina_Bool disable)
{
   API_ENTRY return;

   if (sd->disabled == disable) return;
   sd->disabled = disable;

   if (disable)
     edje_object_signal_emit(sd->edje_obj, "e,state,disabled", "e");
   else
     edje_object_signal_emit(sd->edje_obj, "e,state,enabled", "e");
}

E_API int
e_slider_direction_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->reversed;
}

E_API void
e_slider_size_min_get(Evas_Object *obj, Evas_Coord *minw, Evas_Coord *minh)
{
   API_ENTRY return;
   if (minw) *minw = sd->minw;
   if (minh) *minh = sd->minh;
}

E_API Evas_Object *
e_slider_edje_object_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->edje_obj;
}

E_API void
e_slider_special_value_add(Evas_Object *obj, double value, double error, const char *label)
{
   E_Slider_Special_Value *sv;
   API_ENTRY return;

   sv = malloc(sizeof(E_Slider_Special_Value));
   if (!sv) return;
   sv->value = value;
   sv->error = error;
   sv->label = eina_stringshare_add(label);

   sd->special_values = eina_list_append(sd->special_values, sv);
   _e_smart_format_update(sd);
}

/* local subsystem functions */
static Eina_Bool
_e_smart_set_timer(void *data)
{
   E_Smart_Data *sd = data;
   double pos = 0.0;

   if (sd->val_range > 0.0) pos = (sd->val - sd->val_min) / sd->val_range;
   if (pos < 0.0) pos = 0.0;
   else if (pos > 1.0)
     pos = 1.0;
   if (sd->reversed) pos = 1.0 - pos;
   sd->changing++;
   if (sd->set_timer) ecore_timer_del(sd->set_timer);
   sd->set_timer = NULL;
   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.slider", pos, pos);
   edje_object_message_signal_process(sd->edje_obj);
   sd->changing--;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_smart_value_update(E_Smart_Data *sd)
{
   if (sd->set_timer) ecore_timer_del(sd->set_timer);
   sd->set_timer = ecore_timer_loop_add(0.05, _e_smart_set_timer, sd);
}

static void
_e_smart_value_update_now(E_Smart_Data *sd)
{
   _e_smart_set_timer(sd);
}

static void
_e_smart_value_fetch(E_Smart_Data *sd)
{
   double posx = 0.0, posy = 0.0, pos = 0.0;

   edje_object_part_drag_value_get
     (sd->edje_obj, "e.dragable.slider", &posx, &posy);
   if (sd->horizontal) pos = posx;
   else pos = posy;
   sd->val = (pos * sd->val_range) + sd->val_min;
}

static void
_e_smart_value_limit(E_Smart_Data *sd)
{
   if (sd->val < sd->val_min) sd->val = sd->val_min;
   if (sd->val > sd->val_max) sd->val = sd->val_max;
   if (sd->val_range > 0.0)
     {
        if (sd->step_count > 0)
          {
             double p, s;

             s = sd->val_range / sd->step_count;
             p = sd->val / s;
             if (sd->direction == 1)
               p = ceil(p);
             else if (sd->direction == -1)
               p = floor(p);
             else
               p = round(p);
             sd->val = p * s;
          }
        else if (sd->step_size > 0.0)
          {
             double p;

             p = sd->val / sd->step_size;
             if (sd->direction == 1)
               p = ceil(p);
             else if (sd->direction == -1)
               p = floor(p);
             else
               p = round(p);
             sd->val = p * sd->step_size;
          }
     }
   sd->direction = 0;
}

static void
_e_smart_format_update(E_Smart_Data *sd)
{
   const E_Slider_Special_Value *sv;
   const Eina_List *l;

   EINA_LIST_FOREACH(sd->special_values, l, sv)
     if (fabs(sd->val - sv->value) <= sv->error)
       {
          edje_object_part_text_set(sd->edje_obj, "e.text.label", sv->label);
          return;
       }

   if (sd->format)
     {
        char buf[256];
DISABLE_WARNING(format-nonliteral, format-nonliteral, format-nonliteral)
        snprintf(buf, sizeof(buf), sd->format, sd->val);
ENABLE_WARNING(format-nonliteral, format-nonliteral, format-nonliteral)
        edje_object_part_text_set(sd->edje_obj, "e.text.label", buf);
     }
}

static void
_e_smart_signal_cb_drag(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd = data;
   double pval = sd->val;

   _e_smart_value_fetch(sd);
   _e_smart_value_limit(sd);
   _e_smart_format_update(sd);
   if (sd->changing) return;
   if (!EINA_DBL_EQ(sd->val, pval))
     evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_signal_cb_drag_start(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd = data;
   double pval = sd->val;

   _e_smart_value_fetch(sd);
   _e_smart_value_limit(sd);
   _e_smart_format_update(sd);
   if (sd->changing) return;
   if (!EINA_DBL_EQ(sd->val, pval))
     evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_signal_cb_drag_stop(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd = data;
   double pval = sd->val;

   _e_smart_value_fetch(sd);
   _e_smart_value_limit(sd);
   _e_smart_format_update(sd);
   _e_smart_value_update(sd);
   if (sd->changing) return;
   if (!EINA_DBL_EQ(sd->val, pval))
     evas_object_smart_callback_call(sd->smart_obj, "changed", NULL);
}

static void
_e_smart_signal_cb_wheel_up(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd = data;

   if (sd->disabled) return;
   edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", -0.05, -0.05);
   sd->direction = -1;
}

static void
_e_smart_signal_cb_wheel_down(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Smart_Data *sd = data;

   if (sd->disabled) return;
   edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", 0.05, 0.05);
   sd->direction = 1;
}

static void
_e_smart_event_key_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Key_Down *ev = event_info;
   E_Smart_Data *sd = data;

   if (sd->disabled) return;
   if ((!strcmp(ev->key, "Down")) ||
       (!strcmp(ev->key, "KP_Down")) ||
       (!strcmp(ev->key, "Left")) ||
       (!strcmp(ev->key, "KP_Left")))
     {
        if (sd->step_count > 0)
          {
             double s = sd->val_range / sd->step_count;
             edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", -s, -s);
          }
        else
          edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", -1, -1);
        sd->direction = -1;
     }
   else if ((!strcmp(ev->key, "Up")) ||
            (!strcmp(ev->key, "KP_Up")) ||
            (!strcmp(ev->key, "Right")) ||
            (!strcmp(ev->key, "KP_Right")))
     {
        if (sd->step_count > 0)
          {
             double s = sd->val_range / sd->step_count;
             edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", s, s);
          }
        else
          edje_object_part_drag_step(sd->edje_obj, "e.dragable.slider", 1, 1);
        sd->direction = 1;
     }
   else if ((!strcmp(ev->key, "Home")) ||
            (!strcmp(ev->key, "KP_Home")))
     {
        edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.slider", 0., 0.);
        sd->direction = 0;
     }
   else if ((!strcmp(ev->key, "End")) ||
            (!strcmp(ev->key, "KP_End")))
     {
        edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.slider", 1., 1.);
        sd->direction = 0;
     }
}

static void
_e_smart_event_mouse_down(void *data, Evas *e, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   Evas_Coord x, y, w, h;
   E_Smart_Data *sd = data;
   double pos;
   static int in_md = 0;

   if (sd->disabled) return;
   if (in_md > 0) return;
   in_md++;
   evas_object_geometry_get(sd->event, &x, &y, &w, &h);
   if (sd->horizontal)
     {
        pos = ((double)ev->output.x - (double)x) / (double)w;
        if (pos > 1.0) pos = 1.0;
        if (pos < 0.0) pos = 0.0;
        if (sd->reversed)
          pos = 1.0 - pos;
     }
   else
     {
        pos = (((double)ev->output.y - (double)y) / (double)h);
        if (pos > 1.0) pos = 1.0;
        if (pos < 0.0) pos = 0.0;
        if (!sd->reversed)
          pos = 1.0 - pos;
     }

   edje_object_part_drag_value_set(sd->edje_obj, "e.dragable.slider", pos, pos);
   edje_object_message_signal_process(sd->edje_obj); /* really needed or go in infinite loop */
   evas_event_feed_mouse_cancel(e, 0, NULL);
   evas_event_feed_mouse_down(e, 1, EVAS_BUTTON_NONE, ev->timestamp, NULL);
   in_md--;
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   evas_object_move(sd->edje_obj, sd->x, sd->y);
   evas_object_resize(sd->edje_obj, sd->w, sd->h);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;

   sd->val = 0.0;
   sd->val_min = 0.0;
   sd->val_max = 1.0;
   sd->val_range = 1.0;
   sd->step_size = 0.01;
   sd->reversed = 0;
   sd->step_count = 0;
   sd->horizontal = 0;
   sd->format = NULL;
   sd->direction = 0;

   sd->edje_obj = edje_object_add(evas_object_evas_get(obj));
   e_theme_edje_object_set(sd->edje_obj, "base/theme/widgets",
                           "e/widgets/slider_vertical");
   edje_object_size_min_calc(sd->edje_obj, &(sd->minw), &(sd->minh));
   evas_object_smart_member_add(sd->edje_obj, obj);

   sd->event = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_color_set(sd->event, 0, 0, 0, 0);
   evas_object_pass_events_set(sd->event, EINA_TRUE);
   edje_object_part_swallow(sd->edje_obj, "e.swallow.bar", sd->event);
   evas_object_smart_member_add(sd->event, sd->edje_obj);

   edje_object_signal_callback_add(sd->edje_obj, "drag", "*", _e_smart_signal_cb_drag, sd);
   edje_object_signal_callback_add(sd->edje_obj, "drag,start", "*", _e_smart_signal_cb_drag_start, sd);
   edje_object_signal_callback_add(sd->edje_obj, "drag,stop", "*", _e_smart_signal_cb_drag_stop, sd);
   edje_object_signal_callback_add(sd->edje_obj, "drag,step", "*", _e_smart_signal_cb_drag_stop, sd);
   edje_object_signal_callback_add(sd->edje_obj, "drag,set", "*", _e_smart_signal_cb_drag_stop, sd);
   edje_object_signal_callback_add(sd->edje_obj, "mouse,wheel,0,1", "*", _e_smart_signal_cb_wheel_up, sd);
   edje_object_signal_callback_add(sd->edje_obj, "mouse,wheel,0,-1", "*", _e_smart_signal_cb_wheel_down, sd);

   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN, _e_smart_event_key_down, sd);
   evas_object_event_callback_add(sd->event, EVAS_CALLBACK_MOUSE_DOWN, _e_smart_event_mouse_down, sd);
}

static void
_e_smart_del(Evas_Object *obj)
{
   E_Slider_Special_Value *sv;

   INTERNAL_ENTRY;
   evas_object_del(sd->event);
   evas_object_del(sd->edje_obj);
   if (sd->format) eina_stringshare_del(sd->format);
   if (sd->set_timer) ecore_timer_del(sd->set_timer);
   EINA_LIST_FREE(sd->special_values, sv)
     {
        eina_stringshare_del(sv->label);
        free(sv);
     }
   free(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   if ((sd->x == x) && (sd->y == y)) return;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   if ((sd->w == w) && (sd->h == h)) return;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->edje_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->edje_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->edje_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->edje_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->edje_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static Evas_Smart_Class sc = EVAS_SMART_CLASS_INIT_NAME_VERSION(SMART_NAME);
      if (!sc.add)
        {
           sc.add = _e_smart_add;
           sc.del = _e_smart_del;
           sc.move = _e_smart_move;
           sc.resize = _e_smart_resize;
           sc.show = _e_smart_show;
           sc.hide = _e_smart_hide;
           sc.color_set = _e_smart_color_set;
           sc.clip_set = _e_smart_clip_set;
           sc.clip_unset = _e_smart_clip_unset;
        }
      _e_smart = evas_smart_class_new(&sc);
   }
}

#include "e.h"

#define SMART_NAME     "e_slidesel"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;
typedef struct _E_Smart_Item E_Smart_Item;

struct _E_Smart_Data
{
   Evas_Coord    x, y, w, h;

   Evas_Object  *smart_obj;
   Evas_Object  *edje_obj;
   Evas_Object  *event_obj;
   Evas_Object  *slide_obj;
   Eina_List    *items;
   Evas_Coord    down_x, down_y;
   E_Smart_Item *cur;
   double        down_time;
   unsigned char down E_BITFIELD;
   unsigned char down_cancel E_BITFIELD;
};

struct _E_Smart_Item
{
   E_Smart_Data *sd;
   const char   *label;
   const char   *icon;
   void          (*func)(void *data);
   void         *data;
};

/* local subsystem functions */
static void _e_smart_event_mouse_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_smart_event_mouse_up(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_smart_event_mouse_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_smart_event_key_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_smart_reconfigure(E_Smart_Data *sd);
static void _e_smart_add(Evas_Object *obj);
static void _e_smart_del(Evas_Object *obj);
static void _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _e_smart_show(Evas_Object *obj);
static void _e_smart_hide(Evas_Object *obj);
static void _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void _e_smart_clip_unset(Evas_Object *obj);
static void _e_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

static void
_e_smart_label_change(void *data)
{
   E_Smart_Item *it;

   it = data;
   edje_object_part_text_set(it->sd->edje_obj, "e.text.label", it->label);
   it->sd->cur = it;
}

/* externally accessible functions */
E_API Evas_Object *
e_slidesel_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_slidesel_item_distance_set(Evas_Object *obj, Evas_Coord dist)
{
   API_ENTRY return;
   e_slidecore_item_distance_set(sd->slide_obj, dist);
}

E_API void
e_slidesel_item_add(Evas_Object *obj, const char *label, const char *icon, void (*func)(void *data), void *data)
{
   E_Smart_Item *it;

   API_ENTRY return;
   it = calloc(1, sizeof(E_Smart_Item));
   if (!it) return;
   it->sd = sd;
   if (label) it->label = eina_stringshare_add(label);
   if (icon) it->icon = eina_stringshare_add(icon);
   it->func = func;
   it->data = data;
   sd->items = eina_list_append(sd->items, it);
   e_slidecore_item_add(sd->slide_obj, label, icon, _e_smart_label_change, it);
}

E_API void
e_slidesel_jump(Evas_Object *obj, int num)
{
   API_ENTRY return;
   e_slidecore_jump(sd->slide_obj, num);
}

/* local subsystem functions */
static void
_e_smart_event_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (ev->button == 1)
     {
        sd->down_time = ecore_loop_time_get();
        sd->down = 1;
        sd->down_cancel = 0;
        sd->down_x = ev->canvas.x;
        sd->down_y = ev->canvas.y;
        edje_object_signal_emit(sd->edje_obj, "e,state,slide,hint,on", "e");
     }
}

static void
_e_smart_event_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (ev->button == 1)
     {
        if (!sd->down_cancel)
          {
             edje_object_signal_emit(sd->edje_obj, "e,state,slide,hint,off", "e");
             if (!(ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD))
               {
                  if (sd->cur)
                    {
                       /* get rid of accidental release and presses */
//		       if ((t - sd->down_time) > 0.2)
                       {
                          edje_object_signal_emit(sd->edje_obj, "e,action,select", "e");
                          if (sd->cur->func) sd->cur->func(sd->cur->data);
                       }
                    }
               }
          }
        sd->down = 0;
     }
}

static void
_e_smart_event_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if ((sd->down) && (!sd->down_cancel))
     {
        Evas_Coord d1, d2, d;

        d1 = ev->cur.canvas.x - sd->down_x;
        d2 = ev->cur.canvas.y - sd->down_y;
        d = (d1 * d1) + (d2 * d2);
        if (d > (64 * 64))
          {
             edje_object_signal_emit(sd->edje_obj, "e,state,slide,hint,off", "e");
             sd->down_cancel = 1;
          }
     }
}

static void
_e_smart_event_key_down(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
/*
   Evas_Event_Key_Down *ev;
   E_Smart_Data *sd;

   sd = data;
   ev = event_info;
   if (!strcmp(ev->key, "Left"))
     x -= sd->step.x;
   else if (!strcmp(ev->key, "Right"))
     x += sd->step.x;
   else if (!strcmp(ev->key, "Up"))
     y -= sd->step.y;
   else if (!strcmp(ev->key, "Home"))
     y = 0;
   else if (!strcmp(ev->key, "End"))
     y = my;
   else if (!strcmp(ev->key, "Down"))
     y += sd->step.y;
   else if (!strcmp(ev->key, "Prior"))
     {
        if (sd->page.y < 0)
          y -= -(sd->page.y * vh) / 100;
        else
          y -= sd->page.y;
     }
   else if (!strcmp(ev->key, "Next"))
     {
        if (sd->page.y < 0)
          y += -(sd->page.y * vh) / 100;
        else
          y += sd->page.y;
     }
 */
}

static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   evas_object_move(sd->edje_obj, sd->x, sd->y);
   evas_object_resize(sd->edje_obj, sd->w, sd->h);
   evas_object_move(sd->event_obj, sd->x, sd->y);
   evas_object_resize(sd->event_obj, sd->w, sd->h);
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;
   Evas_Object *o;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   evas_object_smart_data_set(obj, sd);

   sd->smart_obj = obj;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;

   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN, _e_smart_event_key_down, sd);
   evas_object_propagate_events_set(obj, 0);

   o = edje_object_add(evas_object_evas_get(obj));
   sd->edje_obj = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/slidesel");
   evas_object_smart_member_add(o, obj);

   o = e_slidecore_add(evas_object_evas_get(obj));
   sd->slide_obj = o;
   edje_object_part_swallow(sd->edje_obj, "e.swallow.content", o);
   evas_object_show(o);

   o = evas_object_rectangle_add(evas_object_evas_get(obj));
   sd->event_obj = o;
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_smart_event_mouse_down, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, _e_smart_event_mouse_up, sd);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, _e_smart_event_mouse_move, sd);
   evas_object_smart_member_add(o, obj);
   evas_object_repeat_events_set(o, 1);
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_del(sd->slide_obj);
   evas_object_del(sd->edje_obj);
   evas_object_del(sd->event_obj);
   free(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_show(sd->edje_obj);
   evas_object_show(sd->event_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_hide(sd->edje_obj);
   evas_object_hide(sd->event_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   evas_object_color_set(sd->edje_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   evas_object_clip_set(sd->edje_obj, clip);
   evas_object_clip_set(sd->event_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   evas_object_clip_unset(sd->edje_obj);
   evas_object_clip_unset(sd->event_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#include "e.h"

Evas_Smart *_e_spectrum_smart = NULL;

typedef struct _E_Spectrum E_Spectrum;

struct _E_Spectrum
{
   Evas_Object      *o_spectrum;
   Evas_Object      *o_event;
   Evas_Object      *o_cursor;

   int               iw, ih; /* square image width/height */
   E_Color_Component mode;

   E_Color          *cv;
   Ecore_Timer      *draw_timer;
};

static Eina_Bool _e_spectrum_redraw(void *d);

static void
_e_spectrum_smart_add(Evas_Object *o)
{
   E_Spectrum *sp;
   sp = E_NEW(E_Spectrum, 1);

   if (!sp) return;

   evas_object_smart_data_set(o, sp);

   sp->mode = E_COLOR_COMPONENT_R;

   sp->o_spectrum = evas_object_image_add(evas_object_evas_get(o));
   sp->iw = sp->ih = 255;
   evas_object_image_size_set(sp->o_spectrum, sp->iw, sp->ih);
   evas_object_image_alpha_set(sp->o_spectrum, 1);

   evas_object_smart_member_add(sp->o_spectrum, o);
}

static void
_e_spectrum_smart_del(Evas_Object *o)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   evas_object_del(sp->o_spectrum);
   evas_object_del(sp->o_event);
   evas_object_del(sp->o_cursor);

   if (sp->draw_timer) ecore_timer_del(sp->draw_timer);

   E_FREE(sp);
}

static void
_e_spectrum_smart_show(Evas_Object *o)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   evas_object_show(sp->o_spectrum);
   evas_object_show(sp->o_event);
   evas_object_show(sp->o_cursor);
}

static void
_e_spectrum_smart_hide(Evas_Object *o)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   evas_object_hide(sp->o_spectrum);
   evas_object_hide(sp->o_event);
   evas_object_hide(sp->o_cursor);
}

static void
_e_spectrum_smart_move(Evas_Object *o, Evas_Coord x, Evas_Coord y)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   evas_object_move(sp->o_spectrum, x, y);
   evas_object_move(sp->o_event, x, y);
   evas_object_move(sp->o_cursor, x, y);
}

static void
_e_spectrum_smart_resize(Evas_Object *o, Evas_Coord w, Evas_Coord h)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   evas_object_image_fill_set(sp->o_spectrum, 0, 0, w, h);

   evas_object_resize(sp->o_spectrum, w, h);
   evas_object_resize(sp->o_event, w, h);
   evas_object_resize(sp->o_cursor, w, h);
}

static void
_e_spectrum_smart_color_set(Evas_Object *o, int r, int g, int b, int a)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;
   evas_object_color_set(sp->o_spectrum, r, g, b, a);
   evas_object_color_set(sp->o_event, r, g, b, a);
   evas_object_color_set(sp->o_cursor, r, g, b, a);
}

static void
_e_spectrum_smart_clip_set(Evas_Object *o, Evas_Object *o_clip)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;
   evas_object_clip_set(sp->o_spectrum, o_clip);
   evas_object_clip_set(sp->o_event, o_clip);
   evas_object_clip_set(sp->o_cursor, o_clip);
}

static void
_e_spectrum_smart_clip_unset(Evas_Object *o)
{
   E_Spectrum *sp;

   sp = evas_object_smart_data_get(o);
   if (!sp) return;
   evas_object_clip_unset(sp->o_spectrum);
   evas_object_clip_unset(sp->o_event);
   evas_object_clip_unset(sp->o_cursor);
}

static void
_e_spectrum_smart_init(void)
{
   if (_e_spectrum_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         "e_spectrum",
         EVAS_SMART_CLASS_VERSION,
         _e_spectrum_smart_add,
         _e_spectrum_smart_del,
         _e_spectrum_smart_move,
         _e_spectrum_smart_resize,
         _e_spectrum_smart_show,
         _e_spectrum_smart_hide,
         _e_spectrum_smart_color_set,
         _e_spectrum_smart_clip_set,
         _e_spectrum_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_spectrum_smart = evas_smart_class_new(&sc);
   }
}

void
_e_spectrum_color_calc(E_Spectrum *sp, float vx, float vy, float vz, int *r, int *g, int *b)
{
   switch (sp->mode)
     {
      case E_COLOR_COMPONENT_R:
        *r = 255 * vz;
        *g = 255 * vy;
        *b = 255 * vx;
        break;

      case E_COLOR_COMPONENT_G:
        *r = 255 * vx;
        *g = 255 * vz;
        *b = 255 * vy;
        break;

      case E_COLOR_COMPONENT_B:
        *r = 255 * vy;
        *g = 255 * vx;
        *b = 255 * vz;
        break;

      case E_COLOR_COMPONENT_H:
        evas_color_hsv_to_rgb(vz * 360.0, vy, vx, r, g, b);
        break;

      case E_COLOR_COMPONENT_S:
        evas_color_hsv_to_rgb(vx * 360.0, vz, vy, r, g, b);
        break;

      case E_COLOR_COMPONENT_V:
        evas_color_hsv_to_rgb(vy * 360.0, vx, vz, r, g, b);
        break;

      default:
        break;
     }
}

void
_e_spectrum_2d_color_at(E_Spectrum *sp, int x, int y, int *r, int *g, int *b)
{
   int rr, gg, bb;
   float h, s, v;

   if (!sp || !sp->cv) return;

   switch (sp->mode)
     {
      case E_COLOR_COMPONENT_R:
        rr = sp->cv->r;
        gg = (1 - (y / (double)(sp->ih))) * 255;
        bb = (x / (double)(sp->iw)) * 255;
        break;

      case E_COLOR_COMPONENT_G:
        rr = (x / (double)(sp->iw)) * 255;
        gg = sp->cv->g;
        bb = (1 - (y / (double)(sp->ih))) * 255;
        break;

      case E_COLOR_COMPONENT_B:
        rr = (1 - (y / (double)(sp->ih))) * 255;
        gg = (x / (double)(sp->iw)) * 255;
        bb = sp->cv->b;
        break;

      case E_COLOR_COMPONENT_H:
        h = sp->cv->h;
        s = 1 - (y / (double)(sp->ih));
        v = x / (double)(sp->iw);
        evas_color_hsv_to_rgb(h, s, v, &rr, &gg, &bb);
        break;

      case E_COLOR_COMPONENT_S:
        s = sp->cv->s;
        v = 1 - (y / (double)(sp->ih));
        h = x / (double)(sp->iw) * 360;
        evas_color_hsv_to_rgb(h, s, v, &rr, &gg, &bb);
        break;

      case E_COLOR_COMPONENT_V:
        v = sp->cv->v;
        h = (1 - (y / (double)(sp->ih))) * 360;
        s = x / (double)(sp->iw);
        evas_color_hsv_to_rgb(h, s, v, &rr, &gg, &bb);
        break;

      case E_COLOR_COMPONENT_MAX:
      default:
        return;
     }

   if (r) *r = rr;
   if (g) *g = gg;
   if (b) *b = bb;
}

static Eina_Bool
_e_spectrum_redraw(void *d)
{
   E_Spectrum *sp = d;
   int *data;
   int i, j;
   int r, g, b;
   float vx, vy, vz = 0;

   data = evas_object_image_data_get(sp->o_spectrum, 1);
   if (!data)
     {
        sp->draw_timer = NULL;
        return ECORE_CALLBACK_CANCEL;
     }

   switch (sp->mode)
     {
      case E_COLOR_COMPONENT_R:
        vz = (float)sp->cv->r / 255;
        break;

      case E_COLOR_COMPONENT_G:
        vz = (float)sp->cv->g / 255;
        break;

      case E_COLOR_COMPONENT_B:
        vz = (float)sp->cv->b / 255;
        break;

      case E_COLOR_COMPONENT_H:
        vz = sp->cv->h / 360;
        break;

      case E_COLOR_COMPONENT_S:
        vz = sp->cv->s;
        break;

      case E_COLOR_COMPONENT_V:
        vz = sp->cv->v;
        break;

      case E_COLOR_COMPONENT_MAX:
        break;
     }

   for (i = 0; i < sp->ih; i++)
     {
        vy = (float)i / sp->ih;
        for (j = 0; j < sp->iw; j++)
          {
             vx = (float)j / sp->iw;
             _e_spectrum_color_calc(sp, vx, vy, vz, &r, &g, &b);
             data[(i * sp->iw) + j] =
               (sp->cv->a << 24) |
               (((r * sp->cv->a) / 255) << 16) |
               (((g * sp->cv->a) / 255) << 8) |
               (((b * sp->cv->a) / 255));
          }
     }

   evas_object_image_data_set(sp->o_spectrum, data);
   evas_object_image_data_update_add(sp->o_spectrum, 0, 0, sp->iw, sp->ih);
   sp->draw_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_spectrum_update(E_Spectrum *sp)
{
   if (!sp || !sp->cv) return;

   if (sp->draw_timer) ecore_timer_del(sp->draw_timer);
   sp->draw_timer = ecore_timer_loop_add(.001, _e_spectrum_redraw, sp);
}

Evas_Object *
e_spectrum_add(Evas *e)
{
   _e_spectrum_smart_init();
   return evas_object_smart_add(e, _e_spectrum_smart);
}

void
e_spectrum_mode_set(Evas_Object *o, E_Color_Component mode)
{
   E_Spectrum *sp;

   if (evas_object_smart_smart_get(o) != _e_spectrum_smart) SMARTERRNR();
   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   if (sp->mode == mode) return;
   sp->mode = mode;
   _e_spectrum_update(sp);
}

E_Color_Component
e_spectrum_mode_get(Evas_Object *o)
{
   E_Spectrum *sp;

   if (evas_object_smart_smart_get(o) != _e_spectrum_smart) SMARTERR(0);
   sp = evas_object_smart_data_get(o);
   if (!sp) return -1;

   return sp->mode;
}

void
e_spectrum_color_value_set(Evas_Object *o, E_Color *cv)
{
   E_Spectrum *sp;

   if (evas_object_smart_smart_get(o) != _e_spectrum_smart) SMARTERRNR();
   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   sp->cv = cv;
   _e_spectrum_update(sp);
}

void
e_spectrum_update(Evas_Object *o)
{
   E_Spectrum *sp;

   if (evas_object_smart_smart_get(o) != _e_spectrum_smart) SMARTERRNR();
   sp = evas_object_smart_data_get(o);
   if (!sp) return;

   _e_spectrum_update(sp);
}

#include "config.h"
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#ifdef HAVE_PRCTL
# include <sys/prctl.h>
#elif defined(HAVE_PROCCTL)
# include <sys/procctl.h>
#endif

/* the ptrace interface used here is really linux specific -
 * FreeBSD, NetBSD and Mac OS X use slightly different ptrace API that should
 * still be feasible to use (but not compatible), OpenBSD uses a really old
 * version of the FreeBSD API that lacks required functionality we need, and
 * Solaris doesn't have ptrace at all
 */
#ifdef HAVE_SYS_PTRACE_H
# ifdef __linux__
#  include <sys/ptrace.h>
# else
#  undef HAVE_SYS_PTRACE_H
# endif
#endif

#include <fcntl.h>
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif
#include <signal.h>
#include <errno.h>

# if !defined (__FreeBSD__) && !defined (__OpenBSD__)
#  ifdef HAVE_MALLOC_H
#   include <malloc.h>
#  endif
# endif

#include <Eina.h>

#define myasprintf(__b, __fmt, args...) do { \
   char __bb[sizeof(__fmt) + 1]; \
   int __cnt = snprintf(__bb, sizeof(__bb), __fmt, ##args); \
   if (__cnt >= 0) { \
      *(__b) = alloca(__cnt + 1); \
      snprintf(*(__b), __cnt + 1, __fmt, ##args); \
   } \
} while (0)

#ifdef E_API
# undef E_API
#endif
#ifdef WIN32
# ifdef BUILDING_DLL
#  define E_API __declspec(dllexport)
# else
#  define E_API __declspec(dllimport)
# endif
#else
# ifdef __GNUC__
#  if __GNUC__ >= 4
/* BROKEN in gcc 4 on amd64 */
#   if 0
#    pragma GCC visibility push(hidden)
#   endif
#   define E_API __attribute__ ((visibility("default")))
#  else
#   define E_API
#  endif
# else
#  define E_API
# endif
#endif

static Eina_Bool stop_ptrace = EINA_FALSE;

static void  env_set(const char *var, const char *val);
E_API int    prefix_determine(char *argv0);

static void
env_set(const char *var, const char *val)
{
   if (val)
     {
#ifdef HAVE_SETENV
        setenv(var, val, 1);
#else
        char *buf;
        size_t size = strlen(var) + 1 + strlen(val) + 1;

        // yes - this does leak. we know. intentional if we set the same
        // env multiple times because we likely won't and alloca is wrong
        // as we lose the env content on the stack. but becomes part of
        // the environment so it has to stay around after putenv is called
        buf = malloc(size);
        sprintf(buf, "%s=%s", var, val);
        putenv(buf);
#endif
     }
   else
     {
#ifdef HAVE_UNSETENV
        unsetenv(var);
#else
        if (getenv(var)) putenv(var);
#endif
     }
}

/* local subsystem globals */
static Eina_Prefix *pfx = NULL;

/* externally accessible functions */
E_API int
prefix_determine(char *argv0)
{
   pfx = eina_prefix_new(argv0, prefix_determine,
                         "E", "enlightenment", "AUTHORS",
                         PACKAGE_BIN_DIR, PACKAGE_LIB_DIR,
                         PACKAGE_DATA_DIR, LOCALE_DIR);
   if (!pfx) return 0;
   return 1;
}

static void
copy_args(char **dst, char **src, size_t count)
{
   for (; count > 0; count--, dst++, src++) *dst = *src;
}

static void
_env_path_prepend(const char *env, const char *path)
{
   char *p, *s;
   const char *p2;
   int len = 0, len2 = 0;

   if (!path) return;

   p = getenv(env);
   p2 = path;
   len2 = strlen(p2);
   if (p)
     {
        len = strlen(p);
        // path already there at the start. don't prepend. :)
        if ((!strcmp(p, p2)) ||
            ((len > len2) &&
             (!strncmp(p, p2, len2)) &&
             (p[len2] == ':')))
          return;
     }
   s = malloc(len + 1 + len2 + 1);
   if (s)
     {
        s[0] = 0;
        strcat(s, p2);
        if (p)
          {
             if (p[0] != ':') strcat(s, ":");
             strcat(s, p);
          }
        env_set(env, s);
        free(s);
     }
}

static void
_env_path_append(const char *env, const char *path)
{
   char *p, *s;
   const char *p2;
   int len = 0, len2 = 0;

   if (!path) return;

   p = getenv(env);
   p2 = path;
   len2 = strlen(p2);
   if (p)
     {
        len = strlen(p);
        // path already there at the end. don't append. :)
        if ((!strcmp(p, p2)) ||
            ((len > len2) &&
             (!strcmp((p + len - len2), p2)) &&
             (p[len - len2 - 1] == ':')))
          return;
     }
   s = malloc(len + 1 + len2 + 1);
   if (s)
     {
        s[0] = 0;
        if (p)
          {
             strcat(s, p);
             if (len > 0)
               {
                  if (s[len - 1] != ':') strcat(s, ":");
               }
          }
        strcat(s, p2);
        env_set(env, s);
        free(s);
     }
}

static void
_sigusr1(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   struct sigaction action;

   // release ptrace
   stop_ptrace = EINA_TRUE;

   action.sa_sigaction = _sigusr1;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGUSR1, &action, NULL);
}

static void
_sigusr2(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   struct sigaction action;

   putenv("E_DESKLOCK_LOCKED=locked");

   action.sa_sigaction = _sigusr2;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGUSR2, &action, NULL);
}

static void
_sighup(int x EINA_UNUSED, siginfo_t *info EINA_UNUSED, void *data EINA_UNUSED)
{
   struct sigaction action;

   putenv("E_DESKLOCK_LOCKED=no");

   action.sa_sigaction = _sighup;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGHUP, &action, NULL);
}

static void
_print_usage(const char *hstr)
{
   printf("Please run:\n"
          "\tenlightenment %s\n"
          "\tfor more options.\n",
          hstr);
   exit(0);
}

static Eina_Bool
_sig_continue(siginfo_t sig)
{
   return ((sig.si_signo != SIGSEGV) &&
           (sig.si_signo != SIGFPE) &&
//         (sig.si_signo != SIGBUS) &&
           (sig.si_signo != SIGABRT));
}

static void
_sig_remember(siginfo_t sig, Eina_Bool *susr1, Eina_Bool *sill)
{
   if (sig.si_signo == SIGUSR1)
     {
        if (*sill) *susr1 = EINA_TRUE;
     }
   else *sill = (sig.si_signo == SIGILL);
}

static int
_e_ptrace_attach(int child, int *status, Eina_Bool really_know)
{
   int result = 0;

#ifdef HAVE_SYS_PTRACE_H
   if (really_know) return waitpid(child, status, 0);

   ptrace(PT_ATTACH, child, NULL, 0);
   result = waitpid(child, status, 0);

   if (!stop_ptrace && WIFSTOPPED(*status)) ptrace(PT_CONTINUE, child, NULL, 0);
#else
   (void)child;
   (void)really_know;
   (void)status;
#endif

   return result;
}

static void
_e_ptrace_detach(int child, int back, Eina_Bool really_know)
{
#ifdef HAVE_SYS_PTRACE_H
   if (!really_know) ptrace(PT_DETACH, child, NULL, back);
#else
   (void)child;
   (void)back;
   (void)really_know;
#endif
}

static void
_e_ptrace_traceme(Eina_Bool really_know)
{
#ifdef HAVE_SYS_PTRACE_H
   if (!really_know) ptrace(PT_TRACE_ME, 0, NULL, 0);
#else
   (void)really_know;
#endif
}

static int
_e_ptrace_getsiginfo(int child, siginfo_t *sig, Eina_Bool really_know)
{
   memset(sig, 0, sizeof(siginfo_t));
#ifdef HAVE_SYS_PTRACE_H
   if (!really_know) return ptrace(PT_GETSIGINFO, child, NULL, sig);
#else
   (void)child;
   (void)sig;
   (void)really_know;
#endif
   return 0;
}

static void
_e_ptrace_continue(int child, int back, Eina_Bool really_know)
{
#ifdef HAVE_SYS_PTRACE_H
   if (!really_know) ptrace(PT_CONTINUE, child, NULL, back);
#else
   (void)child;
   (void)back;
   (void)really_know;
#endif
}

static void
_e_start_stdout_err_redir(const char *home)
{
   int logfd;
   char *logf = NULL, *logf_old = NULL;

   // rename old olg file
   myasprintf(&logf, "%s/.e-log.log", home);
   myasprintf(&logf_old, "%s/.e-log.log.old", home);
   rename(logf, logf_old);
   // open new log file and move stdout/err to it
   logfd = open(logf, O_WRONLY | O_CREAT | O_TRUNC, 0600);
   printf("Enlightenment: See logs in: %s\n", logf);
   if (logfd < 0) return;
   dup2(logfd, 1); // stdout to file
   dup2(logfd, 2); // stderr to file
}

static int
_e_start_child(const char *home, char **args, Eina_Bool really_know)
{
   // have e process die with parent enlightenment_start
#ifdef HAVE_PRCTL
   prctl(PR_SET_PDEATHSIG, SIGTERM);
#elif defined(HAVE_PROCCTL)
   int sig = SIGTERM;
   procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sig);
#endif
   _e_start_stdout_err_redir(home);
   _e_ptrace_traceme(really_know);
   execv(args[0], args);
   // We failed, 0 means normal exit from E with no restart or crash so
   // let's exit
   return 0;
}

static Eina_Bool
_e_ptrace_kernel_check(const char *home)
{
#ifdef __linux__
   // Check if patch to prevent ptrace to another process is present
   // in the kernel
   Eina_Bool ret = EINA_FALSE;
   int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY);
   if (fd != -1)
     {
        char c;
        ret = read(fd, &c, (sizeof(c)) == sizeof(c)) && (c != '0');
        close(fd);
     }
   if (ret)
     {
        char *buf = NULL;
        FILE *f;

        myasprintf(&buf, "%s/.e-crashdump.txt", home);
        f = fopen(buf, "a");
        if (f)
          {
             fprintf(f,
                     "ERROR: /proc/sys/kernel/yama/ptrace_scope is 1 disallowing remote\n"
                     "attachment to a process. This means a gdb backtrace cannot be logged.\n"
                     "To fix this, as root please do:\n"
                     "  echo 0 > /proc/sys/kernel/yama/ptrace_scope\n");
             fclose(f);
          }
     }
   return ret;
#else
   return EINA_FALSE;
#endif
}

static int
_e_call_gdb(int child, const char *home, char **backtrace_str)
{
   int r = 0;
   char *buf = NULL;
   /* call e_sys gdb */
   myasprintf(&buf,
              "gdb "
              "--pid=%i "
              "-batch "
              "-ex 'set logging file %s/.e-crashdump.txt' "
              "-ex 'set logging on' "
              "-ex 'thread apply all backtrace full' "
              "-ex detach > /dev/null 2>&1 < /dev/zero",
              child,
              home);
   r = system(buf);

   fprintf(stderr, "called gdb with '%s' = %i\n", buf, WEXITSTATUS(r));
   myasprintf(&buf, "%s/.e-crashdump.txt", home);
   *backtrace_str = strdup(buf);
   return WEXITSTATUS(r);
}

static int
_e_call_alert(int child, siginfo_t sig, int exit_gdb, const char *backtrace_str,
              Eina_Bool susr1)
{
   char *buf = NULL;

   myasprintf(&buf,
              backtrace_str ?
              "%s/enlightenment/utils/enlightenment_alert %i %i %i '%s'" :
              "%s/enlightenment/utils/enlightenment_alert %i %i %i",
              eina_prefix_lib_get(pfx),
              (sig.si_signo == SIGSEGV && susr1) ? SIGILL : sig.si_signo,
              child,
              exit_gdb,
              backtrace_str);
   return system(buf);
}

static int
path_contains(const char *path)
{
   char *realp, *realp2, *env2 = NULL, *p, *p2;
   char buf[PATH_MAX], buf2[PATH_MAX];
   const char *env;
   ssize_t p_len;
   int ret = 0;

   if (!path) return ret;
   realp = realpath(path, buf);
   if (!realp) realp = (char *)path;

   env = getenv("PATH");
   if (!env) goto done;
   env2 = strdup(env);
   if (!env2) goto done;

   p = env2;
   while (p)
     {
        p2 = strchr(p, ':');

        if (p2) p_len = p2 - p;
        else p_len = strlen(p);

        if (p_len <= 0) goto next;
        if (p2) *p2 = 0;
        realp2 = realpath(p, buf2);
        if (realp2)
          {
             if (!strcmp(realp, realp2)) goto ok;
          }
        else
          {
             if (!strcmp(realp, p)) goto ok;
          }
next:
        if (p2) p = p2 + 1;
        else break;
     }
   // failed to find
   goto done;
ok:
   ret = 1;
done:
   free(env2);
   return ret;
}

int
main(int argc, char **argv)
{
   int i, ret = -1;
   char *buf = NULL, *buf2 = NULL, *buf3 = NULL, **args, *home;
   const char *bindir;
   Eina_Bool really_know = EINA_FALSE;
   struct sigaction action;
   pid_t child = -1;
   Eina_Bool restart = EINA_TRUE;

   unsetenv("NOTIFY_SOCKET");

   /* Setup USR1 to detach from the child process and let it get gdb by advanced users */
   action.sa_sigaction = _sigusr1;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGUSR1, &action, NULL);

   /* Setup USR2 to go into "lock down" mode */
   action.sa_sigaction = _sigusr2;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGUSR2, &action, NULL);

   /* Setup HUP to go exit "lock down" mode */
   action.sa_sigaction = _sighup;
   action.sa_flags = SA_RESETHAND;
   sigemptyset(&action.sa_mask);
   sigaction(SIGHUP, &action, NULL);

   eina_init();

   /* reexcute myself with dbus-launch if dbus-launch is not running yet */
   if ((!getenv("DBUS_SESSION_BUS_ADDRESS")) &&
       (!getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")))
     {
        char **dbus_argv;

        dbus_argv = alloca((argc + 3) * sizeof (char *));
        dbus_argv[0] = "dbus-launch";
        dbus_argv[1] = "--exit-with-session";
        copy_args(dbus_argv + 2, argv, argc);
        dbus_argv[2 + argc] = NULL;
        execvp("dbus-launch", dbus_argv);
     }

   prefix_determine(argv[0]);

   env_set("E_START", argv[0]);
   putenv("E_START_MANAGER=1");

   if ((!getenv("DISPLAY")) && (!getenv("E_WL_FORCE")))
     {
        printf("***************************************************************\n");
        printf("*  You are probably starting Enlightenment in wayland DRM/KMS *\n");
        printf("***************************************************************\n");
        printf("                                                               \n");
        printf("  Wayland support is experimental. It may work for you.        \n");
        printf("  It may not. If you wish you help out then please do, but     \n");
        printf("  for a proper experience use Enlightenment in X11.            \n");
        printf("                                                               \n");
        printf("  If you do not want this pause and message, please set        \n");
        printf("  the following environment variable:                          \n");
        printf("                                                               \n");
        printf("    E_WL_FORCE=drm                                             \n");
        printf("                                                               \n");
        printf("  Example:                                                     \n");
        printf("                                                               \n");
        printf("    E_WL_FORCE=drm enlightenment_start                         \n");
        printf("                                                               \n");
        sleep(10);
     }
   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-h")) || (!strcmp(argv[i], "-help")) ||
            (!strcmp(argv[i], "--help")))
          {
             _print_usage(argv[i]);
          }
        else if (!strcmp(argv[i], "-display"))
          {
             i++;
             env_set("DISPLAY", argv[i]);
          }
        else if (!strcmp(argv[i], "-i-really-know-what-i-am-doing-and-accept"
                                  "-full-responsibility-for-it"))
          {
             really_know = EINA_TRUE;
          }
     }

   bindir = eina_prefix_bin_get(pfx);
   if (!path_contains(bindir))
     {
        if (really_know) _env_path_append("PATH", bindir);
        else _env_path_prepend("PATH", bindir);
     }
   myasprintf(&buf2,
              "E_ALERT_FONT_DIR=%s/data/fonts",
              eina_prefix_data_get(pfx));
   putenv(buf2);
   myasprintf(&buf3,
              "E_ALERT_SYSTEM_BIN=%s/enlightenment/utils/enlightenment_system",
              eina_prefix_lib_get(pfx));
   putenv(buf3);

   home = getenv("HOME");
   if (home)
     {
        const char *tmps = getenv("XDG_DATA_HOME");

        if (tmps)        myasprintf(&buf, "%s/Applications/.bin", tmps);
        else             myasprintf(&buf, "%s/Applications/.bin", home);
        if (really_know) _env_path_append("PATH", buf);
        else             _env_path_prepend("PATH", buf);
     }

   /* run e directly now */
   myasprintf(&buf, "%s/enlightenment", eina_prefix_bin_get(pfx));

   args = alloca((argc + 1) * sizeof(char *));
   printf("Enlightenment: Command: %s\n", buf);
   args[0] = buf;
   copy_args(&args[1], argv + 1, argc - 1);
   args[argc] = NULL;

   /* Now looping until */
   while (restart)
     {
        pid_t result;
        int status, back;
        Eina_Bool done = EINA_FALSE;
        Eina_Bool remember_sigill = EINA_FALSE;
        Eina_Bool remember_sigusr1 = EINA_FALSE;

        stop_ptrace = EINA_FALSE;

        child = fork();

        if (child < 0)
          {
             ret = -1;
             break;
          }
        else if (child == 0)
          { // we are in the child fork - so exec
             ret = _e_start_child(home, args, really_know);
             break;
          }

        putenv("E_RESTART_OK=");
        /* in the parent - ptrace attach and continue */
        putenv("E_RESTART=1");
        _e_ptrace_attach(child, &status, really_know);

        /* now loop until done */
not_done:
#ifdef HAVE_MALLOC_TRIM
        malloc_trim(0);
#endif
        result = waitpid(child, &status, WNOHANG);
        /* Wait for E */
        if (!result) result = waitpid(-1, &status, 0);

        if (result == child)
          {
             if (WIFSTOPPED(status) && (!stop_ptrace))
               {
                  char *backtrace_str = NULL;

                  siginfo_t sig;
                  int r = _e_ptrace_getsiginfo(child, &sig,
                                               really_know);

                  back = ((r == 0) && (sig.si_signo != SIGTRAP))
                          ? sig.si_signo : 0;

                  _sig_remember(sig, &remember_sigusr1, &remember_sigill);

                  if ((r != 0) || (_sig_continue(sig)))
                    {
                       _e_ptrace_continue(child, back, really_know);
                       goto not_done;
                    }
                  _e_ptrace_detach(child, back, really_know);
                  usleep(200000);

                  /* And call gdb if available */
                  if (home && !_e_ptrace_kernel_check(home))
                    r = _e_call_gdb(child, home, &backtrace_str);
                  else
                    r = 0;

                  /* kill e */
                  if (!getenv("DISPLAY"))
                    {
                       kill(child, SIGKILL);
                       usleep(500000);
                    }

                  /* call e_alert */
                  r = _e_call_alert(child, sig, r, backtrace_str,
                                    remember_sigusr1);
                  free(backtrace_str);

                  if (getenv("DISPLAY")) kill(child, SIGKILL);
                  if (WEXITSTATUS(r) == 101) restart = EINA_FALSE;
                  done = EINA_TRUE;
               }
             else if (!WIFEXITED(status) || (stop_ptrace))
               {
                  restart = EINA_TRUE;
                  done = EINA_TRUE;
               }
             else if (WEXITSTATUS(status) == 121)
               {
                  putenv("E_RESTART_OK=1");
                  restart = EINA_TRUE;
                  done = EINA_TRUE;
               }
             else if (WEXITSTATUS(status) == 111)
               {
                  putenv("E_RESTART_OK=1");
                  restart = EINA_TRUE;
                  done = EINA_TRUE;
               }
             else if (WEXITSTATUS(status) == 101)
               {
                  printf("Explicit error exit from enlightenment\n");
                  restart = EINA_FALSE;
                  done = EINA_TRUE;
               }
             else if (WEXITSTATUS(status) == 0)
               {
                  restart = EINA_FALSE;
                  done = EINA_TRUE;
               }
             else
               {
                  printf("Invalid exit from enlightenment: code=%i\n", WEXITSTATUS(status));
                  restart = EINA_TRUE;
                  done = EINA_TRUE;
               }
          }
        else if (result == -1)
          {
             if (errno != EINTR)
               {
                  done = EINA_TRUE;
                  restart = EINA_FALSE;
               }
             else if (stop_ptrace)
               {
                  kill(child, SIGSTOP);
                  usleep(200000);
                  _e_ptrace_detach(child, 0, really_know);
               }
          }
        if (!done) goto not_done;
     }
   return ret;
}

#include "e.h"

/* TODO:
 * - Need some kind of "wait for exit" system, maybe register with
 *   e_config? startup and restart apps could also be in e_config
 */

/* local subsystem functions */
static void      _e_startup(void);
static void      _e_startup_next_cb(void *data);
static Eina_Bool _e_startup_event_cb(void *data, int ev_type, void *ev);
static Eina_Bool _e_startup_time_exceeded(void *data);

/* local subsystem globals */
static E_Order *startup_apps = NULL;
static int start_app_pos = -1;
static Ecore_Event_Handler *desktop_cache_update_handler = NULL;
static Ecore_Timer *timer = NULL;
static Eina_Bool desktop_cache_update = EINA_FALSE;
static Eina_Bool started = EINA_FALSE;
static E_Startup_Mode startup_mode = 0;

/* externally accessible functions */


E_API void
e_startup_mode_set(E_Startup_Mode mode)
{
   startup_mode = mode;
   desktop_cache_update_handler =
     ecore_event_handler_add(EFREET_EVENT_DESKTOP_CACHE_BUILD,
                             _e_startup_event_cb, NULL);
   if (timer) ecore_timer_del(timer);
   timer = ecore_timer_add(20.0, _e_startup_time_exceeded, NULL);
   e_init_undone();
}

E_API void
e_startup(void)
{
   if (!desktop_cache_update)
     started = EINA_TRUE;
   else
     _e_startup();
}

/* local subsystem functions */
static Eina_Bool
_e_startup_delay(void *data)
{
   Efreet_Desktop *desktop = data;
   e_exec(NULL, desktop, NULL, NULL, NULL);
   efreet_desktop_unref(desktop);
   return EINA_FALSE;
}

static void
_e_startup(void)
{
   Efreet_Desktop *desktop;
   const char *s;
   double delay = 0.0;

   if (!startup_apps)
     {
        e_init_done();
        return;
     }
   desktop = eina_list_nth(startup_apps->desktops, start_app_pos);
   start_app_pos++;
   if (!desktop)
     {
        e_object_del(E_OBJECT(startup_apps));
        startup_apps = NULL;
        start_app_pos = -1;
        e_init_done();
        return;
     }
   if (desktop->x)
     {
        s = eina_hash_find(desktop->x, "X-GNOME-Autostart-Delay");
        if (s) delay = eina_convert_strtod_c(s, NULL);
     }
   if (delay > 0.0)
     {
        efreet_desktop_ref(desktop);
        ecore_timer_add(delay, _e_startup_delay, desktop);
     }
   else e_exec(NULL, desktop, NULL, NULL, NULL);
   ecore_job_add(_e_startup_next_cb, NULL);
}

static void
_e_startup_next_cb(void *data EINA_UNUSED)
{
   _e_startup();
}

static void
_e_startup_error_dialog(const char *msg)
{
   E_Dialog *dia;

   dia = e_dialog_new(NULL, "E", "_startup_error_dialog");
   EINA_SAFETY_ON_NULL_RETURN(dia);

   e_dialog_title_set(dia, "ERROR!");
   e_dialog_icon_set(dia, "enlightenment", 64);
   e_dialog_text_set(dia, msg);
   e_dialog_button_add(dia, _("Close"), NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_win_no_remember_set(dia->win, 1);
   e_dialog_show(dia);
}

static E_Order *
_e_startup_load(const char *file)
{
   E_Order *o = e_order_new(file);
   if (!o) return NULL;
   if (!o->desktops)
     {
        e_object_del(E_OBJECT(o));
        o = NULL;
     }
   return o;
}

static Eina_Bool
_e_startup_event_cb(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *ev)
{
   char buf[PATH_MAX];
   Efreet_Event_Cache_Update *e;

   if (timer) ecore_timer_del(timer);
   timer = NULL;

   e = ev;
   if ((e) && (e->error))
     {
        fprintf(stderr, "E: efreet couldn't build cache\n");
        _e_startup_error_dialog("E: Efreetd cannot be connected to.<br>"
                                "Please check:<br>"
                                "$XDG_RUTIME_DIR/.ecore/efreetd/0<br>"
                                "or<br>"
                                "~/.ecore/efreetd/0<br>"
                                "Is your system very slow on start so<br>"
                                "efreetd cannot be connected to within<br>"
                                "0.5sec after launch??");
     }
   desktop_cache_update = EINA_TRUE;
   E_FREE_FUNC(desktop_cache_update_handler, ecore_event_handler_del);

   if (startup_mode == E_STARTUP_START)
     {
        e_user_dir_concat_static(buf, "applications/startup/.order");
        startup_apps = _e_startup_load(buf);
        if (!startup_apps)
          {
             e_prefix_data_concat_static(buf, "data/applications/startup/.order");
             startup_apps = e_order_new(buf);
          }
     }
   else if (startup_mode == E_STARTUP_RESTART)
     {
        e_user_dir_concat_static(buf, "applications/restart/.order");
        startup_apps = _e_startup_load(buf);
        if (!startup_apps)
          {
             e_prefix_data_concat_static(buf, "data/applications/restart/.order");
             startup_apps = e_order_new(buf);
          }
     }
   if (startup_apps) start_app_pos = 0;
   if (started) _e_startup();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_startup_time_exceeded(void *data EINA_UNUSED)
{
   fprintf(stderr, "E: efreet didn't notify about cache update\n");
   _e_startup_error_dialog("E: Efreet did not update cache.<br>"
                           "Please check your Efreet setup.<br>"
                           "Is efreetd running?<br>"
                           "Can ~/.cache/efreet be written to?");
   timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}
# ifdef HAVE_CONFIG_H
#  include "config.h"
# endif

#include <Eet.h>

typedef struct _E_Static_Grab        E_Static_Grab;
typedef struct _E_Static_Grab_Module E_Static_Grab_Module;

struct _E_Static_Grab
{
   struct
   {
      int         core_count;
      int         thread_count;
      int         bogo;
      int         model;
      int         family;
      int         stepping;
      int         cache_size;
      int         addr_space;

      const char *vendor;
      const char *model_name;

      Eina_Bool   fpu;
   } cpu;

   struct
   {
      const char *name;
      const char *release;
      const char *os;
      const char *kernel_release;
   } distribution;

   struct
   {
      struct
      {
         const char *vendor;
         const char *renderer;
         const char *version;
      } gpu;

      const char *name;
      const char *release_date;
      const char *build_date;

      Eina_List  *modules;
   } x;
};

struct _E_Static_Grab_Module
{
   const char *name;
   const char *vendor;
   const char *compiled_for;
   const char *version;
   const char *class;
   const char *ABI_class;
   const char *ABI_version;
};

/* For /proc/cpuinfo */
#define CPU_CORES     "cpu cores"
#define CACHE_SIZE    "cache size"
#define CPU_FAMILY    "cpu family"
#define STEPPING      "stepping"
#define SIBLINGS      "siblings"
#define BOGOMIPS      "bogomips"
#define MODEL         "model"
#define MODEL_NAME    "model name"
#define VENDOR_ID     "vendor_id"
#define FPU           "fpu"

/* For /var/log/Xorg.0.log */
#define RELEASE_DATE  "Release Date"
#define BUILD_DATE    "Build Date"
#define LOAD_MODULE   "(II) LoadModule"
#define UNLOAD_MODULE "(II) UnloadModule"
#define MODULE_OF     "(II) Module %s"
#define LOADING       "(II) Loading"
#define VENDOR        "vendor=\""
#define COMPILED      "compiled for "
#define MODULE_CLASS  "Module class"
#define ABI_CLASS     "ABI class"
#undef VERSION
#define VERSION       ", version "

static const char *
_e_static_grab_name(const char *line, const char *end, const char *name)
{
   size_t l;

   l = strlen(name);
   if (line + l + 3 > end) return end;
   if (strncmp(name, line, l)) return end;

   return line + l;
}

static Eina_Bool
_e_static_grab_int(const char *line, const char *end, const char *txt, int *v)
{
   static const char *value = "0123456789";
   const char *over;
   int r = 0;

   over = _e_static_grab_name(line, end, txt);
   while (over < end)
     {
        if (strchr(value, *over)) break;
        over++;
     }
   if (over == end) return EINA_FALSE;

   while (over < end)
     {
        const char *offset = strchr(value, *over);

        if (!offset) break;
        r = r * 10 + (offset - value);

        over++;
     }

   *v = r;

   return EINA_TRUE;
}

static Eina_Bool
_e_static_grab_string(const char *line, const char *end, const char *txt, const char **s)
{
   static const char *value = " :\t";
   const char *over;

   over = _e_static_grab_name(line, end, txt);
   while (over < end)
     {
        if (!strchr(value, *over)) break;
        over++;
     }
   if (over == end) return EINA_FALSE;

   if (*over == '"' && *(end - 2) == '"')
     {
        over++;
        end--;
     }

   *s = eina_stringshare_add_length(over, end - over - 1);
   return EINA_TRUE;
}

static const char *
_e_static_grab_discard(const char *current, Eina_File_Line *line, int begin, int end)
{
   while (current < line->end &&
          strchr(" \t", *current))
     current++;

   if (*current != begin) return current;

   while (current < line->end &&
          *current != end)
     current++;

   if (current == line->end) return current;

   for (current++;
        current < line->end && strchr(" \t", *current);
        current++)
     ;

   return current;
}

static void
_e_static_grab_cpu(E_Static_Grab *grab)
{
   char buf[4096];
   FILE *f;
   const char *fpu = NULL;
   int siblings = 0;

   grab->cpu.addr_space = sizeof (void *);

   f = fopen("/proc/cpuinfo", "r");
   if (!f) return;

   while (!feof(f))
     {
        char *end;
        size_t length = 0;

        if (!fgets(buf, sizeof (buf), f)) break;
        length = strlen(buf);
        end = buf + length;

        if (length < 3) continue;

        switch (*buf)
          {
           case 'c':
             if (_e_static_grab_int(buf, end, CPU_CORES, &grab->cpu.core_count)) break;
             if (_e_static_grab_int(buf, end, CACHE_SIZE, &grab->cpu.cache_size)) break;
             _e_static_grab_int(buf, end, CPU_FAMILY, &grab->cpu.family);
             break;

           case 's':
             if (_e_static_grab_int(buf, end, STEPPING, &grab->cpu.stepping)) break;
             _e_static_grab_int(buf, end, SIBLINGS, &siblings);
             break;

           case 'b':
             _e_static_grab_int(buf, end, BOGOMIPS, &grab->cpu.bogo);
             break;

           case 'm':
             if (_e_static_grab_string(buf, end, MODEL_NAME, &grab->cpu.model_name)) break;
             _e_static_grab_int(buf, end, MODEL, &grab->cpu.model);
             break;

           case 'v':
             _e_static_grab_string(buf, end, VENDOR_ID, &grab->cpu.vendor);
             break;

           case 'f':
             _e_static_grab_string(buf, end, FPU, &fpu);
             break;
          }
     }
   fclose(f);

   if (siblings) grab->cpu.thread_count = siblings / grab->cpu.core_count;
   if (fpu)
     {
        if (!strcmp(fpu, "yes")) grab->cpu.fpu = EINA_TRUE;
        eina_stringshare_del(fpu);
     }
}

static void
_e_static_grab_x(E_Static_Grab *grab)
{
   Eina_Bool in_module = EINA_FALSE;
   E_Static_Grab_Module *module;
   Eina_File_Line *line;
   Eina_Iterator *it;
   Eina_File *f;

   f = eina_file_open("/var/log/Xorg.0.log", EINA_FALSE);
   if (!f) return;

   module = calloc(1, sizeof (E_Static_Grab_Module));
   if (!module)
     {
        eina_file_close(f);
        return;
     }

   it = eina_file_map_lines(f);
   EINA_ITERATOR_FOREACH(it, line)
     {
        const char *current;

        current = _e_static_grab_discard(line->start, line, '[', ']');
        if (current >= line->end) continue;

        if (!grab->x.name)
          {
             if (line->end - current - 1 <= 0) continue;
             grab->x.name = eina_stringshare_add_length(current, line->end - current - 1);
             continue;
          }

        if (!in_module)
          {
             switch (*current)
               {
                case 'R':
                  _e_static_grab_string(current, line->end, RELEASE_DATE, &grab->x.release_date);
                  break;

                case 'B':
                  _e_static_grab_string(current, line->end, BUILD_DATE, &grab->x.build_date);
                  break;

                case '(':
                {
                   E_Static_Grab_Module *md;
                   Eina_List *l;
                   const char *tmp;

                   if (_e_static_grab_string(current, line->end, LOAD_MODULE, &module->name))
                     {
                        EINA_LIST_FOREACH(grab->x.modules, l, md)
                          if (md->name == module->name)
                            {
                               eina_stringshare_del(module->name);
                               module->name = NULL;
                               break;
                            }

                        if (module->name) in_module = EINA_TRUE;
                        break;
                     }
                   else if (_e_static_grab_string(current, line->end, UNLOAD_MODULE, &tmp))
                     {
                        EINA_LIST_FOREACH(grab->x.modules, l, md)
                          if (md->name == tmp)
                            {
                               grab->x.modules = eina_list_remove_list(grab->x.modules, l);

                               eina_stringshare_del(md->name);
                               eina_stringshare_del(md->vendor);
                               eina_stringshare_del(md->compiled_for);
                               eina_stringshare_del(md->version);
                               eina_stringshare_del(md->class);
                               eina_stringshare_del(md->ABI_class);
                               eina_stringshare_del(md->ABI_version);
                               free(md);
                               break;
                            }
                        eina_stringshare_del(tmp);
                     }
                   break;
                }
               }
          }
        else
          {
             char *buffer;
             Eina_Bool found = EINA_FALSE;

             switch (*current)
               {
                case '(':
                {
                   const char *vendor;

                   if (strncmp(current, LOADING, strlen(LOADING)) == 0)
                     {
                        found = EINA_TRUE;
                        break;
                     }

                   buffer = alloca(strlen(MODULE_OF) + strlen(module->name) + 1);
                   sprintf(buffer, MODULE_OF, module->name);

                   if (_e_static_grab_string(current, line->end, buffer, &vendor))
                     {
                        if (strncmp(vendor, VENDOR, strlen(VENDOR)) == 0)
                          {
                             module->vendor = eina_stringshare_add_length(vendor + strlen(VENDOR),
                                                                          eina_stringshare_strlen(vendor) - strlen(VENDOR) - 1);
                             found = EINA_TRUE;
                          }
                        eina_stringshare_del(vendor);
                     }
                   break;
                }

                case 'c':
                  if (strncmp(current, COMPILED, strlen(COMPILED)) == 0)
                    {
                       const char *lookup = current + strlen(COMPILED);

                       while (lookup < line->end && *lookup != ',')
                         lookup++;

                       module->compiled_for = eina_stringshare_add_length(current + strlen(COMPILED),
                                                                          lookup - current - strlen(COMPILED));
                       found = EINA_TRUE;
                       break;
                    }
                  break;

                case 'M':
                  if (_e_static_grab_string(current, line->end, MODULE_CLASS, &module->class))
                    {
                       found = EINA_TRUE;
                       break;
                    }
                  break;

                case 'A':
                {
                   const char *tmp;

                   if (_e_static_grab_string(current, line->end, ABI_CLASS, &tmp))
                     {
                        const char *lookup = strchr(tmp, ',');

                        if (lookup)
                          {
                             module->ABI_class = eina_stringshare_add_length(tmp, lookup - tmp);
                             if (strncmp(lookup, VERSION, strlen(VERSION)) == 0)
                               {
                                  module->ABI_version = eina_stringshare_add_length(lookup + strlen(VERSION),
                                                                                    eina_stringshare_strlen(tmp) - (lookup - tmp + strlen(VERSION) - 1));
                               }
                             eina_stringshare_del(tmp);
                          }
                        else
                          {
                             module->ABI_class = tmp;
                          }
                     }
                   break;
                }
               }

             if (!found)
               {
                  in_module = EINA_FALSE;
                  grab->x.modules = eina_list_append(grab->x.modules, module);
                  module = calloc(1, sizeof (E_Static_Grab_Module));
               }
          }
     }

   free(module);

   eina_iterator_free(it);
   eina_file_close(f);
}

int
main(int argc, char **argv EINA_UNUSED)
{
   E_Static_Grab_Module *module;
   Eina_List *l;
   E_Static_Grab grab;

   if (argc != 2) exit(0);

#if 0
   /* FIXME: Latter push information into an eet file when updated */
   if ((!strcmp(argv[1], "-h")) ||
       (!strcmp(argv[1], "-help")) ||
       (!strcmp(argv[1], "--help")))
     {
        printf("This is an internal tool for Enlightenment.\n"
               "do not use it.\n");
        exit(0);
     }
#endif

   eina_init();
   eet_init();

   memset(&grab, 0, sizeof (grab));
   _e_static_grab_cpu(&grab); /* FIXME: please provide patch for more Unix */
   _e_static_grab_x(&grab);

   fprintf(stderr, "%i core with %i thread running in %i bits at %i bogomips with %i cache from vendor %s with model %s\n",
           grab.cpu.core_count, grab.cpu.thread_count, grab.cpu.addr_space * 8, grab.cpu.bogo, grab.cpu.cache_size, grab.cpu.vendor, grab.cpu.model_name);
   fprintf(stderr, "X Server: '%s' released '%s' and build '%s'\n",
           grab.x.name, grab.x.release_date, grab.x.build_date);
   fprintf(stderr, "*** X Modules ***\n");
   EINA_LIST_FOREACH(grab.x.modules, l, module)
     fprintf(stderr, "[%s - '%s' - '%s' - '%s' - '%s','%s' ]\n",
             module->name, module->vendor, module->compiled_for,
             module->class, module->ABI_class, module->ABI_version);
   fprintf(stderr, "*** ***\n");

   /* FIXME: Get information about what Unix (Linux, BSD, ...) it is run on */
   /* FIXME: run a mainloop and start gathering result from df, free, lspci and lsusb */
   /* FIXME: use xrandr to figure out the screen config */

   eet_shutdown();
   eina_shutdown();

   return 0;
}

#include "e.h"

#define ACTION_TIMEOUT 30.0

/* local subsystem functions */
static void      _e_sys_cb_logout_logout(void *data, E_Dialog *dia);
static void      _e_sys_cb_logout_wait(void *data, E_Dialog *dia);
static void      _e_sys_cb_logout_abort(void *data, E_Dialog *dia);
static Eina_Bool _e_sys_cb_logout_timer(void *data);
static void      _e_sys_logout_after(void);
static void      _e_sys_logout_begin(E_Sys_Action a_after, Eina_Bool raw);
static void      _e_sys_current_action(void);
static int       _e_sys_action_do(E_Sys_Action a, char *param, Eina_Bool raw);

static int _e_sys_can_halt = 0;
static int _e_sys_can_reboot = 0;
static int _e_sys_can_suspend = 0;
static int _e_sys_can_hibernate = 0;

static E_Sys_Action _e_sys_action_current = E_SYS_NONE;
static E_Sys_Action _e_sys_action_after = E_SYS_NONE;
static Eina_Bool _e_sys_action_after_raw = EINA_FALSE;
static double _e_sys_begin_time = 0.0;
static double _e_sys_logout_begin_time = 0.0;
static Ecore_Timer *_e_sys_logout_timer = NULL;
static E_Dialog *_e_sys_logout_confirm_dialog = NULL;
static Ecore_Timer *_e_sys_susp_hib_check_timer = NULL;
static double _e_sys_susp_hib_check_last_tick = 0.0;
static Ecore_Timer *_e_sys_phantom_wake_check_timer = NULL;
static Ecore_Timer *_e_sys_resume_delay_timer = NULL;
static Eina_Bool _e_sys_suspended = EINA_FALSE;

static Ecore_Event_Handler *_e_sys_acpi_handler = NULL;

static void _e_sys_systemd_handle_inhibit(void);
static void _e_sys_systemd_poweroff(void);
static void _e_sys_systemd_reboot(void);
static void _e_sys_systemd_suspend(void);
static void _e_sys_systemd_hibernate(void);
static void _e_sys_systemd_exists_cb(void *data, const Eldbus_Message *m, Eldbus_Pending *p);

static Eina_Bool systemd_works = EINA_FALSE;
static int _e_sys_systemd_inhibit_fd = -1;

static const int E_LOGOUT_AUTO_TIME = 60.0;
static const int E_LOGOUT_WAIT_TIME = 3.0;

static Ecore_Timer *action_timeout = NULL;

static Eldbus_Proxy *login1_manger_proxy = NULL;

static int _e_sys_comp_waiting = 0;

static Ecore_Timer *_e_sys_screensaver_unignore_timer = NULL;

static double resume_backlight;

static Ecore_Timer *_e_sys_suspend_delay_timer = NULL;
static Ecore_Timer *_e_sys_hibernate_delay_timer = NULL;
static Ecore_Timer *_e_sys_halt_reboot_timer = NULL;

static Eina_Bool on_the_way_out = EINA_FALSE;

E_API int E_EVENT_SYS_SUSPEND = -1;
E_API int E_EVENT_SYS_HIBERNATE = -1;
E_API int E_EVENT_SYS_RESUME = -1;

static Eina_Bool
_e_sys_comp_done2_cb(void *data)
{
   printf("SSS: _e_sys_comp_done2_cb %p\n", data);
   e_sys_action_raw_do((E_Sys_Action)(long)data, NULL);
   return EINA_FALSE;
}

static void
_e_sys_comp_done_cb(void *data, Evas_Object *obj, const char *sig, const char *src)
{
   if (_e_sys_comp_waiting == 1) _e_sys_comp_waiting--;
   edje_object_signal_callback_del(obj, sig, src, _e_sys_comp_done_cb);
   if (_e_sys_screensaver_unignore_timer)
     {
        ecore_timer_del(_e_sys_screensaver_unignore_timer);
        _e_sys_screensaver_unignore_timer = NULL;
     }
   e_screensaver_ignore();
   ecore_evas_manual_render_set(e_comp->ee, EINA_TRUE);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        if (_e_sys_action_after != E_SYS_RESTART)
          {
             ecore_x_screensaver_suspend();
             ecore_x_dpms_force(EINA_TRUE);
          }
     }
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type != E_PIXMAP_TYPE_X)
     {
        if (e_comp->screen && e_comp->screen->dpms)
          e_comp->screen->dpms(3);
     }
#endif
   if (_e_sys_action_after != E_SYS_RESTART)
     {
        edje_freeze();
        ecore_timer_add(0.5, _e_sys_comp_done2_cb, data);
     }
   else
     {
        ecore_timer_add(0.3, _e_sys_comp_done2_cb, data);
     }
   E_FREE_FUNC(action_timeout, ecore_timer_del);
}

static Eina_Bool
_e_sys_comp_action_timeout(void *data)
{
   const Eina_List *l;
   E_Zone *zone;
   E_Sys_Action a = (long)(intptr_t)data;
   const char *sig = NULL;

   if (_e_sys_comp_waiting == 1) _e_sys_comp_waiting--;
   switch (a)
     {
      case E_SYS_RESTART:
        sig = "e,state,sys,logout,done";
        break;
      case E_SYS_LOGOUT:
        sig = "e,state,sys,logout,done";
        break;
      case E_SYS_HALT:
        sig = "e,state,sys,halt,done";
        break;
      case E_SYS_REBOOT:
        sig = "e,state,sys,reboot,done";
        break;
      case E_SYS_SUSPEND:
        sig = "e,state,sys,suspend,done";
        break;
      case E_SYS_HIBERNATE:
        sig = "e,state,sys,hibernate,done";
        break;
      default:
        break;
     }
   E_FREE_FUNC(action_timeout, ecore_timer_del);
   if (sig)
     {
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          edje_object_signal_callback_del(zone->over, sig, "e", _e_sys_comp_done_cb);
     }
   e_sys_action_raw_do(a, NULL);
   return EINA_FALSE;
}

static void
_e_sys_comp_zones_fade(E_Sys_Action a, const char *sig, Eina_Bool out)
{
   const Eina_List *l;
   E_Zone *zone;
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        // XXX:
        // don't fade backlight if we are exiting or logging out
        // in future detect if e is the login manager and then
        // we can assume it can manage backlight right anyway... then disable
        // this if below when that happens
        if ((a != E_SYS_EXIT) &&  (a != E_SYS_EXIT_NOW) && (a != E_SYS_LOGOUT))
          e_zone_fade_handle(zone, out, 0.5);
        edje_object_signal_emit(zone->base, sig, "e");
        edje_object_signal_emit(zone->over, sig, "e");
     }
}

static void
_e_sys_comp_emit_cb_wait(E_Sys_Action a, const char *sig, const char *rep, Eina_Bool nocomp_push)
{
   E_Zone *zone;

   if (_e_sys_comp_waiting == 0) _e_sys_comp_waiting++;
   if (nocomp_push) e_comp_override_add();
   else e_comp_override_timed_pop();

   _e_sys_comp_zones_fade(a, sig, nocomp_push);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     _e_comp_x_screensaver_on();
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     _e_comp_wl_screensaver_on();
#endif

   if (rep)
     {
        zone = eina_list_data_get(e_comp->zones);
        if (zone) edje_object_signal_callback_add(zone->over, rep, "e", _e_sys_comp_done_cb, (void *)(long)a);
        if (action_timeout) ecore_timer_del(action_timeout);
        action_timeout = ecore_timer_loop_add(ACTION_TIMEOUT, (Ecore_Task_Cb)_e_sys_comp_action_timeout, (intptr_t*)(long)a);
     }
}

static void
_e_sys_comp_suspend(void)
{
   resume_backlight = e_config->backlight.normal;
   _e_sys_comp_emit_cb_wait(E_SYS_SUSPEND, "e,state,sys,suspend", "e,state,sys,suspend,done", EINA_TRUE);
}

static void
_e_sys_comp_hibernate(void)
{
   resume_backlight = e_config->backlight.normal;
   _e_sys_comp_emit_cb_wait(E_SYS_HIBERNATE, "e,state,sys,hibernate", "e,state,sys,hibernate,done", EINA_TRUE);
}

static void
_e_sys_comp_reboot(void)
{
   _e_sys_comp_emit_cb_wait(E_SYS_REBOOT, "e,state,sys,reboot", "e,state,sys,reboot,done", EINA_TRUE);
}

static void
_e_sys_comp_shutdown(void)
{
   _e_sys_comp_emit_cb_wait(E_SYS_HALT, "e,state,sys,halt", "e,state,sys,halt,done", EINA_TRUE);
}

static void
_e_sys_comp_logout(void)
{
   _e_sys_comp_emit_cb_wait(E_SYS_LOGOUT, "e,state,sys,logout", "e,state,sys,logout,done", EINA_TRUE);
}

static void
_e_sys_comp_restart(void)
{
   E_Zone *zone;

   resume_backlight = e_config->backlight.normal;
   zone = eina_list_data_get(e_comp->zones);
   if ((zone) && (edje_object_data_get(zone->over, "restarted")))
     _e_sys_comp_emit_cb_wait(E_SYS_RESTART, "e,state,sys,restart", "e,state,sys,restart,done", EINA_TRUE);
   else
     _e_sys_comp_emit_cb_wait(E_SYS_RESTART, "e,state,sys,logout", "e,state,sys,logout,done", EINA_TRUE);
}

static Eina_Bool
_e_sys_screensaver_unignore_delay(void *data EINA_UNUSED)
{
   _e_sys_screensaver_unignore_timer = NULL;
   e_screensaver_unignore();
   return EINA_FALSE;
}

static Eina_Bool
_e_sys_phantom_wake_check_cb(void *data EINA_UNUSED)
{
   E_Action *act = e_action_find("suspend_smart");

   _e_sys_phantom_wake_check_timer = NULL;
   if ((e_acpi_lid_is_closed()) && (act))
     {
        ERR("Phantom wake/resume while lid still closed? Suspending again");
        if (act->func.go) act->func.go(E_OBJECT(e_zone_current_get()), NULL);
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_sys_comp_resume2(void *data EINA_UNUSED)
{
   Eina_List *l;
   E_Zone *zone;

   _e_sys_action_current = E_SYS_NONE;
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        _e_comp_x_screensaver_off();
        ecore_x_screensaver_reset();
     }
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     _e_comp_wl_screensaver_off();
#endif
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     e_backlight_level_set(zone, resume_backlight, -1.0);
   _e_sys_comp_zones_fade(E_SYS_SUSPEND, "e,state,sys,resume", EINA_FALSE);
   if (e_acpi_lid_is_closed())
     {
        if (_e_sys_phantom_wake_check_timer)
          ecore_timer_del(_e_sys_phantom_wake_check_timer);
        _e_sys_phantom_wake_check_timer =
          ecore_timer_add(1.0, _e_sys_phantom_wake_check_cb, NULL);
     }
   printf("SSS: sys resume2 @ %1.8f\n", ecore_time_get());
   return EINA_FALSE;
}

static void
_e_sys_comp_resume(void)
{
   printf("SSS: sys resume ... \n");
   edje_thaw();
   ecore_evas_manual_render_set(e_comp->ee, EINA_FALSE);
   evas_damage_rectangle_add(e_comp->evas, 0, 0, e_comp->w, e_comp->h);
#ifndef HAVE_WAYLAND_ONLY
   if (e_comp->comp_type == E_PIXMAP_TYPE_X)
     {
        ecore_x_dpms_force(EINA_FALSE);
        ecore_x_screensaver_resume();
        ecore_x_screensaver_reset();
     }
#endif
#ifdef HAVE_WAYLAND
   if (e_comp->comp_type != E_PIXMAP_TYPE_X)
     {
        if (e_comp->screen && e_comp->screen->dpms)
          e_comp->screen->dpms(0);
     }
#endif
   e_screensaver_deactivate();
   if (_e_sys_screensaver_unignore_timer)
     ecore_timer_del(_e_sys_screensaver_unignore_timer);
   _e_sys_screensaver_unignore_timer =
     ecore_timer_add(0.3, _e_sys_screensaver_unignore_delay, NULL);
   ecore_timer_add(0.6, _e_sys_comp_resume2, NULL);
   printf("SSS: sys resume @ %1.8f\n", ecore_time_get());
}

static void
_e_sys_systemd_signal_prepare_shutdown(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
   EINA_SAFETY_ON_TRUE_RETURN(eldbus_message_error_get(msg, NULL, NULL));
   Eina_Bool b = EINA_FALSE;

   if (!eldbus_message_arguments_get(msg, "b", &b)) return;
   printf("SSS: systemd said to prepare for shutdown! bool=%i @%1.8f\n", (int)b, ecore_time_get());
   if (b)
     {
        if (!e_sys_on_the_way_out_get()) e_sys_action_do(E_SYS_LOGOUT, NULL);
     }
}

static void
_e_sys_systemd_signal_prepare_sleep(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
   EINA_SAFETY_ON_TRUE_RETURN(eldbus_message_error_get(msg, NULL, NULL));
   Eina_Bool b = EINA_FALSE;

   if (!eldbus_message_arguments_get(msg, "b", &b)) return;
   // b == 1 -> suspending
   // b == 0 -> resuming
   printf("SSS: systemd said to prepare for sleep! bool=%i @%1.8f\n", (int)b, ecore_time_get());
   if (b == EINA_FALSE)
     {
        if (_e_sys_suspended)
          {
             _e_sys_suspended = EINA_FALSE;
             ecore_event_add(E_EVENT_SYS_RESUME, NULL, NULL, NULL);
             _e_sys_comp_resume();
          }
     }
   else
     {
        _e_sys_suspended = EINA_TRUE;
        if (e_config->desklock_on_suspend)
          {
             if (!e_desklock_state_get())
               {
                  // XXX: this desklock - ensure its instant
                  e_desklock_show(EINA_TRUE);
               }
          }
     }
}

/* externally accessible functions */
EINTERN int
e_sys_init(void)
{
   Eldbus_Connection *conn;
   Eldbus_Object *obj;

   conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
   obj = eldbus_object_get(conn, "org.freedesktop.login1",
                           "/org/freedesktop/login1");
   login1_manger_proxy = eldbus_proxy_get(obj,
                                          "org.freedesktop.login1.Manager");
   eldbus_proxy_signal_handler_add(login1_manger_proxy, "PrepareForShutdown",
                                   _e_sys_systemd_signal_prepare_shutdown,
                                   NULL);
   eldbus_proxy_signal_handler_add(login1_manger_proxy, "PrepareForSleep",
                                   _e_sys_systemd_signal_prepare_sleep,
                                   NULL);
   eldbus_name_owner_get(conn, "org.freedesktop.login1",
                         _e_sys_systemd_exists_cb, NULL);
   _e_sys_systemd_handle_inhibit();

   E_EVENT_SYS_SUSPEND = ecore_event_type_new();
   E_EVENT_SYS_HIBERNATE = ecore_event_type_new();
   E_EVENT_SYS_RESUME = ecore_event_type_new();
   return 1;
}

EINTERN int
e_sys_shutdown(void)
{
   if (_e_sys_halt_reboot_timer)
     ecore_timer_del(_e_sys_halt_reboot_timer);
   if (_e_sys_suspend_delay_timer)
     ecore_timer_del(_e_sys_suspend_delay_timer);
   if (_e_sys_hibernate_delay_timer)
     ecore_timer_del(_e_sys_hibernate_delay_timer);
   if (_e_sys_resume_delay_timer)
     ecore_timer_del(_e_sys_resume_delay_timer);
   if (_e_sys_screensaver_unignore_timer)
     ecore_timer_del(_e_sys_screensaver_unignore_timer);
   if (_e_sys_acpi_handler)
     ecore_event_handler_del(_e_sys_acpi_handler);
   _e_sys_halt_reboot_timer = NULL;
   _e_sys_suspend_delay_timer = NULL;
   _e_sys_hibernate_delay_timer = NULL;
   _e_sys_resume_delay_timer = NULL;
   _e_sys_screensaver_unignore_timer = NULL;
   _e_sys_acpi_handler = NULL;
   if (login1_manger_proxy)
     {
         Eldbus_Connection *conn;
         Eldbus_Object *obj;

         obj = eldbus_proxy_object_get(login1_manger_proxy);
         conn = eldbus_object_connection_get(obj);
         eldbus_proxy_unref(login1_manger_proxy);
         eldbus_object_unref(obj);
         eldbus_connection_unref(conn);
         login1_manger_proxy = NULL;
     }
   if (_e_sys_systemd_inhibit_fd >= 0)
     {
        close(_e_sys_systemd_inhibit_fd);
        _e_sys_systemd_inhibit_fd = -1;
     }
   return 1;
}

E_API int
e_sys_action_possible_get(E_Sys_Action a)
{
   switch (a)
     {
      case E_SYS_EXIT:
      case E_SYS_RESTART:
      case E_SYS_EXIT_NOW:
        return 1;

      case E_SYS_LOGOUT:
        return 1;

      case E_SYS_HALT:
      case E_SYS_HALT_NOW:
        return _e_sys_can_halt;

      case E_SYS_REBOOT:
        return _e_sys_can_reboot;

      case E_SYS_SUSPEND:
        return _e_sys_can_suspend;

      case E_SYS_HIBERNATE:
        return _e_sys_can_hibernate;

      default:
        return 0;
     }
   return 0;
}

E_API int
e_sys_action_do(E_Sys_Action a, char *param)
{
   int ret = 0;

   if (_e_sys_action_current != E_SYS_NONE)
     {
        _e_sys_current_action();
        return 0;
     }
   e_config_save_flush();
   switch (a)
     {
      case E_SYS_EXIT:
      case E_SYS_RESTART:
      case E_SYS_EXIT_NOW:
      case E_SYS_LOGOUT:
      case E_SYS_HALT_NOW:
        on_the_way_out = EINA_TRUE;
        EINA_FALLTHROUGH;

      case E_SYS_SUSPEND:
      case E_SYS_HIBERNATE:
        ret = _e_sys_action_do(a, param, EINA_FALSE);
        break;

      case E_SYS_HALT:
      case E_SYS_REBOOT:
        on_the_way_out = EINA_TRUE;
        if (!e_util_immortal_check())
          ret = _e_sys_action_do(a, param, EINA_FALSE);
        break;

      default:
        break;
     }

   printf("SSS: e_sys_ction_do ret=%i a=%i\n", ret, _e_sys_action_current);
   if (ret) _e_sys_action_current = a;
   else _e_sys_action_current = E_SYS_NONE;

   return ret;
}

E_API int
e_sys_action_raw_do(E_Sys_Action a, char *param)
{
   int ret = 0;

   e_config_save_flush();
   if (_e_sys_action_current != E_SYS_NONE)
     {
        _e_sys_current_action();
        return 0;
     }

   ret = _e_sys_action_do(a, param, EINA_TRUE);

   printf("SSS: e_sys_ction_raw_do ret=%i a=%i\n", ret, _e_sys_action_current);
   if (ret) _e_sys_action_current = a;
   else _e_sys_action_current = E_SYS_NONE;

   return ret;
}

static Eina_List *extra_actions = NULL;

E_API E_Sys_Con_Action *
e_sys_con_extra_action_register(const char *label,
                                const char *icon_group,
                                const char *button_name,
                                void (*func)(void *data),
                                const void *data)
{
   E_Sys_Con_Action *sca;

   sca = E_NEW(E_Sys_Con_Action, 1);
   if (label)
     sca->label = eina_stringshare_add(label);
   if (icon_group)
     sca->icon_group = eina_stringshare_add(icon_group);
   if (button_name)
     sca->button_name = eina_stringshare_add(button_name);
   sca->func = func;
   sca->data = data;
   extra_actions = eina_list_append(extra_actions, sca);
   return sca;
}

E_API void
e_sys_con_extra_action_unregister(E_Sys_Con_Action *sca)
{
   extra_actions = eina_list_remove(extra_actions, sca);
   if (sca->label) eina_stringshare_del(sca->label);
   if (sca->icon_group) eina_stringshare_del(sca->icon_group);
   if (sca->button_name) eina_stringshare_del(sca->button_name);
   free(sca);
}

E_API const Eina_List *
e_sys_con_extra_action_list_get(void)
{
   return extra_actions;
}

E_API Eina_Bool
e_sys_on_the_way_out_get(void)
{
   return on_the_way_out;
}

static void
_e_sys_systemd_inhibit_cb(void *data EINA_UNUSED, const Eldbus_Message *m, Eldbus_Pending *p EINA_UNUSED)
{
   int fd = -1;
   if (eldbus_message_error_get(m, NULL, NULL)) return;
   if (!eldbus_message_arguments_get(m, "h", &fd))
     _e_sys_systemd_inhibit_fd = fd;
}

static void
_e_sys_systemd_handle_inhibit(void)
{
   Eldbus_Message *m;

   if (!login1_manger_proxy) return;
   if (!(m = eldbus_proxy_method_call_new(login1_manger_proxy, "Inhibit")))
     return;
   eldbus_message_arguments_append
     (m, "ssss",
         "handle-power-key:"
         "handle-suspend-key:"
         "handle-hibernate-key:"
         "handle-lid-switch", // what
         "Enlightenment", // who (string)
         "Normal Execution", // why (string)
         "block");
   eldbus_proxy_send(login1_manger_proxy, m, _e_sys_systemd_inhibit_cb, NULL, -1);
}

static void
_e_sys_systemd_check_cb(void *data, const Eldbus_Message *m, Eldbus_Pending *p EINA_UNUSED)
{
   int *dest = data;
   char *s = NULL;
   if (!eldbus_message_arguments_get(m, "s", &s)) return;
   if (!s) return;
   if (!strcmp(s, "yes")) *dest = 1;
   else *dest = 0;
}

static void
_e_sys_systemd_check(void)
{
   if (!login1_manger_proxy) return;
   if (!eldbus_proxy_call(login1_manger_proxy, "CanPowerOff",
                          _e_sys_systemd_check_cb, &_e_sys_can_halt, -1, ""))
     return;
   if (!eldbus_proxy_call(login1_manger_proxy, "CanReboot",
                          _e_sys_systemd_check_cb, &_e_sys_can_reboot, -1, ""))
     return;
   if (!eldbus_proxy_call(login1_manger_proxy, "CanSuspend",
                          _e_sys_systemd_check_cb, &_e_sys_can_suspend, -1, ""))
     return;
   if (!eldbus_proxy_call(login1_manger_proxy, "CanHibernate",
                          _e_sys_systemd_check_cb, &_e_sys_can_hibernate, -1, ""))
     return;
}

static void
_e_sys_systemd_exists_cb(void *data EINA_UNUSED, const Eldbus_Message *m, Eldbus_Pending *p EINA_UNUSED)
{
   const char *id = NULL;

   if (eldbus_message_error_get(m, NULL, NULL)) goto fail;
   if (!eldbus_message_arguments_get(m, "s", &id)) goto fail;
   if ((!id) || (id[0] != ':')) goto fail;
   systemd_works = EINA_TRUE;
   _e_sys_systemd_check();
   return;
fail:
   systemd_works = EINA_FALSE;
   // just pretend to do everything in this case
   _e_sys_can_halt = 1;
   _e_sys_can_reboot = 1;
   _e_sys_can_suspend = 1;
   _e_sys_can_hibernate = 1;
}

static void
_e_sys_systemd_poweroff(void)
{
   eldbus_proxy_call(login1_manger_proxy, "PowerOff", NULL, NULL, -1, "b", 0);
}

static void
_e_sys_systemd_reboot(void)
{
   eldbus_proxy_call(login1_manger_proxy, "Reboot", NULL, NULL, -1, "b", 0);
}

static void
_e_sys_systemd_suspend(void)
{
   eldbus_proxy_call(login1_manger_proxy, "Suspend", NULL, NULL, -1, "b", 0);
}

static void
_e_sys_systemd_hibernate(void)
{
   eldbus_proxy_call(login1_manger_proxy, "Hibernate", NULL, NULL, -1, "b", 0);
}

static Eina_Bool
_e_sys_resume_delay(void *d EINA_UNUSED)
{
   printf("SSS: hib check resume delay...\n");
   _e_sys_resume_delay_timer = NULL;
   if (_e_sys_suspended)
     {
        printf("SSS: suspended to false\n");
        _e_sys_suspended = EINA_FALSE;
        ecore_event_add(E_EVENT_SYS_RESUME, NULL, NULL, NULL);
        _e_sys_comp_resume();
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_sys_susp_hib_check_timer_cb(void *data EINA_UNUSED)
{
   double t = ecore_time_unix_get();

   printf("SSS: hib check @%1.8f (unix %1.8f, delt %1.8f)\n", ecore_time_get(), t, t -  _e_sys_susp_hib_check_last_tick);
   if ((t - _e_sys_susp_hib_check_last_tick) > 0.5)
     {
        printf("SSS: hib check long gap\n");
        _e_sys_susp_hib_check_timer = NULL;
        if (_e_sys_resume_delay_timer)
          ecore_timer_del(_e_sys_resume_delay_timer);
        _e_sys_resume_delay_timer =
          ecore_timer_add(0.2, _e_sys_resume_delay, NULL);
        return EINA_FALSE;
     }
   _e_sys_susp_hib_check_last_tick = t;
   return EINA_TRUE;
}

static void
_e_sys_susp_hib_check(void)
{
   if (_e_sys_susp_hib_check_timer)
     ecore_timer_del(_e_sys_susp_hib_check_timer);
   _e_sys_susp_hib_check_last_tick = ecore_time_unix_get();
   _e_sys_susp_hib_check_timer =
     ecore_timer_loop_add(0.1, _e_sys_susp_hib_check_timer_cb, NULL);
   printf("SSS: hib check begin @%1.8f (unix %1.8f)\n", ecore_time_get(), _e_sys_susp_hib_check_last_tick);
}

/* local subsystem functions */
static void
_e_sys_cb_logout_logout(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (_e_sys_logout_timer)
     {
        ecore_timer_del(_e_sys_logout_timer);
        _e_sys_logout_timer = NULL;
     }
   _e_sys_logout_begin_time = 0.0;
   _e_sys_logout_after();
   e_object_del(E_OBJECT(dia));
   _e_sys_logout_confirm_dialog = NULL;
}

static void
_e_sys_cb_logout_wait(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (_e_sys_logout_timer) ecore_timer_del(_e_sys_logout_timer);
   _e_sys_logout_timer = ecore_timer_loop_add(0.5, _e_sys_cb_logout_timer, NULL);
   _e_sys_logout_begin_time = ecore_time_get();
   e_object_del(E_OBJECT(dia));
   _e_sys_logout_confirm_dialog = NULL;
}

static void
_e_sys_cb_logout_abort(void *data EINA_UNUSED, E_Dialog *dia)
{
   if (_e_sys_logout_timer)
     {
        ecore_timer_del(_e_sys_logout_timer);
        _e_sys_logout_timer = NULL;
     }
   _e_sys_logout_begin_time = 0.0;
   e_object_del(E_OBJECT(dia));
   _e_sys_logout_confirm_dialog = NULL;
   _e_sys_action_current = E_SYS_NONE;
   _e_sys_action_after = E_SYS_NONE;
   _e_sys_action_after_raw = EINA_FALSE;
}

static void
_e_sys_logout_confirm_dialog_update(int remaining)
{
   char txt[4096];

   if (!_e_sys_logout_confirm_dialog)
     {
        fputs("ERROR: updating logout confirm dialog, but none exists!\n",
              stderr);
        return;
     }

   snprintf(txt, sizeof(txt),
            _("Logout is taking too long.<ps/>"
              "Some applications refuse to close.<ps/>"
              "Do you want to finish the logout<ps/>"
              "anyway without closing these<ps/>"
              "applications first?<ps/><ps/>"
              "Auto logout in %d seconds."), remaining);

   e_dialog_text_set(_e_sys_logout_confirm_dialog, txt);
}

static Eina_Bool
_e_sys_cb_logout_timer(void *data EINA_UNUSED)
{
   E_Client *ec;
   int pending = 0;

   E_CLIENT_FOREACH(ec)
     {
        if (e_client_util_ignored_get(ec)) continue;
        if (!ec->internal) pending++;
     }
   if (pending == 0) goto after;
   else if (_e_sys_logout_confirm_dialog)
     {
        int remaining = E_LOGOUT_AUTO_TIME -
          round(ecore_loop_time_get() - _e_sys_logout_begin_time);
        /* it has taken 60 (E_LOGOUT_AUTO_TIME) seconds of waiting the
         * confirm dialog and we still have apps that will not go
         * away. Do the action as user may be far away or forgot it.
         *
         * NOTE: this is the behavior for many operating systems and I
         *       guess the reason is people that hit "shutdown" and
         *       put their laptops in their backpacks in the hope
         *       everything will be turned off properly.
         */
        if (remaining > 0)
          {
             _e_sys_logout_confirm_dialog_update(remaining);
             return ECORE_CALLBACK_RENEW;
          }
        else
          {
             _e_sys_cb_logout_logout(NULL, _e_sys_logout_confirm_dialog);
             return ECORE_CALLBACK_CANCEL;
          }
     }
   else
     {
        // it has taken E_LOGOUT_WAIT_TIME seconds of waiting and we still
        // have apps that will not go away
        double now = ecore_loop_time_get();
        if ((now - _e_sys_logout_begin_time) > E_LOGOUT_WAIT_TIME)
          {
             E_Dialog *dia;

             dia = e_dialog_new(NULL, "E", "_sys_error_logout_slow");
             if (dia)
               {
                  _e_sys_logout_confirm_dialog = dia;
                  e_dialog_title_set(dia, _("Logout problems"));
                  e_dialog_icon_set(dia, "system-log-out", 64);
                  _e_sys_logout_confirm_dialog_update(E_LOGOUT_AUTO_TIME);
                  e_dialog_button_add(dia, _("Logout now"), NULL,
                                      _e_sys_cb_logout_logout, NULL);
                  e_dialog_button_add(dia, _("Wait longer"), NULL,
                                      _e_sys_cb_logout_wait, NULL);
                  e_dialog_button_add(dia, _("Cancel Logout"), NULL,
                                      _e_sys_cb_logout_abort, NULL);
                  e_dialog_button_focus_num(dia, 1);
                  elm_win_center(dia->win, 1, 1);
                  e_win_no_remember_set(dia->win, 1);
                  e_dialog_show(dia);
                  _e_sys_logout_begin_time = now;
                  resume_backlight = e_config->backlight.normal;
               }
             _e_sys_comp_resume();
             return ECORE_CALLBACK_RENEW;
          }
     }
   return ECORE_CALLBACK_RENEW;
after:
   switch (_e_sys_action_after)
     {
      case E_SYS_EXIT:
        _e_sys_comp_logout();
        break;
      case E_SYS_HALT:
      case E_SYS_HALT_NOW:
        _e_sys_comp_shutdown();
        break;
      case E_SYS_REBOOT:
        _e_sys_comp_reboot();
        break;
      default: break;
     }
   _e_sys_logout_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_sys_logout_after(void)
{
   printf("SSS: sys action after %i\n", _e_sys_action_after);
   _e_sys_action_current = _e_sys_action_after;
   _e_sys_action_do(_e_sys_action_after, NULL, _e_sys_action_after_raw);
   _e_sys_action_after = E_SYS_NONE;
   _e_sys_action_after_raw = EINA_FALSE;
}

static void
_e_sys_logout_begin(E_Sys_Action a_after, Eina_Bool raw)
{
   const Eina_List *l, *ll;
   E_Client *ec;

   stopping = 1;
   /* start logout - at end do the a_after action */
   _e_sys_action_after = a_after;
   _e_sys_action_after_raw = raw;
   EINA_LIST_FOREACH_SAFE(e_comp->clients, l, ll, ec)
     {
        e_client_act_close_begin(ec);
     }
   /* and poll to see if all pending windows are gone yet every 0.5 sec */
   _e_sys_logout_begin_time = ecore_time_get();
   if (_e_sys_logout_timer) ecore_timer_del(_e_sys_logout_timer);
   _e_sys_logout_timer = ecore_timer_loop_add(0.5, _e_sys_cb_logout_timer, NULL);
}

static void
_e_sys_restart_begin(E_Sys_Action a_after, Eina_Bool raw)
{
   _e_sys_action_after = a_after;
   _e_sys_action_after_raw = raw;
}

static void
_e_sys_current_action(void)
{
   /* display dialog that currently an action is in progress */
   E_Dialog *dia;

   dia = e_dialog_new(NULL,
                      "E", "_sys_error_action_busy");
   if (!dia) return;

   e_dialog_title_set(dia, _("Enlightenment is busy with another request"));
   e_dialog_icon_set(dia, "enlightenment/sys", 64);
   switch (_e_sys_action_current)
     {
      case E_SYS_LOGOUT:
        e_dialog_text_set(dia, _("Logging out.<ps/>"
                                 "You cannot perform other system actions<ps/>"
                                 "once a logout has begun."));
        break;

      case E_SYS_HALT:
      case E_SYS_HALT_NOW:
        e_dialog_text_set(dia, _("Powering off.<ps/>"
                                 "You cannot perform any other system actions<ps/>"
                                 "once a shutdown has been started."));
        break;

      case E_SYS_REBOOT:
        e_dialog_text_set(dia, _("Resetting.<ps/>"
                                 "You cannot perform any other system actions<ps/>"
                                 "once a reboot has begun."));
        break;

      case E_SYS_SUSPEND:
        e_dialog_text_set(dia, _("Suspending.<ps/>"
                                 "You cannot perform any other system actions<ps/>"
                                 "until suspend is complete."));
        break;

      case E_SYS_HIBERNATE:
        e_dialog_text_set(dia, _("Hibernating.<ps/>"
                                 "You cannot perform any other system actions<ps/>"
                                 "until hibernation is complete."));
        break;

      default:
        e_dialog_text_set(dia, _("EEK! This should not happen"));
        break;
     }
   e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
   e_dialog_button_focus_num(dia, 0);
   elm_win_center(dia->win, 1, 1);
   e_win_no_remember_set(dia->win, 1);
   e_dialog_show(dia);
}

static Eina_Bool
_e_sys_cb_acpi_event(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   E_Event_Acpi *ev = event;

   if (((ev->type == E_ACPI_TYPE_LID) &&
        (ev->status == E_ACPI_LID_OPEN)) ||
       (ev->type == E_ACPI_TYPE_POWER) ||
       (ev->type == E_ACPI_TYPE_SLEEP))
     {
        if (_e_sys_acpi_handler)
          {
             ecore_event_handler_del(_e_sys_acpi_handler);
             _e_sys_acpi_handler = NULL;
          }
        e_powersave_mode_unforce();
        ecore_timer_add(0.5, _e_sys_resume_delay, NULL);
        // XXX: need some way of, at the system level, restoring
        // system and devices back to running normally
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_sys_suspend_delay(void *data EINA_UNUSED)
{
   _e_sys_suspend_delay_timer = NULL;
   _e_sys_begin_time = ecore_time_get();
   if (e_config->suspend_connected_standby == 0)
     {
        if (systemd_works) _e_sys_systemd_suspend();
        else
          {
             _e_sys_susp_hib_check();
             e_system_send("power-suspend", NULL);
          }
     }
   else
     {
        if (_e_sys_acpi_handler)
          ecore_event_handler_del(_e_sys_acpi_handler);
        _e_sys_acpi_handler =
          ecore_event_handler_add(E_EVENT_ACPI,
                                  _e_sys_cb_acpi_event,
                                  NULL);
        e_powersave_mode_force(E_POWERSAVE_MODE_FREEZE);
        // XXX: need some system way of forcing the system into a very low
        // power level with as many devices suspended as possible. below is
        // a simple "freeze the cpu/kernel" which is not what we want actually
        //   sleep 2 && echo freeze | sudo tee /sys/power/state
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_sys_hibernate_delay(void *data EINA_UNUSED)
{
   _e_sys_hibernate_delay_timer = NULL;
   _e_sys_begin_time = ecore_time_get();
   if (systemd_works) _e_sys_systemd_hibernate();
   else
     {
        _e_sys_susp_hib_check();
        e_system_send("power-hibernate", NULL);
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_sys_halt_reboot(void *data EINA_UNUSED)
{
   _e_sys_halt_reboot_timer = NULL;
   ecore_main_loop_quit();
   return EINA_FALSE;
}

static int
_e_sys_action_do(E_Sys_Action a, char *param EINA_UNUSED, Eina_Bool raw)
{
   int ret = 0;

   switch (a)
     {
      case E_SYS_EXIT:
        // XXX TODO: check for e_fm_op_registry entries and confirm
        if (!e_util_immortal_check())
          {
             e_fm2_die();
             ecore_main_loop_quit();
             ret = 1;
          }
        else
          return 0;
        break;

      case E_SYS_RESTART:
        // XXX TODO: check for e_fm_op_registry entries and confirm
        // FIXME: we don't share out immortal info to restarted e. :(
//	if (!e_util_immortal_check())
        if (raw)
          {
             e_fm2_die();
             restart = 1;
             ecore_main_loop_quit();
             ret = 1;
          }
        else
          {
             _e_sys_restart_begin(E_SYS_RESTART, raw);
             _e_sys_comp_restart();
             return 0;
          }
//        else
//          return 0;
      break;

      case E_SYS_EXIT_NOW:
        exit(0);
        break;

      case E_SYS_LOGOUT:
        // XXX TODO: check for e_fm_op_registry entries and confirm
        if (raw)
          {
             e_fm2_die();
             _e_sys_logout_after();
          }
        else if (!_e_sys_comp_waiting)
          {
             _e_sys_logout_begin(E_SYS_EXIT, raw);
          }
        break;

      case E_SYS_HALT:
      case E_SYS_HALT_NOW:
        /* shutdown -h now */
        if (e_util_immortal_check()) return 0;
        e_fm2_die();
        if (!_e_sys_comp_waiting)
          {
             if (raw)
               {
                  printf("SSS: do actual halt...\n");
                  _e_sys_begin_time = ecore_time_get();
                  if (systemd_works)
                    {
                       printf("SSS: tell systemd to halt...\n");
                       _e_sys_systemd_poweroff();
                    }
                  else
                    {
                       printf("SSS: tell e system service to halt...\n");
                       e_system_send("power-halt", NULL);
                    }
                  if (!_e_sys_halt_reboot_timer)
                    _e_sys_halt_reboot_timer = ecore_timer_add
                      (1.0, _e_sys_halt_reboot, NULL);
                  ret = 1;
               }
             else
               {
                  printf("SSS: begin halt...\n");
                  ret = 0;
                  _e_sys_begin_time = ecore_time_get();
                  _e_sys_logout_begin(a, EINA_TRUE);
               }
             /* FIXME: display halt status */
          }
        break;

      case E_SYS_REBOOT:
        /* shutdown -r now */
        if (e_util_immortal_check()) return 0;
        e_fm2_die();
        if (!_e_sys_comp_waiting)
          {
             if (raw)
               {
                  printf("SSS: do actual reboot...\n");
                  _e_sys_begin_time = ecore_time_get();
                  if (systemd_works)
                    {
                       printf("SSS: tell systemd to reboot...\n");
                       _e_sys_systemd_reboot();
                    }
                  else
                    {
                       printf("SSS: tell e system service to reboot...\n");
                       e_system_send("power-reboot", NULL);
                    }
                  if (!_e_sys_halt_reboot_timer)
                    _e_sys_halt_reboot_timer = ecore_timer_add
                      (1.0, _e_sys_halt_reboot, NULL);
                  ret = 1;
               }
             else
               {
                  printf("SSS: begin reboot...\n");
                  ret = 0;
                  _e_sys_begin_time = ecore_time_get();
                  _e_sys_logout_begin(a, EINA_TRUE);
               }
          }
        break;

      case E_SYS_SUSPEND:
        /* /etc/acpi/sleep.sh force */
        if (!_e_sys_comp_waiting)
          {
             if (_e_sys_phantom_wake_check_timer)
               ecore_timer_del(_e_sys_phantom_wake_check_timer);
             _e_sys_phantom_wake_check_timer = NULL;
             if (raw)
               {
                  _e_sys_suspended = EINA_TRUE;
                  if (e_config->desklock_on_suspend)
                  // XXX: this desklock - ensure its instant
                    e_desklock_show(EINA_TRUE);
                  if (_e_sys_suspend_delay_timer)
                    ecore_timer_del(_e_sys_suspend_delay_timer);
                  if (e_config->suspend_connected_standby == 0) ret = 1;
                  // XXX: make timer shorter if desklock is instant
                  _e_sys_suspend_delay_timer =
                    ecore_timer_add(0.5, _e_sys_suspend_delay, NULL);
               }
             else
               {
                  ecore_event_add(E_EVENT_SYS_SUSPEND, NULL, NULL, NULL);
                  _e_sys_comp_suspend();
                  return 0;
               }
             /* FIXME: display suspend status */
          }
        break;

      case E_SYS_HIBERNATE:
        /* /etc/acpi/hibernate.sh force */
        if (!_e_sys_comp_waiting)
          {
             if (_e_sys_phantom_wake_check_timer)
               ecore_timer_del(_e_sys_phantom_wake_check_timer);
             _e_sys_phantom_wake_check_timer = NULL;
             if (raw)
               {
                  _e_sys_suspended = EINA_TRUE;
                  if (e_config->desklock_on_suspend)
                  // XXX: this desklock - ensure its instant
                    e_desklock_show(EINA_TRUE);
                  if (_e_sys_hibernate_delay_timer)
                    ecore_timer_del(_e_sys_hibernate_delay_timer);
                  ret = 1;
                  // XXX: make timer shorter if desklock is instant
                  _e_sys_hibernate_delay_timer =
                    ecore_timer_add(0.5, _e_sys_hibernate_delay, NULL);
               }
             else
               {
                  ecore_event_add(E_EVENT_SYS_HIBERNATE, NULL, NULL, NULL);
                  _e_sys_comp_hibernate();
                  return 0;
               }
             /* FIXME: display hibernate status */
          }
        break;

      default:
        return 0;
     }
   return ret;
}
#include "config.h"

#include <Ecore.h>

#ifdef HAVE_BLUETOOTH
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <sys/select.h>
#include <fcntl.h>
#endif

#define MAX_SZ 500

double
e_sys_l2ping(const char *bluetooth_mac, int timeout_ms)
{
#ifdef HAVE_BLUETOOTH
   char send_buf[L2CAP_CMD_HDR_SIZE + MAX_SZ];
   char recv_buf[L2CAP_CMD_HDR_SIZE + MAX_SZ];
   char tmp[18];
   bdaddr_t bdaddr;
   l2cap_cmd_hdr *send_cmd;
   l2cap_cmd_hdr *recv_cmd;
   struct sockaddr_l2 addr;
   socklen_t optlen;
   double start;
   int fd, err, size, i;
   fd_set rfds, wfds, exfds;
   struct timeval tv;

   // Create socket
   fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_NONBLOCK, BTPROTO_L2CAP);
   if (fd < 0)
     {
        perror("Can't create socket");
        return -1;
     }

   // Bind to local address
   memset(&addr, 0, sizeof(addr));
   addr.l2_family = AF_BLUETOOTH;
   bacpy(&bdaddr, BDADDR_ANY);
   bacpy(&addr.l2_bdaddr, &bdaddr);
   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
     {
        perror("Can't bind socket");
        close(fd);
        return -1;
     }

   if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0)
     {
        perror("Can't set socket to non-blocking... continue");
     }

   // Connect to remote device
   memset(&addr, 0, sizeof(addr));
   addr.l2_family = AF_BLUETOOTH;
   str2ba(bluetooth_mac, &addr.l2_bdaddr);
   if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
     {
        perror("Can't bind connect socket");
        close(fd);
        return -1;
     }

   FD_ZERO(&rfds);
   FD_ZERO(&wfds);
   FD_ZERO(&exfds);
   FD_SET(fd, &wfds);
   tv.tv_sec = timeout_ms / 1000;
   tv.tv_usec = (timeout_ms % 1000) * 1000;
   start = ecore_time_get();
   err = select(fd + 1, &rfds, &wfds, &exfds, &tv);
   if (err == 0)
     {
        fprintf(stderr, "Connect timeout %s\n", bluetooth_mac);
        return -1;
     }
   // adjust timeout by how long we waited to connect
   timeout_ms -= (ecore_time_get() - start) * 1000;
   if (timeout_ms < 1) timeout_ms = 1;

   // Get local address
   memset(&addr, 0, sizeof(addr));
   optlen = sizeof(addr);
   if (getsockname(fd, (struct sockaddr *)&addr, &optlen) < 0)
     {
        perror("Can't get local address");
        return -1;
     }

   ba2str(&addr.l2_bdaddr, tmp);

   size = 44; // use std 44 byte ping size, but no more than MAX_SZ

   send_cmd = (l2cap_cmd_hdr *)send_buf;
   send_cmd->ident = 200;
   send_cmd->len = htobs(size);
   send_cmd->code = L2CAP_ECHO_REQ;
   // init buffer with some content
   for (i = 0; i < size; i++)
     {
        // ABCDEF....WXYZABCEF... up to "size" chars
        send_buf[L2CAP_CMD_HDR_SIZE + i] = 'A' + (i % 26);
     }
   // get our time just before a send
   start = ecore_time_get();
   // send the ping
   if (send(fd, send_buf, L2CAP_CMD_HDR_SIZE + size, 0) <= 0)
     {
        perror("Send failed");
        return -1;
     }

   do
     {
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_ZERO(&exfds);
        FD_SET(fd, &rfds);
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        err = select(fd + 1, &rfds, &wfds, &exfds, &tv);
        if (err < 0)
          {
             perror("Select failed");
             return -1;
          }
        else if (err == 0)
          {
             fprintf(stderr, "Select timeout %s\n", bluetooth_mac);
             return -1;
          }
        err = recv(fd, recv_buf, L2CAP_CMD_HDR_SIZE + size, 0);
        if (err == 0)
          {
             fprintf(stderr, "Disconnect %s\n", bluetooth_mac);
             return -1;
          }
        else if (err < 0)
          {
             perror("Recv failed");
             return -1;
          }

        recv_cmd = (l2cap_cmd_hdr *)recv_buf;
        recv_cmd->len = btohs(recv_cmd->len);
        // we only want the 200 ident response packets
        if (recv_cmd->ident != 200) continue;
        if (recv_cmd->code == L2CAP_COMMAND_REJ)
          {
             fprintf(stderr, "Peer %s doesn't do echo\n", bluetooth_mac);
             return -1;
          }
        if (recv_cmd->len != size)
          {
             fprintf(stderr, "Size %i echo for %s does not match %i\n",
                     recv_cmd->len, bluetooth_mac, size);
             return -1;
          }
        if (memcmp(send_buf + L2CAP_CMD_HDR_SIZE,
                   recv_buf + L2CAP_CMD_HDR_SIZE, size) != 0)
          {
             fprintf(stderr, "Echo response for %s data does not match sent data\n", bluetooth_mac);
             return -1;
          }
        fprintf(stderr, "Device %s responded\n", bluetooth_mac);
        break;
     }
   while (1);

   close(fd);
   // time it took to send and get our response
   return ecore_time_get() - start;
#else
   (void) bluetooth_mac;
   fprintf(stderr, "e_sys_l2ping nop mac=%s timeout=%ims\n", bluetooth_mac, timeout_ms);
   return -1;
#endif
}
#include "config.h"

#define __USE_MISC
#define _SVID_SOURCE
#define _DEFAULT_SOURCE

#ifdef HAVE_FEATURES_H
# include <features.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_ENVIRON
# define _GNU_SOURCE 1
#endif
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#include <Eina.h>

#ifdef HAVE_ENVIRON
extern char **environ;
#endif

/* local subsystem functions */
#ifdef HAVE_EEZE_MOUNT
static Eina_Bool mountopts_check(const char *opts);
static Eina_Bool mount_args_check(int argc, char **argv, const char *action);
#endif
static int       auth_action_ok(char *a,
                                gid_t gid,
                                gid_t *gl,
                                int gn,
                                gid_t egid);
static int       auth_etc_enlightenment_sysactions(char *a,
                                                   char *u,
                                                   char **g);
static void      auth_etc_enlightenment_sysactions_perm(char *path);
static char     *get_word(char *s,
                          char *d);
/* local subsystem globals */
static Eina_Hash *actions = NULL;
static uid_t uid = -1;

/* externally accessible functions */
int
main(int argc,
     char **argv)
{
   int i, gn;
   int test = 0;
   char *action = NULL, *cmd;
#ifdef HAVE_EEZE_MOUNT
   Eina_Bool mnt = EINA_FALSE;
   const char *act = NULL;
#endif
   gid_t gid, gl[65536], egid;

   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-h")) ||
            (!strcmp(argv[i], "-help")) ||
            (!strcmp(argv[i], "--help")))
          {
             printf(
               "This is an internal tool for Enlightenment.\n"
               "do not use it.\n"
               );
             exit(0);
          }
     }
   if (argc >= 3)
     {
        if ((argc == 3) && (!strcmp(argv[1], "-t")))
          {
             test = 1;
             action = argv[2];
          }
#ifdef HAVE_EEZE_MOUNT
        else
          {
             const char *s;

             s = strrchr(argv[1], '/');
             if ((!s) || (!s[1])) exit(1);  /* eeze always uses complete path */
             s++;
             if (strcmp(s, "mount") && strcmp(s, "umount") && strcmp(s, "eject")) exit(1);
             mnt = EINA_TRUE;
             act = s;
             action = argv[1];
          }
#endif
     }
   else if (argc == 2)
     {
        action = argv[1];
     }
   else
     {
        exit(1);
     }
   if (!action) exit(1);

   eina_init();

   uid = getuid();
   gid = getgid();
   egid = getegid();
   gn = getgroups(65536, gl);
   if (gn < 0)
     {
        printf("ERROR: MEMBER OF MORE THAN 65536 GROUPS\n");
        exit(3);
     }
   if (setuid(0) != 0)
     {
        printf("ERROR: UNABLE TO ASSUME ROOT PRIVILEGES\n");
        exit(5);
     }
   if (setgid(0) != 0)
     {
        printf("ERROR: UNABLE TO ASSUME ROOT GROUP PRIVILEGES\n");
        exit(7);
     }

   if (!auth_action_ok(action, gid, gl, gn, egid))
     {
        printf("ERROR: ACTION NOT ALLOWED: %s\n", action);
        exit(10);
     }
   /* we can add more levels of auth here */

   /* when mounting, this will match the exact path to the exe,
    * as required in sysactions.conf
    * this is intentionally pedantic for security
    */
   cmd = eina_hash_find(actions, action);
   if (!cmd)
     {
        printf("ERROR: UNDEFINED ACTION: %s\n", action);
        exit(20);
     }

   /* sanitize environment */
#ifdef HAVE_UNSETENV
# define NOENV(x) unsetenv(x)
   /* pass 1 - just nuke known dangerous env vars brutally if possible via
    * unsetenv(). if you don't have unsetenv... there's pass 2 and 3 */
   NOENV("IFS");
   NOENV("CDPATH");
   NOENV("LOCALDOMAIN");
   NOENV("RES_OPTIONS");
   NOENV("HOSTALIASES");
   NOENV("NLSPATH");
   NOENV("PATH_LOCALE");
   NOENV("COLORTERM");
   NOENV("LANG");
   NOENV("LANGUAGE");
   NOENV("LINGUAS");
   NOENV("TERM");
   NOENV("LD_PRELOAD");
   NOENV("LD_LIBRARY_PATH");
   NOENV("SHLIB_PATH");
   NOENV("LIBPATH");
   NOENV("AUTHSTATE");
   NOENV("DYLD_*");
   NOENV("KRB_CONF*");
   NOENV("KRBCONFDIR");
   NOENV("KRBTKFILE");
   NOENV("KRB5_CONFIG*");
   NOENV("KRB5_KTNAME");
   NOENV("VAR_ACE");
   NOENV("USR_ACE");
   NOENV("DLC_ACE");
   NOENV("TERMINFO");
   NOENV("TERMINFO_DIRS");
   NOENV("TERMPATH");
   NOENV("TERMCAP");
   NOENV("ENV");
   NOENV("BASH_ENV");
   NOENV("PS4");
   NOENV("GLOBIGNORE");
   NOENV("SHELLOPTS");
   NOENV("JAVA_TOOL_OPTIONS");
   NOENV("PERLIO_DEBUG");
   NOENV("PERLLIB");
   NOENV("PERL5LIB");
   NOENV("PERL5OPT");
   NOENV("PERL5DB");
   NOENV("FPATH");
   NOENV("NULLCMD");
   NOENV("READNULLCMD");
   NOENV("ZDOTDIR");
   NOENV("TMPPREFIX");
   NOENV("PYTHONPATH");
   NOENV("PYTHONHOME");
   NOENV("PYTHONINSPECT");
   NOENV("RUBYLIB");
   NOENV("RUBYOPT");
# ifdef HAVE_ENVIRON
   if (environ)
     {
        int again;
        char *tmp, *p;

        /* go over environment array again and again... safely */
        do
          {
             again = 0;

             /* walk through and find first entry that we don't like */
             for (i = 0; environ[i]; i++)
               {
                  /* if it begins with any of these, it's possibly nasty */
                  if ((!strncmp(environ[i], "LD_", 3)) ||
                      (!strncmp(environ[i], "_RLD_", 5)) ||
                      (!strncmp(environ[i], "LC_", 3)) ||
                      (!strncmp(environ[i], "LDR_", 3)))
                    {
                       /* unset it */
                       tmp = strdup(environ[i]);
                       if (!tmp) abort();
                       p = strchr(tmp, '=');
                       if (!p) abort();
                       *p = 0;
                       NOENV(tmp);
                       free(tmp);
                       /* and mark our do to try again from the start in case
                        * unsetenv changes environ ptr */
                       again = 1;
                       break;
                    }
               }
          }
        while (again);
     }
# endif
#endif

   /* pass 2 - clear entire environment so it doesn't exist at all. if you
    * can't do this... you're possibly in trouble... but the worst is still
    * fixed in pass 3 */
#ifdef HAVE_CLEARENV
   clearenv();
#else
# ifdef HAVE_ENVIRON
   environ = NULL;
# endif
#endif

   /* pass 3 - set path and ifs to minimal defaults */
   putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin");
   putenv("IFS= \t\n");

   if ((!test)
#ifdef HAVE_EEZE_MOUNT
       && (!mnt)
#endif
       )
     return system(cmd);
#ifdef HAVE_EEZE_MOUNT
   if (mnt)
     {
        int ret = 0;
        const char *mp = NULL;
        Eina_Strbuf *buf = NULL;

        if (!act) exit(40);
        if (!mount_args_check(argc, argv, act)) exit(40);
        /* all options are deemed safe at this point, so away we go! */
        if (!strcmp(act, "mount"))
          {
             struct stat s;

             mp = argv[5];
             if (stat("/media", &s))
               {
                  mode_t um;

                  um = umask(0);
                  if (mkdir("/media", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
                    {
                       printf("ERROR: COULD NOT CREATE DIRECTORY /media\n");
                       exit(40);
                    }
                  umask(um);
               }
             else if (!S_ISDIR(s.st_mode))
               {
                  printf("ERROR: NOT A DIRECTORY: /media\n");
                  exit(40);
               }

             if (stat(mp, &s))
               {
                  mode_t um;

                  um = umask(0);
                  if (mkdir(mp, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH))
                    {
                       printf("ERROR: COULD NOT CREATE DIRECTORY %s\n", mp);
                       exit(40);
                    }
                  umask(um);
               }
             else if (!S_ISDIR(s.st_mode))
               {
                  printf("ERROR: NOT A DIRECTORY: %s\n", mp);
                  exit(40);
               }
          }
        buf = eina_strbuf_new();
        if (!buf) exit(30);
        for (i = 1; i < argc; i++)
          eina_strbuf_append_printf(buf, "%s ", argv[i]);
        ret = system(eina_strbuf_string_get(buf));
        if ((!strcmp(act, "umount")) && (!ret))
          {
             Eina_Iterator *it;
             char path[PATH_MAX];
             const char *s;
             struct stat st;
             Eina_Bool rm = EINA_TRUE;

             mp = strrchr(argv[2], '/');
             if (!mp) return ret;
             snprintf(path, sizeof(path), "/media%s", mp);
             if (stat(path, &st)) return ret;
             if (!S_ISDIR(st.st_mode)) return ret;
             it = eina_file_ls(path);
             EINA_ITERATOR_FOREACH(it, s)
               {
                  /* don't delete any directories with files in them */
                  rm = EINA_FALSE;
                  eina_stringshare_del(s);
               }
             if (rm)
               {
                  if (rmdir(path))
                    printf("ERROR: COULD NOT UNLINK MOUNT POINT %s\n", path);
               }
          }
        return ret;
     }
#endif
   eina_shutdown();

   return 0;
}

/* local subsystem functions */
#ifdef HAVE_EEZE_MOUNT
static Eina_Bool
mountopts_check(const char *opts)
{
   char buf[64];
   const char *p;
   char *end;
   unsigned long muid;
   Eina_Bool nosuid, nodev, noexec, nuid;

   nosuid = nodev = noexec = nuid = EINA_FALSE;

   /* these are the only possible options which can be present here; check them strictly */
   if (eina_strlcpy(buf, opts, sizeof(buf)) >= sizeof(buf)) return EINA_FALSE;
   for (p = buf; p && p[1]; p = strchr(p + 1, ','))
     {
        if (p[0] == ',') p++;
#define CMP(OPT) \
  if (!strncmp(p, OPT, sizeof(OPT) - 1))

        CMP("nosuid,")
        {
           nosuid = EINA_TRUE;
           continue;
        }
        CMP("nodev,")
        {
           nodev = EINA_TRUE;
           continue;
        }
        CMP("noexec,")
        {
           noexec = EINA_TRUE;
           continue;
        }
        CMP("utf8,") continue;
        CMP("utf8=0,") continue;
        CMP("utf8=1,") continue;
        CMP("iocharset=utf8,") continue;
        CMP("uid=")
        {
           p += 4;
           errno = 0;
           muid = strtoul(p, &end, 10);
           if (muid == ULONG_MAX) return EINA_FALSE;
           if (errno) return EINA_FALSE;
           if (end[0] != ',') return EINA_FALSE;
           if (muid != uid) return EINA_FALSE;
           nuid = EINA_TRUE;
           continue;
        }
        return EINA_FALSE;
     }
   if ((!nosuid) || (!nodev) || (!noexec) || (!nuid)) return EINA_FALSE;
   return EINA_TRUE;
}

static Eina_Bool
check_uuid(const char *uuid)
{
   const char *p;

   for (p = uuid; p[0]; p++)
     if ((!isalnum(*p)) && (*p != '-')) return EINA_FALSE;
   return EINA_TRUE;
}

static Eina_Bool
mount_args_check(int argc, char **argv, const char *action)
{
   Eina_Bool opts = EINA_FALSE;
   struct stat st;
   const char *node;
   char buf[PATH_MAX];

   if (!strcmp(action, "mount"))
     {
        /* will ALWAYS be:
           /path/to/mount -o nosuid,uid=XYZ,[utf8,] UUID=XXXX-XXXX[-XXXX-XXXX] /media/$devnode
         */
        if (argc != 6) return EINA_FALSE;
        if (argv[2][0] == '-')
          {
             /* disallow any -options other than -o */
             if (strcmp(argv[2], "-o")) return EINA_FALSE;
             opts = mountopts_check(argv[3]);
          }
        if (!opts) return EINA_FALSE;
        if (!strncmp(argv[4], "UUID=", sizeof("UUID=") - 1))
          {
             if (!check_uuid(argv[4] + 5)) return EINA_FALSE;
          }
        else
          {
             if (strncmp(argv[4], "/dev/", 5)) return EINA_FALSE;
             if (stat(argv[4], &st)) return EINA_FALSE;
          }

        node = strrchr(argv[5], '/');
        if (!node) return EINA_FALSE;
        if (!node[1]) return EINA_FALSE;
        if (node - argv[5] != 6) return EINA_FALSE;
        snprintf(buf, sizeof(buf), "/dev%s", node);
        if (stat(buf, &st)) return EINA_FALSE;
     }
   else if (!strcmp(action, "umount"))
     {
        /* will ALWAYS be:
           /path/to/umount /dev/$devnode
         */
        if (argc != 3) return EINA_FALSE;
        if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
        if (stat(argv[2], &st)) return EINA_FALSE;
        node = strrchr(argv[2], '/');
        if (!node) return EINA_FALSE;
        if (!node[1]) return EINA_FALSE;
        if (node - argv[2] != 4) return EINA_FALSE;
        /* this is good, but it prevents umounting user-mounted removable media;
         * need to figure out a better way...
         *
           snprintf(buf, sizeof(buf), "/media%s", node);
           if (stat(buf, &st)) return EINA_FALSE;
           if (!S_ISDIR(st.st_mode)) return EINA_FALSE;
         */
     }
   else if (!strcmp(action, "eject"))
     {
        /* will ALWAYS be:
           /path/to/eject /dev/$devnode
         */
        if (argc != 3) return EINA_FALSE;
        if (strncmp(argv[2], "/dev/", 5)) return EINA_FALSE;
        if (stat(argv[2], &st)) return EINA_FALSE;
        node = strrchr(argv[2], '/');
        if (!node) return EINA_FALSE;
        if (!node[1]) return EINA_FALSE;
        if (node - argv[2] != 4) return EINA_FALSE;
     }
   else return EINA_FALSE;
   return EINA_TRUE;
}

#endif

static int
auth_action_ok(char *a,
               gid_t gid,
               gid_t *gl,
               int gn,
               gid_t egid)
{
   struct passwd *pw;
   struct group *gp;
   char *usr = NULL, **grp, *g;
   int ret, i, j;

   pw = getpwuid(uid);
   if (!pw) return 0;
   usr = pw->pw_name;
   if (!usr) return 0;
   grp = alloca(sizeof(char *) * (gn + 1 + 1));
   j = 0;
   gp = getgrgid(gid);
   if (gp)
     {
        grp[j] = gp->gr_name;
        j++;
     }
   for (i = 0; i < gn; i++)
     {
        if (gl[i] != egid)
          {
             gp = getgrgid(gl[i]);
             if (gp)
               {
                  g = alloca(strlen(gp->gr_name) + 1);
                  strcpy(g, gp->gr_name);
                  grp[j] = g;
                  j++;
               }
          }
     }
   grp[j] = NULL;
   /* first stage - check:
    * PREFIX/etc/enlightenment/sysactions.conf
    */
   ret = auth_etc_enlightenment_sysactions(a, usr, grp);
   if (ret == 1) return 1;
   else if (ret == -1)
     return 0;
   /* the DEFAULT - allow */
   return 1;
}

static int
auth_etc_enlightenment_sysactions(char *a,
                                  char *u,
                                  char **g)
{
   FILE *f;
   char file[4096], buf[4096], id[4096], ugname[4096], perm[4096], act[4096];
   char *p, *pp, *s, **gp;
   int ok = 0;
   size_t len, line = 0;
   int allow = 0;
   int deny = 0;

   snprintf(file, sizeof(file), "/etc/enlightenment/sysactions.conf");
   f = fopen(file, "r");
   if (!f)
     {
        snprintf(file, sizeof(file), PACKAGE_SYSCONF_DIR "/enlightenment/sysactions.conf");
        f = fopen(file, "r");
        if (!f) return 0;
     }

   auth_etc_enlightenment_sysactions_perm(file);

   while (fgets(buf, sizeof(buf), f))
     {
        line++;
        len = strlen(buf);
        if (len < 1) continue;
        if (buf[len - 1] == '\n') buf[len - 1] = 0;
        /* format:
         *
         * # comment
         * user:  username  [allow:|deny:] halt reboot ...
         * group: groupname [allow:|deny:] suspend ...
         */
        if (buf[0] == '#') continue;
        p = buf;
        p = get_word(p, id);
        p = get_word(p, ugname);
        pp = p;
        p = get_word(p, perm);
        allow = 0;
        deny = 0;
        if (!strcmp(id, "user:"))
          {
             if (eina_fnmatch(ugname, u, 0))
               {
                  if (!strcmp(perm, "allow:")) allow = 1;
                  else if (!strcmp(perm, "deny:"))
                    deny = 1;
                  else
                    goto malformed;
               }
             else
               continue;
          }
        else if (!strcmp(id, "group:"))
          {
             Eina_Bool matched = EINA_FALSE;

             for (gp = g; *gp; gp++)
               {
                  if (eina_fnmatch(ugname, *gp, 0))
                    {
                       matched = EINA_TRUE;
                       if (!strcmp(perm, "allow:")) allow = 1;
                       else if (!strcmp(perm, "deny:"))
                         deny = 1;
                       else
                         goto malformed;
                    }
               }
             if (!matched) continue;
          }
        else if (!strcmp(id, "action:"))
          {
             while ((*pp) && (isspace(*pp)))
               pp++;
             s = eina_hash_find(actions, ugname);
             if (s) eina_hash_del(actions, ugname, s);
             if (!actions) actions = eina_hash_string_superfast_new(free);
             eina_hash_add(actions, ugname, strdup(pp));
             continue;
          }
        else if (id[0] == 0)
          continue;
        else
          goto malformed;

        for (;; )
          {
             p = get_word(p, act);
             if (act[0] == 0) break;
             if (eina_fnmatch(act, a, 0))
               {
                  if (allow) ok = 1;
                  else if (deny)
                    ok = -1;
                  goto done;
               }
          }

        continue;
malformed:
        printf("WARNING: %s:%zu\n"
               "LINE: '%s'\n"
               "MALFORMED LINE. SKIPPED.\n",
               file, line, buf);
     }
done:
   fclose(f);
   return ok;
}

static void
auth_etc_enlightenment_sysactions_perm(char *path)
{
   struct stat st;
   if (stat(path, &st) == -1)
     return;

   if ((st.st_mode & S_IWGRP) || (st.st_mode & S_IXGRP) ||
       (st.st_mode & S_IWOTH) || (st.st_mode & S_IXOTH))
     {
        printf("ERROR: CONFIGURATION FILE HAS BAD PERMISSIONS (writable by group and/or others)\n");
        exit(10);
     }
}

static char *
get_word(char *s,
         char *d)
{
   char *p1, *p2;

   p1 = s;
   p2 = d;
   while (*p1)
     {
        if (p2 == d)
          {
             if (isspace(*p1))
               {
                  p1++;
                  continue;
               }
          }
        if (isspace(*p1)) break;
        *p2 = *p1;
        p1++;
        p2++;
     }
   *p2 = 0;
   return p1;
}

#include "e.h"

typedef struct
{
   char cmd[24];
   int  size;
} Message_Head;

typedef struct
{
   void (*func) (void *data, const char *params);
   void *data;
   Eina_Bool delete_me : 1;
   Eina_Bool just_added : 1;
} Handler;

static Ecore_Exe *_system_exe = NULL;
static Ecore_Event_Handler *_handler_del = NULL;
static Ecore_Event_Handler *_handler_data = NULL;
static Eina_Binbuf *_msg_buf = NULL;
static Eina_Hash *_handlers = NULL;
static int _handlers_busy = 0;
static Ecore_Timer *_error_dialog_timer = NULL;
static double _last_spawn = 0.0;
static int _respawn_count = 0;

static Eina_Bool
_cb_dialog_timer(void *data)
{
   int exit_code = (int)(long)data;

   _error_dialog_timer = NULL;

        if (exit_code ==  0) // can't even do ecore_exe_run...
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because ecore_exe_run() failed."));
   else if (exit_code == 31) // can't get passwd entry for user
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because your user has no passwd file entry."));
   else if (exit_code == 32) // username is blank in passwd entry
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because your username is blank in the passwd file."));
   else if (exit_code == 33) // can't alloc mem for username
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because it could not allocate memory."));
   else if (exit_code == 34) // can't get group entry for user's group
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because it can't find your user group entry."));
   else if (exit_code == 35) // group name is blank
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because your user group entry is blank."));
   else if (exit_code == 36) // can't setuid to 0
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "since it can't become root. Missing setuid bit?"));
   else if (exit_code == 37) // can't setgid to 0
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "since it can't become group root. Missing setuid bit?"));
   else if (exit_code == 38) // can't get passwd entry for uid 0
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because the passwd file entry for root isn't found."));
   else if (exit_code == 39) // root has no homedir
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because the root home directory is blank."));
   else if (exit_code == 40) // root homedir is not a full path
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because the root home directory is not a full path."));
   else if (exit_code == 41) // root homedir does not resolve to a path
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because the root home directory can't be found."));
   else if (exit_code == 42) // can't change HOME to root homedir
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because it can't change the HOME environment."));
   else if (exit_code == 43) // can't change dir to root homedir
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because it cannot change working directory to root's home."));
   else if (exit_code == 46)    // user denied to all subsystems
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service<br>"
                          "because your user is denied access to all services.<br>"
                          "Please see /etc/enlightenment/system.conf."));
   else
     e_util_dialog_show(_("Error in Enlightenment System Service"),
                        _("Enlightenment cannot successfully start<br>"
                          "the enlightenment_system service for<br>"
                          "some unknown reason."));
   return EINA_FALSE;
}

static void
_system_spawn_error(int exit_code)
{
   if (_error_dialog_timer) ecore_timer_del(_error_dialog_timer);
   _error_dialog_timer = ecore_timer_add(5.0, _cb_dialog_timer,
                                         (void *)(long)exit_code);
}

static void
_system_spawn(void)
{
   char buf[PATH_MAX];
   double t = ecore_time_get();

   if ((t - _last_spawn) < 10.0) _respawn_count++;
   else _respawn_count = 0;
   if (_respawn_count > 5) return;
   snprintf(buf, sizeof(buf),
            "%s/enlightenment/utils/enlightenment_system", e_prefix_lib_get());
   _system_exe = ecore_exe_pipe_run
     (buf, ECORE_EXE_NOT_LEADER | ECORE_EXE_TERM_WITH_PARENT |
      ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_WRITE, NULL);
   if (!_system_exe) _system_spawn_error(0);
}

static Eina_Bool
_system_message_read(void)
{
   Message_Head *head;
   const void *bdata = eina_binbuf_string_get(_msg_buf);
   const char *data = bdata;
   size_t len = eina_binbuf_length_get(_msg_buf);
   Eina_Binbuf *buf2;

   if (!data) return EINA_FALSE;
   if (len < sizeof(Message_Head)) return EINA_FALSE;
   head = (Message_Head *)bdata;
   head->cmd[23] = 0;
   if (len < (sizeof(Message_Head) + head->size)) return EINA_FALSE;
   if (_handlers)
     {
        Eina_List *list, *l, *ll, *plist;
        Handler *h;
        int del_count = 0;

        _handlers_busy++;
        list = plist = eina_hash_find(_handlers, head->cmd);
        EINA_LIST_FOREACH(list, l, h)
          {
             if ((!h->delete_me) && (!h->just_added))
               {
                  if (head->size == 0) h->func(h->data, NULL);
                  else
                    {
                       if ((data + sizeof(Message_Head))[head->size - 1] == 0)
                         h->func(h->data,
                                 (const char *)(data + sizeof(Message_Head)));
                    }
               }
          }
        _handlers_busy--;
        if (_handlers_busy == 0)
          {
             EINA_LIST_FOREACH_SAFE(list, l, ll, h)
               {
                  h->just_added = EINA_FALSE;
                  if (h->delete_me)
                    {
                       list = eina_list_remove_list(list, l);
                       del_count++;
                       free(h);
                    }
               }
          }
        if (del_count > 0)
          {
             eina_hash_del(_handlers, head->cmd, plist);
             if (list)
               eina_hash_add(_handlers, head->cmd, list);
          }
     }
   buf2 = eina_binbuf_new();
   if (buf2)
     {
        eina_binbuf_append_length
          (buf2,
           (const unsigned char *)data + sizeof(Message_Head) + head->size,
           len - (sizeof(Message_Head) + head->size));
        eina_binbuf_free(_msg_buf);
        _msg_buf = buf2;
     }
   return EINA_TRUE;
}

static Eina_Bool
_cb_exe_del(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Del *ev = event;

   if (ev->exe != _system_exe) return ECORE_CALLBACK_PASS_ON;
   if ((ev->exit_code == 31) || // can't get passwd entry for user
       (ev->exit_code == 32) || // username is blank in passwd entry
       (ev->exit_code == 33) || // can't alloc mem for username
       (ev->exit_code == 34) || // can't get group entry for user's group
       (ev->exit_code == 35) || // group name is blank
       (ev->exit_code == 36) || // can't setuid to 0
       (ev->exit_code == 37) || // can't setgid to 0
       (ev->exit_code == 38) || // can't get passwd entry for uid 0
       (ev->exit_code == 39) || // root has no homedir
       (ev->exit_code == 40) || // root homedir is not a full path
       (ev->exit_code == 41) || // root homedir does not resolve to a path
       (ev->exit_code == 42) || // can't change HOME to root homedir
       (ev->exit_code == 43) || // can't change dir to root homedir
       (ev->exit_code == 46)    // user denied to all subsystems
      )
     {
        // didn't run because it literally refused....
        _system_spawn_error(ev->exit_code);
     }
   else
     {
        // it died for some other reason - restart it - maybe crashed?
        if (_msg_buf) eina_binbuf_free(_msg_buf);
        _msg_buf = eina_binbuf_new();
        _system_spawn();
     }
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_cb_exe_data(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Data *ev = event;

   if (ev->exe != _system_exe) return ECORE_CALLBACK_PASS_ON;
   // this is dumb, but it will work as this is avery low bandwidth
   // i/o channel to/from the child exe stdin/out
   if (_msg_buf)
     {
        eina_binbuf_append_length(_msg_buf, ev->data, ev->size);
        while (_msg_buf && _system_message_read());
     }
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_handler_list_free(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
{
   Eina_List *list = data;
   Handler *h;

   EINA_LIST_FREE(list, h) free(h);
   return EINA_TRUE;
}

/* externally accessible functions */
EINTERN int
e_system_init(void)
{
   // XXX:
   //
   // if exe_data - parse/get data
   //   ... per message - call registered cb's for that msg
   _handler_del = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _cb_exe_del, NULL);
   _handler_data = ecore_event_handler_add(ECORE_EXE_EVENT_DATA, _cb_exe_data, NULL);
   _msg_buf = eina_binbuf_new();
   _handlers = eina_hash_string_superfast_new(NULL);
   _system_spawn();
   return 1;
}

EINTERN int
e_system_shutdown(void)
{
   if (_msg_buf) eina_binbuf_free(_msg_buf);
   if (_handler_del) ecore_event_handler_del(_handler_del);
   if (_handler_data) ecore_event_handler_del(_handler_data);
   if (_system_exe)
     {
        ecore_exe_interrupt(_system_exe);
        ecore_exe_quit(_system_exe);
        ecore_exe_terminate(_system_exe);
        ecore_exe_kill(_system_exe);
        ecore_exe_free(_system_exe);
     }
   if (_handlers)
     {
        eina_hash_foreach(_handlers, _handler_list_free, NULL);
        eina_hash_free(_handlers);
     }
   if (_error_dialog_timer) ecore_timer_del(_error_dialog_timer);
   _msg_buf = NULL;
   _handler_del = NULL;
   _handler_data = NULL;
   _system_exe = NULL;
   _handlers = NULL;
   _error_dialog_timer = NULL;
   return 1;
}

E_API void EINA_PRINTF(2, 3)
e_system_send(const char *cmd, const char *fmt, ...)
{
   char *buf = NULL, stack_buf[4096];
   Message_Head head;
   size_t len = strlen(cmd);
   int printed = 0;
   va_list ap;

   if (!_system_exe)
     {
        ERR("Trying to send system command to non-existent process");
        return;
     }
   if (len > 23)
     {
        ERR("Trying to send command of length %i (max 23)", (int)len);
        return;
     }
   if (fmt)
     {
        buf = stack_buf;
        va_start(ap, fmt);
        printed = vsnprintf(stack_buf, sizeof(stack_buf), fmt, ap);
        va_end(ap);
        if ((size_t)printed >= (sizeof(stack_buf) - 1))
          {
             buf = malloc(printed + 1);
             if (!buf) goto end;
             va_start(ap, fmt);
             printed = vsnprintf(buf, printed + 1, fmt, ap);
             va_end(ap);
          }
     }

   memset(head.cmd, 0, sizeof(head.cmd));
   memcpy(head.cmd, cmd, len);
   if (printed > 0) head.size = printed + 1;
   else head.size = 0;
   ecore_exe_send(_system_exe, &head, sizeof(head));
   if ((buf) && (head.size > 0))
     {
        ecore_exe_send(_system_exe, buf, head.size);
     }
end:
   if (buf != stack_buf) free(buf);
}

E_API void
e_system_handler_add(const char *cmd, void (*func) (void *data, const char *params), void *data)
{
   Eina_List *list;
   Handler *h;

   if (!_handlers)
     {
        ERR("Trying to add system handler to NULL handler hash");
        return;
     }
   h = calloc(1, sizeof(Handler));
   if (!h) return;
   h->func = func;
   h->data = data;
   if (_handlers_busy > 0) h->just_added = EINA_TRUE;
   list = eina_hash_find(_handlers, cmd);
   eina_hash_del(_handlers, cmd, list);
   list = eina_list_append(list, h);
   eina_hash_add(_handlers, cmd, list);
}

E_API void
e_system_handler_del(const char *cmd, void (*func) (void *data, const char *params), void *data)
{
   Eina_List *list, *l;
   Handler *h;

  if (!_handlers)
     {
        ERR("Trying to del system handler to NULL handler hash");
        return;
     }
   list = eina_hash_find(_handlers, cmd);
   if (list)
     {
        eina_hash_del(_handlers, cmd, list);
        EINA_LIST_FOREACH(list, l, h)
          {
             if ((h->func == func) && (h->data == data))
               {
                  if (_handlers_busy > 0)
                    h->delete_me = EINA_TRUE;
                  else
                    {
                       list = eina_list_remove_list(list, l);
                       free(h);
                    }
                  break;
               }
          }
        if (list)
          eina_hash_add(_handlers, cmd, list);
     }
}

#include "e.h"

static void _e_test_internal(void);

#ifdef DESKMIRROR_TEST

static Eina_Bool
deskmirror_test(void *d EINA_UNUSED)
{
   E_Zone *zone;
   Evas_Object *o;

   zone = e_zone_current_get();
   o = e_deskmirror_add(e_desk_current_get(zone), 0, 0);
   evas_object_move(o, zone->x + zone->w - (zone->w / 4), zone->y + zone->h / 2);
   evas_object_resize(o, zone->w / 4, zone->h / 4);
   evas_object_layer_set(o, E_LAYER_POPUP);
   evas_object_show(o);

   o = e_deskmirror_add(e_desk_current_get(zone), 0, 0);
   evas_object_move(o, zone->x, zone->y + zone->h / 2);
   evas_object_resize(o, zone->w / 2, zone->h / 2);
   evas_object_layer_set(o, E_LAYER_POPUP);
   evas_object_show(o);
   return EINA_FALSE;
}

#endif

E_API void
e_test(void)
{
   _e_test_internal();

#ifdef DESKMIRROR_TEST
   ecore_timer_loop_add(2.0, deskmirror_test, NULL);
#endif
}

#if 0
static int
_e_test_timer(void *data)
{
   E_Menu *m;

   m = data;
   if (m)
     {
        e_menu_deactivate(m);
        e_object_del(E_OBJECT(m));
        ecore_timer_loop_add(0.05, _e_test_timer, NULL);
        return 0;
     }
   m = e_int_menus_main_new();
   e_menu_activate_mouse(m,
                         eina_list_data_get(e_comp->zones),
                         0, 0, 1, 1, E_MENU_POP_DIRECTION_DOWN, 0);
   ecore_timer_loop_add(0.05, _e_test_timer, m);
   return 0;
}

static void
_e_test_internal(void)
{
   _e_test_timer(NULL);
}

#elif 0
static void
_e_test_resize(E_Win *win)
{
   Evas_Object *o;

   o = win->data;
   printf("RESIZE %i %i\n", win->w, win->h);
   evas_object_resize(o, win->w, win->h);
   evas_object_color_set(o, rand() & 0xff, rand() & 0xff, rand() & 0xff, 255);
}

static void
_e_test_delete(E_Win *win)
{
   printf("DEL!\n");
   e_object_del(E_OBJECT(win));
}

static void
_e_test_internal(void)
{
   E_Win *win;
   Evas_Object *o;

   win = e_win_new(e_comp);
   evas_object_resize_callback_set(win, _e_test_resize);
   e_win_delete_callback_set(win, _e_test_delete);
   e_win_placed_set(win, 0);
   evas_object_geometry_set(win, 10, 80, 400, 200);
   e_win_name_class_set(win, "E", "_test_window");
   e_win_title_set(win, "A test window");
   elm_win_raise(win);
   evas_object_show(win);

   o = evas_object_rectangle_add(evas_object_evas_get(win));
   evas_object_color_set(o, 255, 200, 100, 255);
   evas_object_resize(o, 400, 200);
   evas_object_show(o);

   win->data = o;
}

#elif 0
static int
_e_test_timer(void *data)
{
   E_Menu *m;
   static int y = 0;

   m = data;
   ecore_x_pointer_warp(m->evas_win, 20, y);
   y += 10;
   if (y > m->cur.h) y = 0;
   return 1;
}

static void
_e_test_internal(void)
{
   E_Menu *m;

   m = e_int_menus_main_new();
   e_menu_activate_mouse(m,
                         eina_list_data_get(e_comp->zones),
                         0, 0, 1, 1, E_MENU_POP_DIRECTION_DOWN, 0);
   ecore_timer_loop_add(0.02, _e_test_timer, m);
}

#elif 0
static void
_e_test_dialog_del(void *obj)
{
   E_Dialog *dia;

   dia = obj;
   printf("dialog delete hook!\n");
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;

   dia = e_dialog_new(c, "E", "_test");
   e_object_del_attach_func_set(E_OBJECT(dia), _e_test_dialog_del);
   e_dialog_title_set(dia, "A Test Dialog");
   e_dialog_text_set(dia, "A Test Dialog<ps/>And another line<ps/><hilight>Highlighted Text</hilight>");
   e_dialog_icon_set(dia, "preference-plugin", 64);
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_button_add(dia, "Apply", "system-restart", NULL, NULL);
   e_dialog_button_add(dia, "Cancel", "application-exit", NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
}

#elif 0
static void
_e_test_click(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
#if 1
   double size;

   size = (double)(rand() % 1000) / 999;
   evas_object_resize(obj, size * 1024, size * 768);
#else
   e_scrollframe_child_region_show(data, 1024, 768, 100, 100);
#endif
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *o, *o2, *o3;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_icon_add(dia->win->evas);
   e_icon_file_set(o, "/home/raster/scroll.png");
   evas_object_resize(o, 1024, 768);
   evas_object_focus_set(o, 1);
   evas_object_show(o);

   o2 = e_scrollframe_add(dia->win->evas);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_test_click, o2);
//   e_scrollframe_custom_theme_set(o2, "base/theme/widgets", "e/widgets/scrollframe");
   evas_object_show(o2);
#if 0
   o3 = e_pan_add(dia->win->evas);
   e_pan_child_set(o3, o);
   e_scrollframe_extern_pan_set(o2, o3, e_pan_set, e_pan_get, e_pan_max_get, e_pan_child_size_get);
#else
   e_scrollframe_child_set(o2, o);
#endif

   e_dialog_content_set(dia, o2, 500, 300);
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);

   evas_object_focus_set(o, 1);
}

#elif 0
static E_Dialog *
_e_test_dia(E_Comp *c)
{
   E_Config_Dialog *dia;

   dia = e_int_config_modules(c);
   return dia;
}

static E_Comp *tcon = NULL;

static int
_e_test_timer(void *data)
{
   E_Config_Dialog *dia;

   if (data == NULL)
     {
        dia = _e_test_dia(tcon);
        ecore_timer_loop_add(0.2, _e_test_timer, dia);
     }
   else
     {
        dia = data;
        e_object_del(E_OBJECT(dia));
        ecore_timer_loop_add(0.2, _e_test_timer, NULL);
     }
   return 0;
}

static void
_e_test_internal(void)
{
   tcon = c;
   _e_test_timer(NULL);
}

#elif 0

struct _tmp
{
   Evas_Object *ilist, *scrollframe;
};

static struct _tmp tmp = {NULL, NULL};

static void
_e_test_sel(void *data, void *data2)
{
   Evas_Coord x, y, w, h;

   e_ilist_selected_geometry_get(tmp.ilist, &x, &y, &w, &h);
   e_scrollframe_child_region_show(tmp.scrollframe, x, y, w, h);
}

static void
_e_test_resize(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Evas_Coord mw, mh, vw, vh, w, h;

   e_scrollframe_child_viewport_size_get(obj, &vw, &vh);
   e_ilist_size_min_get(data, &mw, &mh);
   evas_object_geometry_get(data, NULL, NULL, &w, &h);
   if (vw >= mw)
     {
        if (w != vw) evas_object_resize(data, vw, h);
     }
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Coord mw, mh, vw, vh;
   Evas_Object *o, *o2, *o3, *o4;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_ilist_add(dia->win->evas);
   e_ilist_icon_size_set(o, 80, 48);

   o3 = e_livethumb_add(dia->win->evas);
   e_livethumb_vsize_set(o3, 160, 96);
   o4 = edje_object_add(e_livethumb_evas_get(o3));
   e_theme_edje_object_set(o4, "base/theme/borders",
                           "e/widgets/border/default/border");
   e_livethumb_thumb_set(o3, o4);
   e_ilist_append(o, o3, "Item 1", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/palette.png");
   e_ilist_append(o, o3, "Item 2 (Some really long text goes here for testing)", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/mozilla.png");
   e_ilist_append(o, o3, "Item 3 (Medium length)", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/trash_open.png");
   e_ilist_append(o, o3, "Item POOP", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/watch.png");
   e_ilist_append(o, o3, "Item BLING BLING", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/quake3.png");
   e_ilist_append(o, o3, "Sukebelinth", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/opera6.png");
   e_ilist_append(o, o3, "A header", 1, NULL, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/opera6.png");
   e_ilist_append(o, o3, "Panties", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/drawer_open.png");
   e_ilist_append(o, o3, "Flimbert the cagey", 0, _e_test_sel, NULL, NULL, NULL);

   o3 = e_icon_add(dia->win->evas);
   e_icon_file_set(o3, "/home/raster/C/stuff/icons/cd.png");
   e_ilist_append(o, o3, "Norbert", 0, _e_test_sel, NULL, NULL, NULL);

   e_ilist_size_min_get(o, &mw, &mh);
   evas_object_resize(o, mw, mh);
   evas_object_focus_set(o, 1);
   evas_object_show(o);

   o2 = e_scrollframe_add(dia->win->evas);
   evas_object_event_callback_add(o2, EVAS_CALLBACK_RESIZE, _e_test_resize, o);
   evas_object_resize(o2, mw, 150);
   evas_object_show(o2);
   e_scrollframe_child_set(o2, o);

   e_scrollframe_child_viewport_size_get(o2, &vw, &vh);
   e_dialog_content_set(dia, o2, 200, 150);
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_resizable_set(dia, 1);
   e_dialog_show(dia);

   tmp.ilist = o;
   tmp.scrollframe = o2;

   evas_object_focus_set(o, 1);
}

#elif 0
static void
_e_test_cb_e_smart_pan_changed_hook(void *data, Evas_Object *obj, void *event_info)
{
   printf("VAL: %3.3f\n", e_slider_value_get(obj));
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord mw, mh;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_slider_add(dia->win->evas);
   e_slider_orientation_set(o, 1);
   e_slider_value_set(o, 0.5);
   e_slider_value_step_count_set(o, 4);
   e_slider_value_format_display_set(o, "%1.2f V");
   e_slider_size_min_get(o, &mw, &mh);
   evas_object_smart_callback_add(o, "changed", _e_test_cb_e_smart_pan_changed_hook, NULL);
   evas_object_show(o);

   e_dialog_content_set(dia, o, 240 + mw, mh);

   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);

   evas_object_focus_set(o, 1);
}

#elif 0
static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord mw, mh;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_widget_textblock_add(dia->win->evas);
#if 0
   e_widget_textblock_markup_set(o,
                                 "<title>A title</title>"
                                 "This is some text<ps/>"
                                 "Blah blah<ps/>"
                                 "<hilight>highlighted text</hilight><ps/>"
                                 "<ps/>"
                                 "More lines of text<ps/>"
                                 "And yet more lines of text<ps/>"
                                 "A very very long line of text that SHOULD be getting word wrapped because it is so long.<ps/>"
                                 "And another line<ps/>"
                                 "Some more<ps/>"
                                 "Smelly fish on a stick<ps/>"
                                 "Whatever."
                                 );
#else
   e_widget_textblock_plain_set(o,
                                "And here is some plaintext\n"
                                "with some newlines & other special characters\n"
                                "that should get escaped like < and >.\n"
                                "\n"
                                "\tTabs should become 8 spaces too.\n"
                                );
#endif
   evas_object_show(o);

   e_dialog_content_set(dia, o, 160, 160);

   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);

   evas_object_focus_set(o, 1);
}

#elif 0
static void
_e_test_cb_button(void *data1, void *data2)
{
   e_fm2_parent_go(data1);
}

static void
_e_test_cb_changed(void *data, Evas_Object *obj, void *event_info)
{
   if (!e_fm2_has_parent_get(obj)) e_widget_disabled_set(data, 1);
   else e_widget_disabled_set(data, 0);
}

static void
_e_test_cb_favorites_selected(void *data, Evas_Object *obj, void *event_info)
{
   Eina_List *selected;
   E_Fm2_Icon_Info *ici;

   printf("FAV SELECTED\n");
   selected = e_fm2_selected_list_get(obj);
   if (!selected) return;
   ici = eina_list_data_get(selected);
   if ((ici->link) && (ici->mount))
     e_fm2_path_set(data, ici->link, "/");
   else if (ici->link)
     e_fm2_path_set(data, NULL, ici->link);
// FIXME: this should happen on the scrollframe not the fm obj
//   e_widget_scrollframe_child_pos_set(data, 0, 0);
   eina_list_free(selected);
}

static void
_e_test_cb_favorites_files_changed(void *data, Evas_Object *obj, void *event_info)
{
   Eina_List *icons, *l;
   E_Fm2_Icon_Info *ici;
   const char *realpath;
   char *p1, *p2;

   printf("FAV LIST CHANGE!\n");
   icons = e_fm2_all_list_get(obj);
   if (!icons) return;
   realpath = e_fm2_real_path_get(data);
   p1 = ecore_file_realpath(realpath);
   if (!p1) goto done;
   EINA_LIST_FOREACH(icons, l, ici)
     {
        if (ici->link)
          {
             p2 = ecore_file_realpath(ici->link);
             if (!strcmp(p1, p2))
               {
                  e_fm2_select_set(obj, ici->file);
                  E_FREE(p2);
                  goto done;
               }
             E_FREE(p2);
          }
     }
done:
   E_FREE(p1);
   eina_list_free(icons);
}

static void
_e_test_cb_selected(void *data, Evas_Object *obj, void *event_info)
{
   printf("SELECTED!\n");
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *ofm, *ofm2, *of, *ob, *ot;
   Evas_Coord mw, mh;
   E_Fm2_Config fmc;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   /* a table for layout */
   ot = e_widget_table_add(dia->win->evas, 0);

   /* actual files */
   ofm = e_fm2_add(dia->win->evas);

   memset(&fmc, 0, sizeof(E_Fm2_Config));
   fmc.view.mode = E_FM2_VIEW_MODE_LIST;
   fmc.view.open_dirs_in_place = 1;
   fmc.view.selector = 1;
   fmc.view.single_click = 0;
   fmc.view.no_subdir_jump = 0;
   fmc.icon.list.w = 24;
   fmc.icon.list.h = 24;
   fmc.icon.fixed.w = 1;
   fmc.icon.fixed.h = 1;
   fmc.icon.extension.show = 0;
   fmc.icon.key_hint = NULL;
   fmc.list.sort.no_case = 1;
   fmc.list.sort.dirs.first = 1;
   fmc.list.sort.dirs.last = 0;
   fmc.selection.single = 1;
   fmc.selection.windows_modifiers = 0;
   e_fm2_config_set(ofm, &fmc);

   e_fm2_path_set(ofm, "~/", "/");
   ob = e_widget_button_add(dia->win->evas, "Up a directory", NULL,
                            _e_test_cb_button, ofm, NULL);
   e_widget_table_object_append(ot, ob, 1, 0, 1, 1, 0, 0, 1, 0);
   evas_object_show(ob);
   evas_object_smart_callback_add(ofm, "changed", _e_test_cb_changed, ob);
   evas_object_smart_callback_add(ofm, "selected", _e_test_cb_selected, NULL);
   of = e_widget_scrollframe_pan_add(dia->win->evas, ofm,
                                     e_fm2_pan_set, e_fm2_pan_get,
                                     e_fm2_pan_max_get, e_fm2_pan_child_size_get);
   e_widget_size_min_set(of, 128, 128);
   e_widget_table_object_append(ot, of, 1, 1, 1, 1, 1, 1, 1, 1);
   evas_object_show(ofm);
   evas_object_show(of);

   ofm2 = ofm;

   /* shortcut list */
   ofm = e_fm2_add(dia->win->evas);

   memset(&fmc, 0, sizeof(E_Fm2_Config));
   fmc.view.mode = E_FM2_VIEW_MODE_LIST;
   fmc.view.open_dirs_in_place = 1;
   fmc.view.selector = 1;
   fmc.view.single_click = 1;
   fmc.view.no_subdir_jump = 1;
   fmc.icon.list.w = 24;
   fmc.icon.list.h = 24;
   fmc.icon.fixed.w = 1;
   fmc.icon.fixed.h = 1;
   fmc.icon.extension.show = 0;
   fmc.icon.key_hint = NULL;
   fmc.list.sort.no_case = 1;
   fmc.list.sort.dirs.first = 0;
   fmc.list.sort.dirs.last = 0;
   fmc.selection.single = 1;
   fmc.selection.windows_modifiers = 0;
   e_fm2_config_set(ofm, &fmc);

   e_fm2_path_set(ofm, "favorites", "/");
   evas_object_smart_callback_add(ofm, "files_changed", _e_test_cb_favorites_files_changed, ofm2);
   evas_object_smart_callback_add(ofm, "selected", _e_test_cb_favorites_selected, ofm2);
   of = e_widget_scrollframe_pan_add(dia->win->evas, ofm,
                                     e_fm2_pan_set, e_fm2_pan_get,
                                     e_fm2_pan_max_get, e_fm2_pan_child_size_get);
   e_widget_size_min_set(of, 128, 128);
   e_widget_table_object_append(ot, of, 0, 1, 1, 1, 0, 1, 0, 1);
   evas_object_show(ofm);
   evas_object_show(of);

   /* show and pack table */
   evas_object_show(ot);
   e_widget_size_min_get(ot, &mw, &mh);
   e_dialog_content_set(dia, ot, mw, mh);

   /* buttons at the bottom */
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_resizable_set(dia, 1);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   evas_object_resize(dia->win, 400, 300);
}

#elif 0
static void
_e_test_cb_changed(void *data, Evas_Object *obj)
{
//   printf("CHANGED \"%s\"\n", e_widget_fsel_selection_path_get(obj));
}

static void
_e_test_cb_selected(void *data, Evas_Object *obj)
{
   printf("SELECTED \"%s\"\n", e_widget_fsel_selection_path_get(obj));
   e_object_del(E_OBJECT(data));
}

static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *o;
   Evas_Coord mw, mh;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_widget_fsel_add(dia->win->evas, "~/", "/tst", NULL, NULL,
                         _e_test_cb_selected, dia,
                         _e_test_cb_changed, dia, 0);
   evas_object_show(o);
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(dia, o, mw, mh);

   /* buttons at the bottom */
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_resizable_set(dia, 1);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   evas_object_resize(dia->win, 400, 300);
}

#elif 0

static void
_e_test_cb_ok(E_Color_Dialog *dia, E_Color *color, void *data)
{
   printf("Current color: %d, %d, %d\n", color->r, color->g, color->b);
}

static void
_e_test_internal(void)
{
   E_Color_Dialog *d;

   d = e_color_dialog_new(NULL, EINA_FALSE);
   e_color_dialog_show(d);
   e_color_dialog_select_callback_set(d, _e_test_cb_ok, NULL);
}

#elif 0
static void
_e_test_internal(void)
{
   E_Dialog *dia;
   Evas_Object *o, *ob, *of;
   Evas_Coord mw, mh;
   int i;

   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   of = e_scrollframe_add(dia->win->evas);

   ob = elm_box_add(dia->win->evas);
   elm_box_horizontal_set(ob, 0);

   for (i = 0; i < 8; i++)
     {
        o = e_slidesel_add(dia->win->evas);
        e_slidesel_item_distance_set(o, 64);
        e_slidesel_item_add(o, "blah / item 1",
                            "/home/raster/pix/OLD/Download/Crystalline____a.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / smelly fish",
                            "/home/raster/pix/OLD/Download/Reluctant_Sunrise.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / pong",
                            "/home/raster/pix/OLD/Download/Soft_Wings.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / on a stick",
                            "/home/raster/pix/OLD/Download/Stock_rose_1.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / oath",
                            "/home/raster/pix/OLD/Download/The_Eyes_Of_A_Killer.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / yiiihaaaaa",
                            "/home/raster/pix/OLD/Download/lady_bug.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / blah blah blah",
                            "/home/raster/pix/OLD/Download/ocean_rocks_covered_by_ash.jpg",
                            NULL, NULL);
        e_slidesel_item_add(o, "blah / bing bing bing",
                            "/home/raster/pix/OLD/Download/orange_chair_heaven_falling.jpg",
                            NULL, NULL);

        elm_box_pack_end(ob, o);
        elm_box_pack_options_set(o, 1, 1, 1, 0, 0.5, 0.5, 300, 100, 300, 100);
        evas_object_show(o);
     }

   /* fixme... more */
   evas_object_size_hint_min_get(ob, &mw, &mh);
   evas_object_resize(ob, mw, mh);

   e_scrollframe_child_set(of, ob);
   evas_object_show(ob);
//   e_widget_size_min_get(o, &mw, &mh);
   mw = 300; mh = 300;
   e_dialog_content_set(dia, of, mw, mh);
   evas_object_show(of);

   /* buttons at the bottom */
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_resizable_set(dia, 1);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   evas_object_resize(dia->win, 400, 400);
}

static void
_e_test_internal(void)
{
   ecore_timer_loop_add(1.0, _e_test_timer, c);
}

#elif 0

static int
_e_test_timer(void *data)
{
   E_Comp *c;
   E_Dialog *dia;
   Evas_Object *o, *ic;
   Evas_Coord mw, mh;

   c = data;
   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_widget_toolbar_add(dia->win->evas, 48, 48);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_hdd.png");
   e_widget_toolbar_item_append(o, ic, "HDD", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_cd.png");
   e_widget_toolbar_item_append(o, ic, "CD", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_desktop.png");
   e_widget_toolbar_item_append(o, ic, "Desktop", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_home.png");
   e_widget_toolbar_item_append(o, ic, "Home", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_root.png");
   e_widget_toolbar_item_append(o, ic, "Root", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_tmp.png");
   e_widget_toolbar_item_append(o, ic, "Temp", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_globe.png");
   e_widget_toolbar_item_append(o, ic, "World", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_mixer.png");
   e_widget_toolbar_item_append(o, ic, "Mixer", NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_performance.png");
   e_widget_toolbar_item_append(o, ic, "Perform", NULL, NULL, NULL);

   e_widget_toolbar_scrollable_set(o, 1);
   e_widget_toolbar_item_select(o, 1);

   /* fixme... more */
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(dia, o, mw, mh);
   evas_object_show(o);

   /* buttons at the bottom */
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_resizable_set(dia, 1);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   evas_object_resize(dia->win, 400, 200);

   return 0;
}

static void
_e_test_internal(void)
{
   ecore_timer_loop_add(1.0, _e_test_timer, c);
}

#elif 0

static int
_e_test_timer(void *data)
{
   E_Comp *c;
   E_Dialog *dia;
   Evas_Object *o, *ic;
   Evas_Coord mw, mh;

   c = data;
   dia = e_dialog_new(c, "E", "_test");
   e_dialog_title_set(dia, "A Test Dialog");

   o = e_widget_toolbar_add(dia->win->evas, 48, 48);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_hdd.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_cd.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_desktop.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_home.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_root.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_efm_tmp.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_globe.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_mixer.png");
   e_widget_toolbar_item_append(o, ic, NULL, NULL, NULL, NULL);
   ic = e_icon_add(dia->win->evas);
   e_icon_file_set(ic, "/home/raster/C/e17/data/themes/images/icon_performance.png");
   e_widget_toolbar_item_append(o, ic, "x", NULL, NULL, NULL);

   e_widget_toolbar_scrollable_set(o, 1);
   e_widget_toolbar_item_select(o, 1);

   /* fixme... more */
   e_widget_size_min_get(o, &mw, &mh);
   e_dialog_content_set(dia, o, mw, mh);
   evas_object_show(o);

   /* buttons at the bottom */
   e_dialog_button_add(dia, "OK", NULL, NULL, NULL);
   e_dialog_resizable_set(dia, 1);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   evas_object_resize(dia->win, 400, 200);

   return 0;
}

static void
_e_test_internal(void)
{
   ecore_timer_loop_add(1.0, _e_test_timer, c);
}

#elif 0
static Eina_Bool
_e_test_timer(void *data EINA_UNUSED)
{
   static int showhide = 0;
   static E_Menu *m = NULL;

   if (showhide & 0x1)
     {
        printf("del menu\n");
        e_menu_deactivate(m);
        e_object_del(E_OBJECT(m));
     }
   else
     {
        int x, y;
        E_Zone *zone = e_zone_current_get();
        printf("add menu\n");
        m = e_int_menus_main_new();
        m->zone = zone;
        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        e_menu_activate_mouse(m, zone, x, y, 1, 1,
                              E_MENU_POP_DIRECTION_DOWN, 0);
     }
   showhide++;
   printf("%i\n", showhide);
   return EINA_TRUE;
}

static void
_e_test_internal(void)
{
   ecore_timer_loop_add(2.0, _e_test_timer, NULL);
}

#else
static void
_e_test_internal(void)
{
}

#endif
#include "e.h"

/* local subsystem functions */
static void
_cb_settings_theme(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   e_configure_registry_call("appearance/theme", NULL, NULL);
}

/* local subsystem globals */

/* externally accessible functions */

E_API E_Theme_About *
e_theme_about_new(void)
{
   E_Obj_Dialog *od;

   od = e_obj_dialog_new(_("About Theme"), "E", "_theme_about");
   if (!od) return NULL;
   e_obj_dialog_obj_theme_set(od, "base/theme", "e/theme/about");
   e_obj_dialog_obj_part_text_set(od, "e.text.label", _("Close"));
   e_obj_dialog_obj_part_text_set(od, "e.text.theme", _("Select Theme"));
   edje_object_signal_callback_add(od->bg_object,
                                   "e,action,settings,theme", "",
                                   _cb_settings_theme, od);
   return (E_Theme_About *)od;
}

E_API void
e_theme_about_show(E_Theme_About *about)
{
   e_obj_dialog_show((E_Obj_Dialog *)about);
   e_obj_dialog_icon_set((E_Obj_Dialog *)about, "preferences-desktop-theme");
}

#include "e.h"

static void       e_theme_handler_set(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path);
static int        e_theme_handler_test(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path);

static E_Fm2_Mime_Handler *theme_hdl = NULL;

/* externally accessible functions */

EINTERN int
e_theme_init(void)
{
   /* Register mime handler */
   theme_hdl = e_fm2_mime_handler_new(_("Set As Theme"), "preferences-desktop-theme",
                                      e_theme_handler_set, NULL,
                                      e_theme_handler_test, NULL);
   if (theme_hdl) e_fm2_mime_handler_glob_add(theme_hdl, "*.edj");
   return 1;
}

EINTERN int
e_theme_shutdown(void)
{
   if (theme_hdl)
     {
        e_fm2_mime_handler_glob_del(theme_hdl, "*.edj");
        e_fm2_mime_handler_free(theme_hdl);
     }
   return 1;
}

E_API Eina_List *
e_theme_collection_items_find(const char *base EINA_UNUSED, const char *collname)
{
   Eina_List *list, *list2 = NULL, *l;
   const char *s, *s2;
   size_t len = strlen(collname);

   list = elm_theme_group_base_list(NULL, collname);
   if (!list) return NULL;
   EINA_LIST_FREE(list, s)
     {
        char *trans, *p, *p2;
        size_t slen;

        slen = strlen(s);
        if (slen <= len) goto done;
        trans = alloca(slen + 1);
        if (!trans) goto done;
        memcpy(trans, s, slen + 1);
        p = trans + len;
        if (*p != '/') goto done;
        p++;
        if (*p)
          {
             p2 = strchr(p, '/');
             if (p2) *p2 = 0;
             EINA_LIST_FOREACH(list2, l, s2)
               {
                  if (!strcmp(s2, p)) break;
               }
             if (!l) list2 = eina_list_append(list2, eina_stringshare_add(p));
          }
done:
        eina_stringshare_del(s);
     }
   return list2;
}

E_API int
e_theme_edje_object_set(Evas_Object *o, const char *category EINA_UNUSED, const char *group)
{
   const char *file;

   file = elm_theme_group_path_find(NULL, group);
   if (!file) return 0;
   edje_object_file_set(o, file, group);
   return 1;
}

E_API const char *
e_theme_edje_file_get(const char *category EINA_UNUSED, const char *group)
{
   const char *file = elm_theme_group_path_find(NULL, group);
   if (!file) return "";
   return file;
}

E_API const char *
e_theme_edje_icon_fallback_file_get(const char *group)
{
   const char *file;

   if ((e_config->icon_theme) && (!strncmp(group, "e/icons", 7))) return "";
   file = elm_theme_group_path_find(NULL, group);
   if (!file) return "";
   return file;
}

E_API int
e_theme_transition_find(const char *transition)
{
   Eina_List *trans = NULL;
   int found = 0;
   const char *str;

   trans = e_theme_collection_items_find(NULL, "e/transitions");
   if (eina_list_search_sorted(trans, EINA_COMPARE_CB(strcmp), transition))
     found = 1;
   EINA_LIST_FREE(trans, str) eina_stringshare_del(str);
   return found;
}

E_API Eina_List *
e_theme_transition_list(void)
{
   return e_theme_collection_items_find(NULL, "e/transitions");
}

E_API int
e_theme_border_find(const char *border)
{
   Eina_List *bds = NULL;
   int found = 0;
   const char *str;

   bds = e_theme_collection_items_find(NULL, "e/widgets/border");
   if (eina_list_search_sorted(bds, EINA_COMPARE_CB(strcmp), border))
     found = 1;
   EINA_LIST_FREE(bds, str) eina_stringshare_del(str);
   return found;
}

E_API Eina_List *
e_theme_border_list(void)
{
   Eina_List *list, *l;
   const char *s;

   list = e_theme_collection_items_find(NULL, "e/widgets/border");
   // XXX: a horrible hack due to history and bad group naming choices
   // but filter out volume as a border because it is the volume gadget
   // thing not a border
   EINA_LIST_FOREACH(list, l, s)
     {
        if (!strcmp(s, "volume"))
          {
             list = eina_list_remove_list(list, l);
             eina_stringshare_del(s);
             break;
          }
     }
   return list;
}

E_API int
e_theme_shelf_find(const char *shelf)
{
   Eina_List *shelfs = NULL;
   int found = 0;
   const char *str;

   shelfs = e_theme_collection_items_find(NULL, "e/shelf");
   if (eina_list_search_sorted(shelfs, EINA_COMPARE_CB(strcmp), shelf))
     found = 1;
   EINA_LIST_FREE(shelfs, str) eina_stringshare_del(str);
   return found;
}

E_API Eina_List *
e_theme_shelf_list(void)
{
   return e_theme_collection_items_find(NULL, "e/shelf");
}

E_API int
e_theme_comp_frame_find(const char *comp)
{
   Eina_List *comps = NULL;
   int found = 0;
   const char *str;

   comps = e_theme_collection_items_find(NULL, "e/comp/frame");
   if (eina_list_search_sorted(comps, EINA_COMPARE_CB(strcmp), comp))
     found = 1;
   EINA_LIST_FREE(comps, str) eina_stringshare_del(str);
   return found;
}

E_API Eina_List *
e_theme_comp_frame_list(void)
{
   return e_theme_collection_items_find(NULL, "e/comp/frame");
}

/* local subsystem functions */
static void
e_theme_handler_set(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path)
{
   E_Action *a;
   char buf[PATH_MAX];
   int copy = 1;

   if (!path) return;
   /* if not in system dir or user dir, copy to user dir */
   snprintf(buf, sizeof(buf), "%s", elm_theme_system_dir_get());
   if (!strncmp(buf, path, strlen(buf))) copy = 0;
   if (copy)
     {
        snprintf(buf, sizeof(buf), "%s", elm_theme_user_dir_get());
        if (!strncmp(buf, path, strlen(buf))) copy = 0;
     }
   if (copy)
     {
        const char *file;
        char *name;

        file = ecore_file_file_get(path);
        name = ecore_file_strip_ext(file);

        if (name)
          {
             double tim = ecore_time_unix_get();

             snprintf(buf, sizeof(buf), "%s/%s-%f.edj",
                      elm_theme_user_dir_get(), name, tim);
             if (!ecore_file_exists(buf))
               {
                  ecore_file_cp(path, buf);
                  snprintf(buf, sizeof(buf), "%s-%f", name, tim);
                  elm_theme_set(NULL, buf);
               }
             else
               elm_theme_set(NULL, path);
             elm_config_all_flush();
             elm_config_save();
             free(name);
          }
     }
   else
     {
        const char *file;
        char *name;

        file = ecore_file_file_get(path);
        name = ecore_file_strip_ext(file);
        if (name)
          {
             elm_theme_set(NULL, name);
             elm_config_all_flush();
             elm_config_save();
             free(name);
          }
     }

   e_config_save_queue();
   a = e_action_find("restart");
   if ((a) && (a->func.go)) a->func.go(NULL, NULL);
}

static int
e_theme_handler_test(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, const char *path)
{
   if (!path) return 0;
   if (!edje_file_group_exists(path, "e/widgets/border/default/border"))
     return 0;
   return 1;
}
#include "e.h"

typedef struct _E_Thumb E_Thumb;

struct _E_Thumb
{
   int           objid;
   int           w, h;
   const char   *file;
   const char   *key;
   char         *sort_id;
   struct {
      int x, y, x_count, y_count;
   } desk_pan;
   Eina_List    *sigsrc;
   unsigned char queued E_BITFIELD;
   unsigned char busy E_BITFIELD;
   unsigned char done E_BITFIELD;
};

/* local subsystem functions */
static void         _e_thumb_gen_begin(int objid, const char *file, const char *key, int w, int h, int desk_x, int desk_y, int desk_x_count, int desk_y_count, Eina_List *sigsrc);
static void         _e_thumb_gen_end(int objid);
static void         _e_thumb_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void         _e_thumb_hash_add(int objid, Evas_Object *obj);
static void         _e_thumb_hash_del(int objid);
static Evas_Object *_e_thumb_hash_find(int objid);
static void         _e_thumb_thumbnailers_kill(void);
static void         _e_thumb_thumbnailers_kill_cancel(void);
static Eina_Bool    _e_thumb_cb_kill(void *data);
static Eina_Bool    _e_thumb_cb_exe_event_del(void *data, int type, void *event);

/* local subsystem globals */
static Eina_List *_thumbnailers = NULL;
static Eina_List *_thumbnailers_exe = NULL;
static Eina_List *_thumb_queue = NULL;
static int _objid = 0;
static Eina_Hash *_thumbs = NULL;
static int _pending = 0;
static int _num_thumbnailers = 1;
static Ecore_Event_Handler *_exe_del_handler = NULL;
static Ecore_Timer *_kill_timer = NULL;

/* externally accessible functions */
EINTERN int
e_thumb_init(void)
{
   _exe_del_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,
                                              _e_thumb_cb_exe_event_del,
                                              NULL);
   _thumbs = eina_hash_string_superfast_new(NULL);
   return 1;
}

EINTERN int
e_thumb_shutdown(void)
{
   _e_thumb_thumbnailers_kill_cancel();
   _e_thumb_cb_kill(NULL);
   if (_exe_del_handler) ecore_event_handler_del(_exe_del_handler);
   _exe_del_handler = NULL;
   _thumbnailers = eina_list_free(_thumbnailers);
   E_FREE_LIST(_thumbnailers_exe, ecore_exe_free);
   _thumb_queue = eina_list_free(_thumb_queue);
   _objid = 0;
   eina_hash_free(_thumbs);
   _thumbs = NULL;
   _pending = 0;
   return 1;
}

static void
_thumb_preloaded(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "e_thumb_gen", NULL);
}

E_API Evas_Object *
e_thumb_icon_add(Evas *evas)
{
   Evas_Object *obj;
   E_Thumb *eth;

   obj = e_icon_add(evas);
   evas_object_smart_callback_add(obj, "preloaded", _thumb_preloaded, obj);
   _objid++;
   eth = E_NEW(E_Thumb, 1);
   eth->objid = _objid;
   eth->w = 64;
   eth->h = 64;
   evas_object_data_set(obj, "e_thumbdata", eth);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_FREE,
                                  _e_thumb_del_hook, NULL);
   _e_thumb_hash_add(eth->objid, obj);
   return obj;
}

E_API void
e_thumb_icon_file_set(Evas_Object *obj, const char *file, const char *key)
{
   E_Thumb *eth;

   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   eina_stringshare_replace(&eth->file, file);
   eina_stringshare_replace(&eth->key, key);
   E_FREE(eth->sort_id);
}

E_API void
e_thumb_icon_size_set(Evas_Object *obj, int w, int h)
{
   E_Thumb *eth;

   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   if ((w < 1) || (h < 1)) return;
   eth->w = w;
   eth->h = h;
}

E_API void
e_thumb_icon_begin(Evas_Object *obj)
{
   E_Thumb *eth, *eth2;
   char buf[4096];

   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   if (eth->queued) return;
   if (eth->busy) return;
   if (eth->done) return;
   if (!eth->file) return;
   if (!_thumbnailers)
     {
        while ((int)eina_list_count(_thumbnailers_exe) < _num_thumbnailers)
          {
             Ecore_Exe *exe;

             snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_thumb --nice=%d", e_prefix_lib_get(),
                      e_config->thumb_nice);
             exe = e_util_exe_safe_run(buf, NULL);
             _thumbnailers_exe = eina_list_append(_thumbnailers_exe, exe);
          }
        _thumb_queue = eina_list_append(_thumb_queue, eth);
        eth->queued = 1;
        return;
     }
   EINA_LIST_FREE(_thumb_queue, eth2)
     {
        eth2->queued = 0;
        eth2->busy = 1;
        _pending++;
        if (_pending == 1) _e_thumb_thumbnailers_kill_cancel();
        _e_thumb_gen_begin(eth2->objid, eth2->file, eth2->key,
                           eth2->w, eth2->h,
                           eth2->desk_pan.x, eth2->desk_pan.y,
                           eth2->desk_pan.x_count, eth2->desk_pan.y_count,
                           eth2->sigsrc);
     }
   eth->busy = 1;
   _pending++;
   if (_pending == 1) _e_thumb_thumbnailers_kill_cancel();
   _e_thumb_gen_begin(eth->objid, eth->file, eth->key,
                      eth->w, eth->h,
                      eth->desk_pan.x, eth->desk_pan.y,
                      eth->desk_pan.x_count, eth->desk_pan.y_count,
                      eth->sigsrc);
}

E_API void
e_thumb_icon_end(Evas_Object *obj)
{
   E_Thumb *eth;

   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   if (eth->queued)
     {
        _thumb_queue = eina_list_remove(_thumb_queue, eth);
        eth->queued = 0;
     }
   if (eth->busy)
     {
        _e_thumb_gen_end(eth->objid);
        eth->busy = 0;
        _pending--;
        if (_pending == 0) _e_thumb_thumbnailers_kill();
     }
}

E_API void
e_thumb_icon_rethumb(Evas_Object *obj)
{
   E_Thumb *eth;
   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;

   if (eth->done) eth->done = 0;
   else e_thumb_icon_end(obj);

   e_thumb_icon_begin(obj);
}

E_API void
e_thumb_desk_pan_set(Evas_Object *obj, int x, int y, int x_count, int y_count)
{
   E_Thumb *eth;
   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   eth->desk_pan.x = x;
   eth->desk_pan.y = y;
   eth->desk_pan.x_count = x_count;
   eth->desk_pan.y_count = y_count;
}

E_API void
e_thumb_signal_add(Evas_Object *obj, const char *sig, const char *src)
{
   E_Thumb *eth;
   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   eth->sigsrc = eina_list_append(eth->sigsrc, eina_stringshare_add(sig));
   eth->sigsrc = eina_list_append(eth->sigsrc, eina_stringshare_add(src));
}

#define A(v)          (((v) >> 24) & 0xff)
#define R(v)          (((v) >> 16) & 0xff)
#define G(v)          (((v) >> 8) & 0xff)
#define B(v)          (((v)) & 0xff)
#define PIX(p, x, y)  p[((y) << 2) + (x)]
#define PIX2(p, x, y) p[((y) << 1) + (x)]

static void
_e_thumb_key_load(E_Thumb *eth, const char *icon)
{
   Eet_File *ef;
   int size = 0;

   ef = eet_open(icon, EET_FILE_MODE_READ);
   if (!ef) return;
   eth->sort_id = eet_read(ef, "/thumbnail/sort_id", &size);
   if (eth->sort_id)
     {
        if (size > 0) eth->sort_id[size - 1] = 0;
        else
          {
             free(eth->sort_id);
             eth->sort_id = NULL;
          }
     }
   eet_close(ef);
}

E_API const char *
e_thumb_sort_id_get(Evas_Object *obj)
{
   E_Thumb *eth;
   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return "";
   if (!eth->sort_id) return "";
   return eth->sort_id;
}

E_API void
e_thumb_client_data(Ecore_Ipc_Event_Client_Data *e)
{
   int objid;
   char *icon;
   E_Thumb *eth;
   Evas_Object *obj;

   if (!eina_list_data_find(_thumbnailers, e->client))
     _thumbnailers = eina_list_prepend(_thumbnailers, e->client);
   if (e->minor == 2)
     {
        objid = e->ref;
        icon = e->data;
        if ((icon) && (e->size > 1) && (icon[e->size - 1] == 0))
          {
             obj = _e_thumb_hash_find(objid);
             if (obj)
               {
                  eth = evas_object_data_get(obj, "e_thumbdata");
                  if (eth)
                    {
                       eth->busy = 0;
                       _pending--;
                       eth->done = 1;
                       if (_pending == 0) _e_thumb_thumbnailers_kill();
                       if (ecore_file_exists(icon))
                         {
                            e_icon_preload_set(obj, 1);
                            e_icon_file_key_set(obj, icon, "/thumbnail/data");
                            _e_thumb_key_load(eth, icon);
                         }
                       evas_object_smart_callback_call(obj, "e_thumb_gen", NULL);
                    }
               }
          }
     }
   if (e->minor == 1)
     {
        /* hello message */
        EINA_LIST_FREE(_thumb_queue, eth)
          {
             eth->queued = 0;
             eth->busy = 1;
             _pending++;
             if (_pending == 1) _e_thumb_thumbnailers_kill_cancel();
             _e_thumb_gen_begin(eth->objid, eth->file, eth->key,
                                eth->w, eth->h,
                                eth->desk_pan.x, eth->desk_pan.y,
                                eth->desk_pan.x_count, eth->desk_pan.y_count,
                                eth->sigsrc);
          }
     }
}

E_API void
e_thumb_client_del(Ecore_Ipc_Event_Client_Del *e)
{
   if (!eina_list_data_find(_thumbnailers, e->client)) return;
   _thumbnailers = eina_list_remove(_thumbnailers, e->client);
   if ((!_thumbs) && (!_thumbnailers)) _objid = 0;
}

/* local subsystem functions */
static void
_e_thumb_gen_begin(int objid, const char *file, const char *key, int w, int h,
                   int desk_x, int desk_y, int desk_x_count, int desk_y_count,
                   Eina_List *sigsrc)
{
   char *buf, *p;
   int l1, l2, size, *desk;
   Ecore_Ipc_Client *cli;
   Eina_List *l;
   const char *s;

   /* send thumb req */
   // figure out buffer size needed
   l1 = strlen(file);
   l2 = 0;
   if (key) l2 = strlen(key);
   size = (4 * sizeof(int)); // desk_x/y/count
   size += l1 + 1; // file
   size += l2 + 1; // key
   EINA_LIST_FOREACH(sigsrc, l, s)
     {
        size += strlen(s) + 1;
     }
   buf = alloca(size);
   p = buf;

   // fill in buffer data
   // data is:
   //  [int]desk_x
   //  [int]desk_y
   //  [int]desk_x_count
   //  [int]desk_y_count
   //  [char[]]file
   //  [char[]]key
   // optional:
   //  [char[]]sig1
   //  [char[]]src1
   //  [char[]]sig2
   //  [char[]]src2
   //  ...
   desk = (int *)(void *)buf;
   desk[0] = desk_x;
   desk[1] = desk_y;
   desk[2] = desk_x_count;
   desk[3] = desk_y_count;
   p += (4 * sizeof(int));
   strcpy(p, file);
   p += l1 + 1;
   if (key)
     {
        strcpy(p, key);
        p += l2 + 1;
     }
   else
     {
        p[0] = 0;
        p += 1;
     }
   EINA_LIST_FOREACH(sigsrc, l, s)
     {
        strcpy(p, s);
        p += strlen(s) + 1;
     }

   // actually send it off
   cli = eina_list_data_get(_thumbnailers);
   if (!cli) return;
   _thumbnailers = eina_list_remove_list(_thumbnailers, _thumbnailers);
   _thumbnailers = eina_list_append(_thumbnailers, cli);
   ecore_ipc_client_send(cli, E_IPC_DOMAIN_THUMB, 1, objid, w, h, buf, size);
}

static void
_e_thumb_gen_end(int objid)
{
   Eina_List *l;
   Ecore_Ipc_Client *cli;

   /* send thumb cancel */
   EINA_LIST_FOREACH(_thumbnailers, l, cli)
     {
        ecore_ipc_client_send(cli, E_IPC_DOMAIN_THUMB, 2, objid, 0, 0, NULL, 0);
     }
}

static void
_e_thumb_del_hook(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Thumb *eth;
   const char *s;

   eth = evas_object_data_get(obj, "e_thumbdata");
   if (!eth) return;
   evas_object_data_del(obj, "e_thumbdata");
   _e_thumb_hash_del(eth->objid);
   if (eth->busy)
     {
        _e_thumb_gen_end(eth->objid);
        eth->busy = 0;
        _pending--;
        if (_pending == 0) _e_thumb_thumbnailers_kill();
     }
   if (eth->queued)
     _thumb_queue = eina_list_remove(_thumb_queue, eth);
   if (eth->file) eina_stringshare_del(eth->file);
   if (eth->key) eina_stringshare_del(eth->key);
   free(eth->sort_id);
   EINA_LIST_FREE(eth->sigsrc, s) eina_stringshare_del(s);
   free(eth);
}

static void
_e_thumb_hash_add(int objid, Evas_Object *obj)
{
   char buf[32];

   snprintf(buf, sizeof(buf), "%i", objid);
   eina_hash_add(_thumbs, buf, obj);
}

static void
_e_thumb_hash_del(int objid)
{
   char buf[32];

   snprintf(buf, sizeof(buf), "%i", objid);
   if (_thumbs) eina_hash_del(_thumbs, buf, NULL);
   if ((!_thumbs) && (!_thumbnailers)) _objid = 0;
}

static Evas_Object *
_e_thumb_hash_find(int objid)
{
   char buf[32];

   snprintf(buf, sizeof(buf), "%i", objid);
   return eina_hash_find(_thumbs, buf);
}

static void
_e_thumb_thumbnailers_kill(void)
{
   if (_kill_timer) ecore_timer_del(_kill_timer);
   _kill_timer = ecore_timer_loop_add(1.0, _e_thumb_cb_kill, NULL);
}

static void
_e_thumb_thumbnailers_kill_cancel(void)
{
   if (_kill_timer) ecore_timer_del(_kill_timer);
   _kill_timer = NULL;
}

static Eina_Bool
_e_thumb_cb_kill(void *data EINA_UNUSED)
{
   Eina_List *l;
   Ecore_Exe *exe;

   EINA_LIST_FOREACH(_thumbnailers_exe, l, exe)
     ecore_exe_terminate(exe);
   _kill_timer = NULL;
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
_e_thumb_cb_exe_event_del(void *data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_Exe_Event_Del *ev;
   Ecore_Exe *exe;
   Eina_List *l;

   ev = event;
   EINA_LIST_FOREACH(_thumbnailers_exe, l, exe)
     {
        if (exe == ev->exe)
          {
             _thumbnailers_exe = eina_list_remove_list(_thumbnailers_exe, l);
             break;
          }
     }
   if ((!_thumbnailers_exe) && (_thumb_queue))
     {
        while ((int)eina_list_count(_thumbnailers_exe) < _num_thumbnailers)
          {
             Ecore_Exe *exe_thumb;
             char buf[4096];

             snprintf(buf, sizeof(buf), "%s/enlightenment/utils/enlightenment_thumb --nice=%d", e_prefix_lib_get(),
                      e_config->thumb_nice);
             exe_thumb = e_util_exe_safe_run(buf, NULL);
             _thumbnailers_exe = eina_list_append(_thumbnailers_exe, exe_thumb);
          }
     }
   return ECORE_CALLBACK_PASS_ON;
}

#include "config.h"

#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#elif defined __GNUC__
# define alloca __builtin_alloca
#elif defined _AIX
# define alloca __alloca
#elif defined _MSC_VER
# include <malloc.h>
# define alloca _alloca
#else
# include <stddef.h>
# ifdef  __cplusplus
extern "C"
# endif
void *alloca(size_t);
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Ecore_Ipc.h>
#include <Ecore_File.h>
#include <Evas.h>
#include <Eet.h>
#include <Edje.h>
#include <Emotion.h>
#include "e_sha1.h"
#include "e_user.h"

typedef struct _E_Thumb E_Thumb;

struct _E_Thumb
{
   int        objid;
   int        w, h;
   int        desk_x, desk_y, desk_x_count, desk_y_count;
   Eina_List *sigsrc;
   char      *file;
   char      *key;
};

/* local subsystem functions */
static int       _e_ipc_init(void);
static Eina_Bool _e_ipc_cb_server_add(void *data,
                                      int type,
                                      void *event);
static Eina_Bool _e_ipc_cb_server_del(void *data,
                                      int type,
                                      void *event);
static Eina_Bool _e_ipc_cb_server_data(void *data,
                                       int type,
                                       void *event);
static Eina_Bool _e_cb_idle_enterer(void *data);
static void      _e_thumb_generate(E_Thumb *eth);
static char     *_e_thumb_file_id(char *file,
                                  char *key,
                                  int desk_x,
                                  int desk_y,
                                  int desk_x_count,
                                  int desk_y_count,
                                  Eina_List *sigsrc);

/* local subsystem globals */
static Ecore_Idle_Enterer *_idle_enterer = NULL;
static Ecore_Ipc_Server *_e_ipc_server = NULL;
static Eina_List *_thumblist = NULL;
static char _thumbdir[4096] = "";

/* externally accessible functions */
int
main(int argc,
     char **argv)
{
   int i;

   for (i = 1; i < argc; i++)
     {
        if ((!strcmp(argv[i], "-h")) ||
            (!strcmp(argv[i], "-help")) ||
            (!strcmp(argv[i], "--help")))
          {
             printf(
               "This is an internal tool for Enlightenment.\n"
               "do not use it.\n"
               );
             exit(0);
          }
        else if (!strncmp(argv[i], "--nice=", 7))
          {
             const char *val;

             val = argv[i] + 7;
             if (*val)
               {
                  if (nice(atoi(val)) < 0) perror("nice");
               }
          }
     }

   ecore_app_no_system_modules();
   ecore_init();
   ecore_app_args_set(argc, (const char **)argv);
   eet_init();
   evas_init();
   ecore_evas_init();
   edje_init();
   ecore_file_init();
   ecore_ipc_init();
   emotion_init();

   e_user_dir_concat_static(_thumbdir, "fileman/thumbnails");
   ecore_file_mkpath(_thumbdir);

   _idle_enterer = ecore_idle_enterer_add(_e_cb_idle_enterer, NULL);
   if (_idle_enterer)
     {
        if (_e_ipc_init()) ecore_main_loop_begin();
        ecore_idle_enterer_del(_idle_enterer);
        _idle_enterer = NULL;
     }

   if (_e_ipc_server)
     {
        ecore_ipc_server_del(_e_ipc_server);
        _e_ipc_server = NULL;
     }

   emotion_shutdown();
   ecore_ipc_shutdown();
   ecore_file_shutdown();
   ecore_evas_shutdown();
   edje_shutdown();
   evas_shutdown();
   eet_shutdown();
   ecore_shutdown();

   return 0;
}

/* local subsystem functions */
static int
_e_ipc_init(void)
{
   char *sdir;

   sdir = getenv("E_IPC_SOCKET");
   if (!sdir)
     {
        printf("The E_IPC_SOCKET environment variable is not set. This is\n"
               "exported by Enlightenment to all processes it launches.\n"
               "This environment variable must be set and must point to\n"
               "Enlightenment's IPC socket file (minus port number).\n");
        return 0;
     }
   _e_ipc_server = ecore_ipc_server_connect(ECORE_IPC_LOCAL_SYSTEM, sdir, 0, NULL);
   if (!_e_ipc_server) return 0;

   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_ADD, _e_ipc_cb_server_add, NULL);
   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DEL, _e_ipc_cb_server_del, NULL);
   ecore_event_handler_add(ECORE_IPC_EVENT_SERVER_DATA, _e_ipc_cb_server_data, NULL);

   return 1;
}

static Eina_Bool
_e_ipc_cb_server_add(void *data EINA_UNUSED,
                     int type   EINA_UNUSED,
                     void *event)
{
   Ecore_Ipc_Event_Server_Add *e;

   e = event;
   ecore_ipc_server_send(e->server,
                         5 /*E_IPC_DOMAIN_THUMB*/,
                         1 /*hello*/,
                         0, 0, 0, NULL, 0); /* send hello */
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_ipc_cb_server_del(void *data  EINA_UNUSED,
                     int type    EINA_UNUSED,
                     void *event EINA_UNUSED)
{
   /* quit now */
   ecore_main_loop_quit();
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_e_ipc_cb_server_data(void *data EINA_UNUSED,
                      int type   EINA_UNUSED,
                      void *event)
{
   Ecore_Ipc_Event_Server_Data *e;
   E_Thumb *eth;
   Eina_List *l;
   const char *file = NULL;
   const char *key = NULL;

   e = event;
   if (e->major != 5 /*E_IPC_DOMAIN_THUMB*/) return ECORE_CALLBACK_PASS_ON;
   switch (e->minor)
     {
      case 1:
        if (e->data)
          {
             const char *s, *start;
             const int *desk;
             Eina_List *sigsrc = NULL;

             /* begin thumb */
             /* don't check stuff. since this connects TO E it is connecting */
             /* TO a trusted process that WILL send this message properly */
             /* formatted. if the thumbnailer dies anyway - it's not a big loss */
             /* but it is a sign of a bug in e formatting messages maybe */
             s = start = e->data;
             desk = (const int *)(void *)(s);
             s += (4 * sizeof(int));
             file = s;
             s += strlen(s) + 1;
             key = s;
             s += strlen(s) + 1;
             if (!key[0]) key = NULL;
             while ((s - start) < e->size)
               {
                  sigsrc = eina_list_append(sigsrc, eina_stringshare_add(s));
                  s += strlen(s) + 1;
               }
             eth = calloc(1, sizeof(E_Thumb));
             if (eth)
               {
                  eth->objid = e->ref;
                  eth->w = e->ref_to;
                  eth->h = e->response;
                  eth->file = strdup(file);
                  eth->desk_x = desk[0];
                  eth->desk_y = desk[1];
                  eth->desk_x_count = desk[2];
                  eth->desk_y_count = desk[3];
                  eth->sigsrc = sigsrc;
                  if (key) eth->key = strdup(key);
                  _thumblist = eina_list_append(_thumblist, eth);
               }
          }
        break;

      case 2:
        /* end thumb */
        EINA_LIST_FOREACH(_thumblist, l, eth)
          {
             if (eth->objid == e->ref)
               {
                  _thumblist = eina_list_remove_list(_thumblist, l);
                  free(eth->file);
                  free(eth->key);
                  free(eth);
                  break;
               }
          }
        break;

      case 3:
        /* quit now */
        ecore_main_loop_quit();
        break;

      default:
        break;
     }
   return ECORE_CALLBACK_PASS_ON;
}

static void
_cb_wakeup(void *data EINA_UNUSED)
{
}

static Eina_Bool
_e_cb_idle_enterer(void *data EINA_UNUSED)
{
   E_Thumb *eth;

   /* take thumb at head of list */
   if (_thumblist)
     {
        const char *s;

        eth = eina_list_data_get(_thumblist);
        _thumblist = eina_list_remove_list(_thumblist, _thumblist);
        _e_thumb_generate(eth);
        EINA_LIST_FREE(eth->sigsrc, s) eina_stringshare_del(s);
        free(eth->file);
        free(eth->key);
        free(eth);
        if (_thumblist) ecore_job_add(_cb_wakeup, NULL);
     }
   return ECORE_CALLBACK_RENEW;
}

typedef struct _Color Color;

struct _Color
{
   Color        *closest;
   int           closest_dist;
   int           use;
   unsigned char r, g, b;
};

static void
_e_thumb_generate(E_Thumb *eth)
{
   char buf[PATH_MAX + 200], dbuf[PATH_MAX + 2], *id, *td, *ext = NULL;
   Evas *evas = NULL, *evas_im = NULL;
   Ecore_Evas *ee = NULL, *ee_im = NULL;
   Evas_Object *im = NULL, *edje = NULL;
   Eet_File *ef = NULL;
   int iw, ih, alpha, ww, hh;
   const unsigned int *data = NULL;
   time_t mtime_orig, mtime_thumb;

   id = _e_thumb_file_id(eth->file, eth->key,
                         0, 0, 1, 1,
//                         eth->desk_x, eth->desk_y,
//                         eth->desk_x_count, eth->desk_y_count,
                         eth->sigsrc);
   if (!id) return;

   td = strdup(id);
   if (!td)
     {
        free(id);
        return;
     }
   td[2] = 0;

   snprintf(dbuf, sizeof(dbuf), "%s/%s", _thumbdir, td);
   snprintf(buf, sizeof(buf), "%s/%s/%s-%ix%i.thm",
            _thumbdir, td, id + 2, eth->w, eth->h);
   free(id);
   free(td);

   mtime_orig = ecore_file_mod_time(eth->file);
   mtime_thumb = ecore_file_mod_time(buf);
   while (mtime_thumb <= mtime_orig)
     {
        unsigned int *data1;
        Eina_Bool sortkey;
        Evas_Object *im2, *bg;

        im = NULL;
        im2 = NULL;
        bg = NULL;

        ecore_file_mkdir(dbuf);

        edje_file_cache_set(0);
        edje_collection_cache_set(0);
        ee = ecore_evas_buffer_new(1, 1);
        evas = ecore_evas_get(ee);
        evas_image_cache_set(evas, 0);
        evas_font_cache_set(evas, 0);
        ww = 0;
        hh = 0;
        alpha = 1;
        ext = strrchr(eth->file, '.');

        sortkey = EINA_FALSE;

        if (emotion_object_extension_may_play_get(eth->file))
          {
             Evas_Object *em;

             em = emotion_object_add(evas);
             emotion_object_init(em, NULL);
             emotion_object_file_set(em, eth->file);

             im = emotion_file_meta_artwork_get(em, eth->file, EMOTION_ARTWORK_PREVIEW_IMAGE);
             if (!im) im = emotion_file_meta_artwork_get(em, eth->file, EMOTION_ARTWORK_IMAGE);
             if (im)
               {
                  evas_object_image_size_get(im, &ww, &hh);
                  evas_object_image_fill_set(im, 0, 0, ww, hh);
                  evas_object_move(im, 0, 0);
                  evas_object_resize(im, ww, hh);
               }
             evas_object_del(em);
             if (im) goto process;
          }

        if ((ext) && (eth->key) &&
            ((!strcasecmp(ext, ".edj")) ||
             (!strcasecmp(ext, ".eap"))))
          {
             Eina_List *l;

             ww = eth->w;
             hh = eth->h;
             im = ecore_evas_object_image_new(ee);
             ee_im = evas_object_data_get(im, "Ecore_Evas");
             evas_im = ecore_evas_get(ee_im);
             evas_image_cache_set(evas_im, 0);
             evas_font_cache_set(evas_im, 0);
             evas_object_image_size_set(im, ww * 4, hh * 4);
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             edje = edje_object_add(evas_im);
             if ((eth->key) &&
                 ((!strcmp(eth->key, "e/desktop/background")) ||
                  (!strcmp(eth->key, "e/init/splash"))))
               alpha = 0;
             if (edje_object_file_set(edje, eth->file, eth->key))
               {
                  evas_object_move(edje, 0, 0);
                  evas_object_resize(edje, ww * 4, hh * 4);
                  evas_object_show(edje);
               }
             if ((eth->desk_x_count > 0) &&
                 (eth->desk_y_count > 0))
               {
                  Edje_Message_Float_Set *msg;

                  msg = alloca(sizeof(Edje_Message_Float_Set) +
                               (4 * sizeof(double)));
                  msg->count = 5;
                  msg->val[0] = 0.0;
                  msg->val[1] = eth->desk_x;
                  msg->val[2] = eth->desk_x_count;
                  msg->val[3] = eth->desk_y;
                  msg->val[4] = eth->desk_y_count;
                  edje_object_message_send(edje, EDJE_MESSAGE_FLOAT_SET,
                                           0, msg);
               }
             l = eth->sigsrc;
             while (l)
               {
                  const char *sig, *src;

                  sig = l->data;
                  l = l->next;
                  if (l)
                    {
                       src = l->data;
                       l = l->next;
                       edje_object_signal_emit(edje, sig, src);
                    }
               }
             edje_object_message_signal_process(edje);
             evas_object_move(im, 0, 0);
             evas_object_resize(im, ww, hh);
             sortkey = EINA_TRUE;
          }
        else if ((ext) &&
                 ((!strcasecmp(ext, ".ttf")) ||
                  (!strcasecmp(ext, ".pcf")) ||
                  (!strcasecmp(ext, ".bdf")) ||
                  (!strcasecmp(ext, ".ttx")) ||
                  (!strcasecmp(ext, ".pfa")) ||
                  (!strcasecmp(ext, ".pfb")) ||
                  (!strcasecmp(ext, ".afm")) ||
                  (!strcasecmp(ext, ".sfd")) ||
                  (!strcasecmp(ext, ".snf")) ||
                  (!strcasecmp(ext, ".otf")) ||
                  (!strcasecmp(ext, ".psf")) ||
                  (!strcasecmp(ext, ".ttc")) ||
                  (!strcasecmp(ext, ".ttx")) ||
                  (!strcasecmp(ext, ".gsf")) ||
                  (!strcasecmp(ext, ".spd"))
                 ))
          {
             Evas_Coord tx = 0, ty = 0, tw = 0, th = 0;
             ww = eth->w;
             hh = eth->h;
             alpha = 0;

             bg = evas_object_rectangle_add(evas);
             evas_object_color_set(bg, 96, 96, 96, 255);
             evas_object_move(bg, 0, 0);
             evas_object_resize(bg, ww, hh);
             evas_object_show(bg);

             im = evas_object_text_add(evas);
             evas_object_text_font_set(im, eth->file, hh / 4);
             evas_object_color_set(im, 192, 192, 192, 255);
             evas_object_text_ellipsis_set(im, 0.0);
             evas_object_text_text_set(im, "ABCabc");
             evas_object_geometry_get(im, NULL, NULL, &tw, &th);
             if (tw > ww) tw = ww;
             tx = 0 + ((ww - tw) / 2);
             ty = 0 + (((hh / 2) - th) / 2);
             evas_object_move(im, tx, ty);
             evas_object_resize(im, tw, th);
             evas_object_show(im);

             im2 = evas_object_text_add(evas);
             evas_object_text_font_set(im2, eth->file, hh / 4);
             evas_object_color_set(im2, 255, 255, 255, 255);
             evas_object_text_ellipsis_set(im2, 0.0);
             evas_object_text_text_set(im2, "123!@?");
             evas_object_geometry_get(im2, NULL, NULL, &tw, &th);
             if (tw > ww) tw = ww;
             tx = 0 + ((ww - tw) / 2);
             ty = (hh / 2) + (((hh / 2) - th) / 2);
             evas_object_move(im2, tx, ty);
             evas_object_resize(im2, tw, th);
             evas_object_show(im2);
          }
        else if (evas_object_image_extension_can_load_get(ext))
          {
             im = evas_object_image_add(evas);
             evas_object_image_load_orientation_set(im, EINA_TRUE);
             evas_object_image_load_size_set(im, eth->w, eth->h);
             evas_object_image_file_set(im, eth->file, NULL);
             iw = 0; ih = 0;
             evas_object_image_size_get(im, &iw, &ih);
             alpha = evas_object_image_alpha_get(im);
             if ((iw > 0) && (ih > 0))
               {
                  ww = eth->w;
                  hh = (eth->w * ih) / iw;
                  if (hh > eth->h)
                    {
                       hh = eth->h;
                       ww = (eth->h * iw) / ih;
                    }
                  evas_object_image_fill_set(im, 0, 0, ww, hh);
               }
             evas_object_move(im, 0, 0);
             evas_object_resize(im, ww, hh);
             sortkey = EINA_TRUE;
          }
        else
          goto end;

process:
        ecore_evas_alpha_set(ee, alpha);
        ecore_evas_resize(ee, ww, hh);
        evas_object_show(im);
        if (ww <= 0) goto end;
        data = ecore_evas_buffer_pixels_get(ee);
        if (!data) goto end;
        ef = eet_open(buf, EET_FILE_MODE_WRITE);
        if (!ef) goto end;
        eet_write(ef, "/thumbnail/orig_file",
                  eth->file, strlen(eth->file), 1);
        if (eth->key)
          eet_write(ef, "/thumbnail/orig_key",
                    eth->key, strlen(eth->key), 1);
        eet_data_image_write(ef, "/thumbnail/data",
                             (void *)data, ww, hh, alpha,
                             0, 91, 1);
        if (sortkey)
          {
             ww = 4; hh = 4;
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             evas_object_resize(im, ww, hh);
             ecore_evas_resize(ee, ww, hh);
             data = ecore_evas_buffer_pixels_get(ee);
             if (!data) goto end;

             data1 = malloc(ww * hh * sizeof(unsigned int));
             memcpy(data1, data, ww * hh * sizeof(unsigned int));
             ww = 2; hh = 2;
             evas_object_image_fill_set(im, 0, 0, ww, hh);
             evas_object_resize(im, ww, hh);
             ecore_evas_resize(ee, ww, hh);
             data = ecore_evas_buffer_pixels_get(ee);
             if (data)
               {
                  unsigned int *data2;

                  data2 = malloc(ww * hh * sizeof(unsigned int));
                  memcpy(data2, data, ww * hh * sizeof(unsigned int));
                  ww = 1; hh = 1;
                  evas_object_image_fill_set(im, 0, 0, ww, hh);
                  evas_object_resize(im, ww, hh);
                  ecore_evas_resize(ee, ww, hh);
                  data = ecore_evas_buffer_pixels_get(ee);
                  if (data)
                    {
                       unsigned int *data3;
                       unsigned char id2[(21 * 4) + 1];
                       int n, i;
                       int hi, si, vi;
                       float h, s, v;
                       const int pat2[4] =
                         {
                            0, 3, 1, 2
                         };
                       const int pat1[16] =
                         {
                            5, 10, 6, 9,
                            0, 15, 3, 12,
                            1, 14, 7, 8,
                            4, 11, 2, 13
                         };

                       /* ww = hh = 1 here */
                       data3 = malloc(sizeof(unsigned int));
                       memcpy(data3, data, sizeof(unsigned int));
                       // sort_id
                       n = 0;
#define A(v) (((v) >> 24) & 0xff)
#define R(v) (((v) >> 16) & 0xff)
#define G(v) (((v) >> 8) & 0xff)
#define B(v) (((v)) & 0xff)
#define HSV(p)                                         \
  evas_color_rgb_to_hsv(R(p), G(p), B(p), &h, &s, &v); \
  hi = 20 * (h / 360.0);                               \
  si = 20 * s;                                         \
  vi = 20 * v;                                         \
  if (si < 2) hi = 25;
#define SAVEHSV(h, s, v) \
  id2[n++] = 'a' + h;    \
  id2[n++] = 'a' + v;    \
  id2[n++] = 'a' + s;
#define SAVEX(x) \
  id2[n++] = 'a' + x;
#if 0
                       HSV(data3[0]);
                       SAVEHSV(hi, si, vi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEHSV(hi, si, vi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEHSV(hi, si, vi);
                         }
#else
                       HSV(data3[0]);
                       SAVEX(hi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(hi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(hi);
                         }
                       HSV(data3[0]);
                       SAVEX(vi);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(vi);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(vi);
                         }
                       HSV(data3[0]);
                       SAVEX(si);
                       for (i = 0; i < 4; i++)
                         {
                            HSV(data2[pat2[i]]);
                            SAVEX(si);
                         }
                       for (i = 0; i < 16; i++)
                         {
                            HSV(data1[pat1[i]]);
                            SAVEX(si);
                         }
#endif
                       id2[n++] = 0;
                       eet_write(ef, "/thumbnail/sort_id", id2, n, 1);
                       free(data3);
                    }
                  free(data2);
               }
             free(data1);
          }
end:
        if (ef) eet_close(ef);

        /* will free all */
        if (edje) evas_object_del(edje);
        if (ee_im) ecore_evas_free(ee_im);
        else if (im) evas_object_del(im);
        if (im2) evas_object_del(im2);
        if (bg) evas_object_del(bg);
        ecore_evas_free(ee);
        eet_clearcache();
        break;
     }
   /* send back path to thumb */
   ecore_ipc_server_send(_e_ipc_server, 5, 2, eth->objid, 0, 0, buf, strlen(buf) + 1);
}

static char *
_e_thumb_file_id(char *file,
                 char *key,
                 int desk_x,
                 int desk_y,
                 int desk_x_count,
                 int desk_y_count,
                 Eina_List *sigsrc)
{
   char s[64];
   const char *chmap = "0123456789abcdef", *str;
   unsigned char id[20], *st;
   Eina_Strbuf *sbuf;
   int i;
   Eina_List *l;

   sbuf = eina_strbuf_new();
   EINA_LIST_FOREACH(sigsrc, l, str)
     {
        eina_strbuf_append_printf(sbuf, "<<%s>>", str);
     }
   eina_strbuf_append_printf(sbuf, "|%i.%i.%i.%i|",
                             desk_x, desk_y, desk_x_count, desk_y_count);
   eina_strbuf_append_printf(sbuf, "///%s", file);
   if (key) eina_strbuf_append_printf(sbuf, "/%s", key);

   st = (unsigned char *)eina_strbuf_string_get(sbuf);
   e_sha1_sum(st, eina_strbuf_length_get(sbuf), id);

   for (i = 0; i < 20; i++)
     {
        s[(i * 2) + 0] = chmap[(id[i] >> 4) & 0xf];
        s[(i * 2) + 1] = chmap[(id[i]) & 0xf];
     }
   s[(i * 2)] = 0;
   eina_strbuf_free(sbuf);

   return strdup(s);
}

#include "e.h"

/* local function protos */
static void        _e_toolbar_free(E_Toolbar *tbar);
static void        _e_toolbar_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void        _e_toolbar_menu_cb_post(void *data, E_Menu *mn);
static void        _e_toolbar_menu_cb_pre(void *data, E_Menu *mn);
static void        _e_toolbar_menu_append(E_Toolbar *tbar, E_Menu *mn);
//static void        _e_toolbar_menu_cb_edit(void *data, E_Menu *mn, E_Menu_Item *mi);
static void        _e_toolbar_menu_cb_config(void *data, E_Menu *mn, E_Menu_Item *mi);
//static void        _e_toolbar_menu_cb_contents(void *data, E_Menu *mn, E_Menu_Item *mi);
static void        _e_toolbar_gadcon_size_request(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h);
static const char *_e_toolbar_orient_string_get(E_Toolbar *tbar);
static void        _e_toolbar_fm2_changed(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_fm2_dir_changed(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_fm2_dir_deleted(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_fm2_files_deleted(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_fm2_selected(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_fm2_selection_changed(void *data, Evas_Object *obj, void *event_info);
static void        _e_toolbar_menu_items_append(void *data, E_Gadcon_Client *gcc, E_Menu *mn);

/* local vars */
static Eina_List *toolbars = NULL;
static E_Gadcon_Location *tb_location = NULL;

static void
_tb_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED)
{
   E_Toolbar *tbar = data;
   Evas_Coord w, h;
   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   if (tbar->gadcon) e_gadcon_swallowed_min_size_set(tbar->gadcon, w, h);
}
/*
static void
_e_toolbar_gadget_remove(void *data EINA_UNUSED, E_Gadcon_Client *gcc)
{
   E_Toolbar *tbar = eina_list_data_get(toolbars);
   Eina_List *l;

   if (!tbar)
     {
        E_Config_Gadcon *cf_gc;
        EINA_LIST_FOREACH(e_config->gadcons, l, cf_gc)
          {
             if (e_util_strcmp(cf_gc->name, "toolbar")) continue;
             cf_gc->clients = eina_list_remove(cf_gc->clients, gcc->cf);
             break;
          }
        if (!cf_gc) return;
     }
   else if (gcc->cf)
     e_gadcon_client_config_del(tbar->gadcon->cf, gcc->cf);
   EINA_LIST_FOREACH(toolbars, l, tbar)
     e_gadcon_repopulate(tbar->gadcon);
   e_config_save_queue();
}

static int
_e_toolbar_gadget_add(void *data EINA_UNUSED, E_Gadcon_Client *gcc, const E_Gadcon_Client_Class *cc)
{
   E_Toolbar *tbar = eina_list_data_get(toolbars);
   E_Config_Gadcon *cf_gc = NULL;
   E_Gadcon *gc = NULL;
   Eina_List *l;
   E_Config_Gadcon_Client *cf_gcc = gcc->cf;

   if (!tbar)
     {
        E_Config_Gadcon *cf_gc2;
        EINA_LIST_FOREACH(e_config->gadcons, l, cf_gc2)
          {
             if (e_util_strcmp(cf_gc2->name, "toolbar")) continue;
             cf_gc = cf_gc2;
             break;
          }
        if (!cf_gc) return 0;
     }
   else
     gc = tbar->gadcon, cf_gc = gc->cf;
   if (gcc)
     {
        gcc->gadcon->cf->clients = eina_list_remove(gcc->gadcon->cf->clients, cf_gcc);
        if (gc && gc->zone)
          cf_gcc->geom.res = gc->zone->w;
        else if (gc && gc->o_container)
          {
             int w, h;
             evas_object_geometry_get(gc->o_container, NULL, NULL, &w, &h);
             switch (gc->orient)
               {
                case E_GADCON_ORIENT_VERT:
                case E_GADCON_ORIENT_LEFT:
                case E_GADCON_ORIENT_RIGHT:
                case E_GADCON_ORIENT_CORNER_LT:
                case E_GADCON_ORIENT_CORNER_RT:
                case E_GADCON_ORIENT_CORNER_LB:
                case E_GADCON_ORIENT_CORNER_RB:
                  cf_gcc->geom.res = h;
                  break;

                default:
                  cf_gcc->geom.res = w;
               }
          }
        else
          cf_gcc->geom.res = 800;
        cf_gcc->geom.size = 80;
        cf_gcc->geom.pos = cf_gcc->geom.res - cf_gcc->geom.size;
        cf_gc->clients = eina_list_append(cf_gc->clients, cf_gcc);
     }
   else
     {
        if (!gc); // FIXME: okay...need to create a gcc from nothing and add it to nothing...
        else if (!e_gadcon_client_config_new(gc, cc->name))
          return 0;
     }
   EINA_LIST_FOREACH(toolbars, l, tbar)
     e_gadcon_repopulate(tbar->gadcon);
   if (gc || gcc)
     e_config_save_queue();
   return (gc || gcc);
}
*/
EINTERN int
e_toolbar_init(void)
{
   tb_location = e_gadcon_location_new(_("EFM Toolbar"), E_GADCON_SITE_EFM_TOOLBAR,
                                       NULL, NULL, NULL, NULL);
                                    //_e_toolbar_gadget_add, NULL,
                                    //_e_toolbar_gadget_remove, NULL);
   e_gadcon_location_set_icon_name(tb_location, "configure-toolbars");
   e_gadcon_location_register(tb_location);
   return 1;
}

EINTERN int
e_toolbar_shutdown(void)
{
   while (toolbars)
     {
        E_Toolbar *tbar;

        tbar = eina_list_data_get(toolbars);
        e_object_del(E_OBJECT(tbar));
     }
   e_gadcon_location_unregister(tb_location);
   E_FREE_FUNC(tb_location, e_gadcon_location_free);
   return 1;
}

E_API E_Toolbar *
e_toolbar_new(Evas *evas, const char *name, Evas_Object *fwin, Evas_Object *fm2)
{
   E_Toolbar *tbar = NULL;

   if (!name) return NULL;
   if ((!fwin) || (!fm2)) return NULL;

   tbar = E_OBJECT_ALLOC(E_Toolbar, E_TOOLBAR_TYPE, _e_toolbar_free);
   if (!tbar) return NULL;
   tbar->id = 1;
//   tbar->id = eina_list_count(toolbars) + 1;
   tbar->evas = evas;
   tbar->name = eina_stringshare_add(name);
   tbar->fwin = fwin;
   tbar->fm2 = fm2;

   evas_object_smart_callback_add(tbar->fm2, "changed",
                                  _e_toolbar_fm2_changed, tbar);
   evas_object_smart_callback_add(tbar->fm2, "dir_changed",
                                  _e_toolbar_fm2_dir_changed, tbar);
   evas_object_smart_callback_add(tbar->fm2, "dir_deleted",
                                  _e_toolbar_fm2_dir_deleted, tbar);
   evas_object_smart_callback_add(tbar->fm2, "files_deleted",
                                  _e_toolbar_fm2_files_deleted, tbar);
   evas_object_smart_callback_add(tbar->fm2, "selected",
                                  _e_toolbar_fm2_selected, tbar);
   evas_object_smart_callback_add(tbar->fm2, "selection_change",
                                  _e_toolbar_fm2_selection_changed, tbar);

   tbar->o_base = edje_object_add(evas);
   e_theme_edje_object_set(tbar->o_base, "base/theme/fileman/toolbar",
                           "e/fileman/toolbar/default/base");
   evas_object_event_callback_add(tbar->o_base, EVAS_CALLBACK_RESIZE,
                                  _tb_resize, tbar);
   tbar->o_event = evas_object_rectangle_add(evas);
   evas_object_color_set(tbar->o_event, 0, 0, 0, 0);
   evas_object_show(tbar->o_event);
   edje_object_part_swallow(tbar->o_base, "e.swallow.event", tbar->o_event);
   evas_object_event_callback_add(tbar->o_event, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_toolbar_cb_mouse_down, tbar);

   tbar->gadcon = e_gadcon_swallowed_new(tbar->name, tbar->id, tbar->o_base,
                                         "e.swallow.content");
   e_gadcon_size_request_callback_set(tbar->gadcon,
                                      _e_toolbar_gadcon_size_request, tbar);
   /* FIXME: We want to implement "styles" here ? */

   e_toolbar_orient(tbar, E_GADCON_ORIENT_TOP);

   e_gadcon_toolbar_set(tbar->gadcon, tbar);
   tbar->gadcon->location = tb_location;
   e_gadcon_ecore_evas_set(tbar->gadcon, ecore_evas_ecore_evas_get(evas_object_evas_get(tbar->fwin)));
   e_gadcon_util_menu_attach_func_set(tbar->gadcon,
                                      _e_toolbar_menu_items_append, tbar);
   e_gadcon_populate(tbar->gadcon);

   _e_toolbar_gadcon_size_request(tbar, tbar->gadcon, 0, 0);
   toolbars = eina_list_append(toolbars, tbar);
   return tbar;
}

E_API void
e_toolbar_fwin_set(E_Toolbar *tbar, Evas_Object *fwin)
{
   E_OBJECT_CHECK(tbar);
   E_OBJECT_TYPE_CHECK(tbar, E_TOOLBAR_TYPE);
   tbar->fwin = fwin;
}

E_API Evas_Object *
e_toolbar_fwin_get(E_Toolbar *tbar)
{
   E_OBJECT_CHECK_RETURN(tbar, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(tbar, E_TOOLBAR_TYPE, NULL);
   return tbar->fwin;
}

E_API void
e_toolbar_fm2_set(E_Toolbar *tbar, Evas_Object *fm2)
{
   E_OBJECT_CHECK(tbar);
   E_OBJECT_TYPE_CHECK(tbar, E_TOOLBAR_TYPE);
   tbar->fm2 = fm2;
}

E_API Evas_Object *
e_toolbar_fm2_get(E_Toolbar *tbar)
{
   E_OBJECT_CHECK_RETURN(tbar, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(tbar, E_TOOLBAR_TYPE, NULL);
   return tbar->fm2;
}

E_API void
e_toolbar_orient(E_Toolbar *tbar, E_Gadcon_Orient orient)
{
   char buf[4096];

   E_OBJECT_CHECK(tbar);
   E_OBJECT_TYPE_CHECK(tbar, E_TOOLBAR_TYPE);
   e_gadcon_orient(tbar->gadcon, orient);
   snprintf(buf, sizeof(buf), "e,state,orientation,%s",
            _e_toolbar_orient_string_get(tbar));
   edje_object_signal_emit(tbar->o_base, buf, "e");
   edje_object_message_signal_process(tbar->o_base);
}

E_API void
e_toolbar_populate(E_Toolbar *tbar)
{
   E_OBJECT_CHECK(tbar);
   E_OBJECT_TYPE_CHECK(tbar, E_TOOLBAR_TYPE);
   e_gadcon_populate(tbar->gadcon);
}

/* local functions */
static void
_e_toolbar_free(E_Toolbar *tbar)
{
   toolbars = eina_list_remove(toolbars, tbar);

   evas_object_smart_callback_del_full(tbar->fm2, "changed",
                                       _e_toolbar_fm2_changed, tbar);
   evas_object_smart_callback_del_full(tbar->fm2, "dir_changed",
                                       _e_toolbar_fm2_dir_changed, tbar);
   evas_object_smart_callback_del_full(tbar->fm2, "dir_deleted",
                                       _e_toolbar_fm2_dir_deleted, tbar);
   evas_object_smart_callback_del_full(tbar->fm2, "files_deleted",
                                       _e_toolbar_fm2_files_deleted, tbar);
   evas_object_smart_callback_del_full(tbar->fm2, "selected",
                                       _e_toolbar_fm2_selected, tbar);
   evas_object_smart_callback_del_full(tbar->fm2, "selection_change",
                                       _e_toolbar_fm2_selection_changed, tbar);
   if (tbar->menu)
     {
        e_menu_post_deactivate_callback_set(tbar->menu, NULL, NULL);
        e_object_del(E_OBJECT(tbar->menu));
        tbar->menu = NULL;
     }
   if (tbar->cfg_dlg) e_object_del(E_OBJECT(tbar->cfg_dlg));
   e_object_del(E_OBJECT(tbar->gadcon));
   if (tbar->name) eina_stringshare_del(tbar->name);
   evas_object_del(tbar->o_event);
   evas_object_del(tbar->o_base);
   E_FREE(tbar);
}

static void
_e_toolbar_cb_mouse_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   E_Toolbar *tbar;
   E_Menu *mn;
   E_Zone *zone;
   int x, y;

   ev = event_info;
   tbar = data;
   if (ev->button != 3) return;
   mn = e_menu_new();
   e_menu_post_deactivate_callback_set(mn, _e_toolbar_menu_cb_post, tbar);
   tbar->menu = mn;
   _e_toolbar_menu_append(tbar, mn);
   zone = e_zone_current_get();
   ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
   e_menu_activate_mouse(mn, zone, x, y, 1, 1,
                         E_MENU_POP_DIRECTION_DOWN, ev->timestamp);
}

static void
_e_toolbar_menu_cb_post(void *data, E_Menu *mn EINA_UNUSED)
{
   E_Toolbar *tbar;

   tbar = data;
   if (!tbar->menu) return;
   e_object_del(E_OBJECT(tbar->menu));
   tbar->menu = NULL;
}

static void
_e_toolbar_menu_cb_pre(void *data, E_Menu *mn)
{
   E_Toolbar *tbar;
   E_Menu_Item *mi;

   tbar = data;
   e_menu_pre_activate_callback_set(mn, NULL, NULL);
/*
   mi = e_menu_item_new(mn);
   if (tbar->gadcon->editing)
     e_menu_item_label_set(mi, _("Stop Moving Items"));
   else
     e_menu_item_label_set(mi, _("Begin Moving Items"));
   e_util_menu_item_theme_icon_set(mi, "transform-scale");
   e_menu_item_callback_set(mi, _e_toolbar_menu_cb_edit, tbar);

   mi = e_menu_item_new(mn);
   e_menu_item_separator_set(mi, 1);
*/
   mi = e_menu_item_new(mn);
   e_menu_item_label_set(mi, _("Toolbar Settings"));
   e_util_menu_item_theme_icon_set(mi, "configure");
   e_menu_item_callback_set(mi, _e_toolbar_menu_cb_config, tbar);
/*
   mi = e_menu_item_new(mn);
   e_menu_item_label_set(mi, _("Set Toolbar Contents"));
   e_util_menu_item_theme_icon_set(mi, "preferences-toolbar");
   e_menu_item_callback_set(mi, _e_toolbar_menu_cb_contents, tbar);
*/
}

static void
_e_toolbar_menu_items_append(void *data, E_Gadcon_Client *gcc EINA_UNUSED, E_Menu *mn)
{
   E_Toolbar *tbar;

   tbar = data;
   _e_toolbar_menu_append(tbar, mn);
}

static void
_e_toolbar_menu_append(E_Toolbar *tbar, E_Menu *mn)
{
   E_Menu_Item *mi;
   E_Menu *subm;

   subm = e_menu_new();
   mi = e_menu_item_new(mn);
   e_menu_item_label_set(mi, tbar->name);
   e_util_menu_item_theme_icon_set(mi, "preferences-toolbar");
   e_menu_pre_activate_callback_set(subm, _e_toolbar_menu_cb_pre, tbar);
   e_menu_item_submenu_set(mi, subm);
   e_object_unref(E_OBJECT(subm));
}
/*
static void
_e_toolbar_menu_cb_edit(void *data, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Toolbar *tbar;

   tbar = data;
   if (tbar->gadcon->editing)
     e_gadcon_edit_end(tbar->gadcon);
   else
     e_gadcon_edit_begin(tbar->gadcon);
}
*/
static void
_e_toolbar_menu_cb_config(void *data, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Toolbar *tbar;

   tbar = data;
   if (!tbar->cfg_dlg) e_int_toolbar_config(tbar);
}
/*
static void
_e_toolbar_menu_cb_contents(void *data, E_Menu *mn EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
{
   E_Toolbar *tbar;

   tbar = data;
   if (!tbar->gadcon->config_dialog) e_int_gadcon_config_toolbar(tbar->gadcon);
}
*/
static void
_e_toolbar_gadcon_size_request(void *data, E_Gadcon *gc, Evas_Coord w, Evas_Coord h)
{
   E_Toolbar *tbar;
   Evas_Coord ww, hh, nw, nh;

   tbar = data;
   ww = hh = 0;
   evas_object_geometry_get(gc->o_container, NULL, NULL, &ww, &hh);
   switch (gc->orient)
     {
      case E_GADCON_ORIENT_TOP:
      case E_GADCON_ORIENT_BOTTOM:
        w = ww;
        h = 32;
        break;

      case E_GADCON_ORIENT_LEFT:
      case E_GADCON_ORIENT_RIGHT:
        w = 32;
        h = hh;
        break;

      default:
        break;
     }
   e_gadcon_swallowed_min_size_set(gc, w, h);
   edje_object_size_min_calc(tbar->o_base, &nw, &nh);
   tbar->minw = nw;
   tbar->minh = nh;
}

static const char *
_e_toolbar_orient_string_get(E_Toolbar *tbar)
{
   const char *sig = "";

   switch (tbar->gadcon->orient)
     {
      case E_GADCON_ORIENT_HORIZ:
        sig = "horizontal";
        break;

      case E_GADCON_ORIENT_VERT:
        sig = "vertical";
        break;

      case E_GADCON_ORIENT_LEFT:
        sig = "left";
        break;

      case E_GADCON_ORIENT_RIGHT:
        sig = "right";
        break;

      case E_GADCON_ORIENT_TOP:
        sig = "top";
        break;

      case E_GADCON_ORIENT_BOTTOM:
        sig = "bottom";
        break;

      default:
        break;
     }
   return sig;
}

static void
_e_toolbar_fm2_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "changed", tbar);
     }
}

static void
_e_toolbar_fm2_dir_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "dir_changed", tbar);
     }
}

static void
_e_toolbar_fm2_dir_deleted(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "dir_deleted", tbar);
     }
}

static void
_e_toolbar_fm2_files_deleted(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "files_deleted", tbar);
     }
}

static void
_e_toolbar_fm2_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "selected", tbar);
     }
}

static void
_e_toolbar_fm2_selection_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Toolbar *tbar;
   Eina_List *l = NULL;
   E_Gadcon_Client *gcc = NULL;

   tbar = data;
   if (!tbar) return;
   EINA_LIST_FOREACH(tbar->gadcon->clients, l, gcc)
     {
        if (!gcc) continue;
        evas_object_smart_callback_call(gcc->o_base, "selection_changed", tbar);
     }
}

#include "e.h"

static Ecore_Con_Url *url_up = NULL;
static Eina_List *handlers = NULL;
static Ecore_Timer *update_timer = NULL;
static E_Dialog *dialog = NULL;
static char *machid = NULL;

static void
_update_done(void)
{
   if (url_up)
     {
        ecore_con_url_free(url_up);
        url_up = NULL;
     }
}

static void
_delete_cb(void *obj EINA_UNUSED)
{
   dialog = NULL;
}

static void
_ok_cb(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(dialog));
   if (e_config->update.later > 0)
     {
        e_config->update.later = 0;
        e_config_save_queue();
     }
}

static void
_bother_me_later_cb(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   e_object_del(E_OBJECT(dialog));
   // 5 * 5 * 1hr === about 1 day limit, so bother-me later will wait
   // a day in between botherings. botherings reset on e start or restart
   if (e_config->update.later < 5)
     {
        e_config->update.later++;
        e_config_save_queue();
     }
}

static void
_never_tell_me_cb(void *data EINA_UNUSED, E_Dialog *dia EINA_UNUSED)
{
   if (update_timer) ecore_timer_del(update_timer);
   update_timer = NULL;
   e_object_del(E_OBJECT(dialog));
   e_config->update.check = 0;
   e_config->update.later = 0;
   e_config_save_queue();
}

static void
_new_version(const char *ver)
{
   char text[2048];

   if (dialog) return;

   dialog = e_dialog_new(NULL, "E", "_update_available");

   e_object_del_attach_func_set(E_OBJECT(dialog), _delete_cb);
   e_dialog_button_add(dialog, _("OK"), NULL,
                       _ok_cb, NULL);
   e_dialog_button_add(dialog, _("Bother me later"), NULL,
                       _bother_me_later_cb, NULL);
   e_dialog_button_add(dialog, _("Never tell me"), NULL,
                       _never_tell_me_cb, NULL);
   e_dialog_button_focus_num(dialog, 1);
   e_dialog_title_set(dialog, _("Update Notice"));
   e_dialog_icon_set(dialog, "dialog-warning", 64);

   snprintf(text, sizeof(text),
            _("Your enlightenment version is<ps/>"
              "not the current latest release.<ps/>"
              "The latest version is:<ps/>"
              "<ps/>"
              "%s<ps/>"
              "<ps/>"
              "Please visit www.enlightenment.org<ps/>"
              "or update your system packages<ps/>"
              "to get a new version."), ver);
   e_dialog_text_set(dialog, text);
   elm_win_center(dialog->win, 1, 1);
   e_dialog_show(dialog);
}

static Eina_Bool
_upload_data_cb(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{
   Ecore_Con_Event_Url_Data *ev = event;
   if (ev->url_con != url_up) return EINA_TRUE;
   if (ev->size > 0)
     {
        char *txt = alloca(ev->size + 1);

        memcpy(txt, ev->data, ev->size);
        txt[ev->size] = 0;

        if (!strncmp(txt, "OK", 2))
          {
          }
        else if (!strncmp(txt, "OLD", 3))
          {
             char *ver = strchr(txt, ' ');
             if (ver)
               {
                  ver++;
                  _new_version(ver);
               }
          }
     }
   return EINA_FALSE;
}

static Eina_Bool
_upload_progress_cb(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{
   Ecore_Con_Event_Url_Progress *ev = event;
   if (ev->url_con != url_up) return EINA_TRUE;
   return EINA_FALSE;
}

static Eina_Bool
_upload_complete_cb(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event)
{
   Ecore_Con_Event_Url_Complete *ev = event;
   if (ev->url_con != url_up) return EINA_TRUE;
   if (ev->status != 200)
     {
        _update_done();
        return EINA_FALSE;
     }
   _update_done();
   return EINA_FALSE;
}

static void
_update_machid_get(void)
{
   FILE *f;
   char buf[4096], *c;
   size_t len;

   f = fopen("/etc/machine-id", "r");
   if (!f) f = fopen("/var/lib/dbus/machine-id", "r");
   if (!f)
     {
        e_user_dir_concat_static(buf, ".machid");
        f = fopen(buf, "r");
     }
   if (f)
     {
        len = fread(buf, 1, sizeof(buf) - 1, f);
        if (len > 10)
          {
             buf[len] = 0;
             for (c = buf; *c; c++)
               {
                  if (!isalnum(*c))
                    {
                       *c = 0;
                       break;
                    }
               }
             machid = strdup(buf);
             fclose(f);
             return;
          }
        fclose(f);
     }

   // generate ID
   e_user_dir_concat_static(buf, ".machid");
   f = fopen(buf, "w");
   if (f)
     {
        double t;
        fwrite("GEN-", 4, 1, f);
        t = ecore_time_unix_get();
        fprintf(f, "%1.16f-%i-%i\n", t, rand(), rand());
        fclose(f);
        _update_machid_get();
        return;
     }
   // this just is all a wash - just use this
   machid = strdup("NOIDEAWHATTHEIDOFTHISMACHINEIS");
}

static void
_update_post_generate(char *buf, int size)
{
   if (!machid) _update_machid_get();
   if (!machid) return;
   snprintf(buf, size,
            "CLIENT %s\n"
            "UPDATE enlightenment %s",
            machid, VERSION);
}

static void
_update_check(void)
{
   char buf[1024];

   if (url_up) _update_done();
   url_up = ecore_con_url_new("http://www.enlightenment.org/update.php");
   if (url_up)
     {
        _update_post_generate(buf, sizeof(buf));
        ecore_con_url_http_version_set(url_up, ECORE_CON_URL_HTTP_VERSION_1_0);
        ecore_con_url_post(url_up, buf, strlen(buf), "text/plain");
     }
   else
     _update_done();
}

static Eina_Bool
_update_timeout_cb(void *data)
{
   double t = 3600.0; // base minimum betwene checks -> 1hr min
   int later = e_config->update.later;

   if (e_config->update.check) _update_check();
   if (update_timer) ecore_timer_del(update_timer);
   if (later > 0)
     {
        later++;
        t *= (later * later);
     }
   update_timer = ecore_timer_loop_add(t, _update_timeout_cb, data);
   return EINA_FALSE;
}

EINTERN int
e_update_init(void)
{
   if (ecore_con_url_init())
     {
        handlers = eina_list_append
            (handlers, ecore_event_handler_add
              (ECORE_CON_EVENT_URL_DATA, _upload_data_cb, NULL));
        handlers = eina_list_append
            (handlers, ecore_event_handler_add
              (ECORE_CON_EVENT_URL_PROGRESS, _upload_progress_cb, NULL));
        handlers = eina_list_append
            (handlers, ecore_event_handler_add
              (ECORE_CON_EVENT_URL_COMPLETE, _upload_complete_cb, NULL));
        if (e_config->update.check)
          {
             e_config->update.check = 0;
             _update_timeout_cb(NULL);
             e_config->update.check = 1;
          }
     }
   return 1;
}

EINTERN int
e_update_shutdown(void)
{
   if (handlers)
     {
        _update_done();
        ecore_con_url_shutdown();
     }
   if (update_timer)
     {
        ecore_timer_del(update_timer);
        update_timer = NULL;
     }
   if (dialog) e_object_del(E_OBJECT(dialog));
   dialog = NULL;
   _update_done();
   if (machid)
     {
        free(machid);
        machid = NULL;
     }
   return 1;
}

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <Efreet.h>

#include "e_macros.h"

static const char *_e_user_homedir = NULL;
static size_t _e_user_homedir_len = 0;

/* externally accessible functions */
E_API const char *
e_user_homedir_get(void)
{
   char *d;

   if (_e_user_homedir)
     return _e_user_homedir;

   _e_user_homedir = d = getenv("HOME");
   if (!_e_user_homedir)
     {
        _e_user_homedir = "/tmp";
        _e_user_homedir_len = sizeof("/tmp") - 1;
        return _e_user_homedir;
     }

   _e_user_homedir_len = strlen(_e_user_homedir);
   while ((_e_user_homedir_len > 1) &&
          (d[_e_user_homedir_len - 1] == '/'))
     {
        _e_user_homedir_len--;
        d[_e_user_homedir_len] = '\0';
     }
   return _e_user_homedir;
}

/**
 * Concatenate '~/' and @a path.
 *
 * @return similar to snprintf(), this returns the number of bytes written or
 *     that would be required to write if greater or equal than size.
 */
E_API size_t
e_user_homedir_concat_len(char *dst, size_t size, const char *path, size_t path_len)
{
   if (!_e_user_homedir)
     e_user_homedir_get();

   return eina_str_join_len(dst, size, '/', _e_user_homedir, _e_user_homedir_len, path, path_len);
}

E_API size_t
e_user_homedir_concat(char *dst, size_t size, const char *path)
{
   return e_user_homedir_concat_len(dst, size, path, strlen(path));
}

/**
 * same as snprintf("~/"fmt, ...).
 */
E_API size_t EINA_PRINTF(3, 4)
e_user_homedir_snprintf(char *dst, size_t size, const char *fmt, ...)
{
   size_t off, ret;
   va_list ap;

   if (!_e_user_homedir)
     e_user_homedir_get();
   if (!_e_user_homedir)
     return 0;

   va_start(ap, fmt);

   off = _e_user_homedir_len + 1;
   if (size < _e_user_homedir_len + 2)
     {
        if (size > 1)
          {
             memcpy(dst, _e_user_homedir, size - 1);
             dst[size - 1] = '\0';
          }
        ret = off + vsnprintf(dst + off, size - off, fmt, ap);
        va_end(ap);
        return ret;
     }

   memcpy(dst, _e_user_homedir, _e_user_homedir_len);
   dst[_e_user_homedir_len] = '/';

   ret = off + vsnprintf(dst + off, size - off, fmt, ap);
   va_end(ap);
   return ret;
}

/**
 * Return the directory where user .desktop files should be stored.
 * If the directory does not exist, it will be created. If it cannot be
 * created, a dialog will be displayed an NULL will be returned
 */
E_API const char *
e_user_desktop_dir_get(void)
{
   static char dir[PATH_MAX] = "";

   if (!dir[0])
     snprintf(dir, sizeof(dir), "%s/applications", efreet_data_home_get());

   return dir;
}

/**
 * Return the directory where user .icon files should be stored.
 * If the directory does not exist, it will be created. If it cannot be
 * created, a dialog will be displayed an NULL will be returned
 */
E_API const char *
e_user_icon_dir_get(void)
{
   static char dir[PATH_MAX] = "";

   if (!dir[0])
     snprintf(dir, sizeof(dir), "%s/icons", efreet_data_home_get());

   return dir;
}

static const char *_e_user_dir = NULL;
static size_t _e_user_dir_len = 0;

E_API void         e_util_env_set(const char *var, const char *val);

/**
 * Return ~/.e/e
 */
E_API const char *
e_user_dir_get(void)
{
   static char dir[PATH_MAX] = "";

   if (!dir[0])
     {
        char *d = getenv("E_HOME");

        if (d)
          snprintf(dir, sizeof(dir), "E_HOME_DIR=%s/e", d);
        else
          {
#ifdef DOXDG
             if ((d = getenv("XDG_CONFIG_HOME")))
               snprintf(dir, sizeof(dir), "E_HOME_DIR=%s/e", d);
             else
#endif
               {
                  char buf[PATH_MAX - 128];
#ifdef DOXDG
                  e_user_homedir_concat(buf, sizeof(buf), ".config/e");
#else
                  e_user_homedir_concat(buf, sizeof(buf), ".e/e");
#endif
                  snprintf(dir, sizeof(dir), "E_HOME_DIR=%s", buf);
               }
          }
        _e_user_dir = dir + strlen("E_HOME_DIR=");
        _e_user_dir_len = strlen(_e_user_dir);
        putenv(dir);
     }
   return _e_user_dir;
}

/**
 * Concatenate '~/.e/e' and @a path.
 *
 * @return similar to snprintf(), this returns the number of bytes written or
 *     that would be required to write if greater or equal than size.
 */
E_API size_t
e_user_dir_concat_len(char *dst, size_t size, const char *path, size_t path_len)
{
   if (!_e_user_dir)
     e_user_dir_get();

   return eina_str_join_len(dst, size, '/', _e_user_dir, _e_user_dir_len, path, path_len);
}

E_API size_t
e_user_dir_concat(char *dst, size_t size, const char *path)
{
   return e_user_dir_concat_len(dst, size, path, strlen(path));
}

/**
 * same as snprintf("~/.e/e/"fmt, ...).
 */
E_API size_t EINA_PRINTF(3, 4)
e_user_dir_snprintf(char *dst, size_t size, const char *fmt, ...)
{
   size_t off, ret;
   va_list ap;

   if (!_e_user_dir)
     e_user_dir_get();
   if (!_e_user_dir)
     return 0;

   va_start(ap, fmt);

   off = _e_user_dir_len + 1;
   if (size < _e_user_dir_len + 2)
     {
        if (size > 1)
          {
             memcpy(dst, _e_user_dir, size - 1);
             dst[size - 1] = '\0';
          }
        ret = off + vsnprintf(dst + off, size - off, fmt, ap);
        va_end(ap);
        return ret;
     }

   memcpy(dst, _e_user_dir, _e_user_dir_len);
   dst[_e_user_dir_len] = '/';

   ret = off + vsnprintf(dst + off, size - off, fmt, ap);
   va_end(ap);
   return ret;
}

#include "e.h"

E_API E_Path * path_data = NULL;
E_API E_Path * path_images = NULL;
E_API E_Path * path_fonts = NULL;
E_API E_Path * path_icons = NULL;
E_API E_Path * path_modules = NULL;
E_API E_Path * path_backgrounds = NULL;
E_API E_Path * path_messages = NULL;

/* local subsystem functions */
static Eina_Bool    _e_util_cb_delayed_del(void *data);
static Eina_Bool    _e_util_wakeup_cb(void *data);

static Evas_Object *_e_util_icon_add(const char *path, Evas *evas, int size);

static void         _e_util_cb_delayed_cancel(void *data, void *obj);

/* local subsystem globals */
static Ecore_Timer *_e_util_dummy_timer = NULL;

/* externally accessible functions */
E_API void
e_util_wakeup(void)
{
   if (_e_util_dummy_timer) return;
   _e_util_dummy_timer = ecore_timer_loop_add(0.0, _e_util_wakeup_cb, NULL);
}

E_API void
e_util_env_set(const char *var, const char *val)
{
   if (val)
     {
#ifdef HAVE_SETENV
        setenv(var, val, 1);
#else
        char buf[8192];

        snprintf(buf, sizeof(buf), "%s=%s", var, val);
        putenv(strdup(buf));
#endif
     }
   else
     {
#ifdef HAVE_UNSETENV
        unsetenv(var);
#else
        if (getenv(var)) putenv(var);
#endif
     }
}

E_API int
e_util_glob_match(const char *str, const char *pattern)
{
   if ((!str) || (!pattern)) return 0;
   if (pattern[0] == 0)
     {
        if (str[0] == 0) return 1;
        return 0;
     }
   if (str == pattern) return 1;
   if (!strcmp(pattern, "*")) return 1;
   if (eina_fnmatch(pattern, str, 0)) return 1;
   return 0;
}

E_API int
e_util_glob_case_match(const char *str, const char *pattern)
{
   const char *p;
   char *tstr, *tglob, *tp;

   if (str == pattern) return 1;
   if (pattern[0] == 0)
     {
        if (str[0] == 0) return 1;
        return 0;
     }
   if (!strcmp(pattern, "*")) return 1;
   tstr = alloca(strlen(str) + 1);
   for (tp = tstr, p = str; *p != 0; p++, tp++)
     *tp = tolower(*p);
   *tp = 0;
   tglob = alloca(strlen(pattern) + 1);
   for (tp = tglob, p = pattern; *p != 0; p++, tp++)
     *tp = tolower(*p);
   *tp = 0;
   if (eina_fnmatch(tglob, tstr, 0)) return 1;
   return 0;
}

E_API int
e_util_strcmp(const char *s1, const char *s2)
{
   if ((s1) && (s2))
     {
        if (s1 == s2) return 0;
        return strcmp(s1, s2);
     }
   return 0x7fffffff;
}

E_API int
e_util_strcasecmp(const char *s1, const char *s2)
{
   if ((!s1) && (!s2)) return 0;
   if (!s1) return -1;
   if (!s2) return 1;
   return strcasecmp(s1, s2);
}

E_API int
e_util_both_str_empty(const char *s1, const char *s2)
{
   int empty = 0;

   if ((!s1) && (!s2)) return 1;
   if ((!s1) || ((s1) && (s1[0] == 0))) empty++;
   if ((!s2) || ((s2) && (s2[0] == 0))) empty++;
   if (empty == 2) return 1;
   return 0;
}

E_API int
e_util_immortal_check(void)
{
   Eina_List *wins;

   wins = e_clients_immortal_list();
   if (wins)
     {
        e_util_dialog_show(_("Cannot exit - immortal windows."),
                           _("Some windows are left still around with the Lifespan lock enabled. This means<ps/>"
                             "that Enlightenment will not allow itself to exit until these windows have<ps/>"
                             "been closed or have the lifespan lock removed.<ps/>"));
        /* FIXME: should really display a list of these lifespan locked */
        /* windows in a dialog and let the user disable their locks in */
        /* this dialog */
        eina_list_free(wins);
        return 1;
     }
   return 0;
}

E_API int
e_util_edje_icon_check(const char *name)
{
   const char *file;
   char buf[4096];

   if ((!name) || (!name[0])) return 0;
   snprintf(buf, sizeof(buf), "e/icons/%s", name);
   file = e_theme_edje_file_get("base/theme/icons", buf);
   if (file[0]) return 1;
   return 0;
}

/* WARNING This function is deprecated,. must be made static.
 * You should use e_util_icon_theme_set instead
 */
E_API int
e_util_edje_icon_set(Evas_Object *obj, const char *name)
{
   const char *file;
   char buf[4096];

   if ((!name) || (!name[0])) return 0;
   snprintf(buf, sizeof(buf), "e/icons/%s", name);
   file = e_theme_edje_file_get("base/theme/icons", buf);
   if (file[0])
     {
        edje_object_file_set(obj, file, buf);
        return 1;
     }
   return 0;
}

static int
_e_util_icon_theme_set(Evas_Object *obj, const char *icon, Eina_Bool fallback)
{
   const char *file;
   char buf[4096];

   if ((!icon) || (!icon[0])) return 0;
   snprintf(buf, sizeof(buf), "e/icons/%s", icon);

   if (fallback)
     file = e_theme_edje_icon_fallback_file_get(buf);
   else
     file = e_theme_edje_file_get("base/theme/icons", buf);

   if (file[0])
     {
        e_icon_file_edje_set(obj, file, buf);
        return 1;
     }

   return 0;
}

static int
_e_util_icon_fdo_set(Evas_Object *obj, const char *icon)
{
   const char *path = NULL;
   unsigned int size;

   if ((!icon) || (!icon[0])) return 0;
   size = e_icon_scale_size_get(obj);
   if (size < 16) size = 16;
   size = e_util_icon_size_normalize(size * e_scale);

   path = efreet_icon_path_find(e_config->icon_theme, icon, size);
   if (!path) return 0;

   e_icon_file_set(obj, path);
   return 1;
}

/* use e_icon_size_scale_set(obj, size) to set the preferred icon size */
E_API int
e_util_icon_theme_set(Evas_Object *obj, const char *icon)
{
   if (icon && (icon[0] == '/'))
     {
        e_icon_file_set(obj, icon);
        return 1;
     }
   if (e_config->icon_theme_overrides)
     {
        if (_e_util_icon_fdo_set(obj, icon))
          return 1;
        if (_e_util_icon_theme_set(obj, icon, EINA_FALSE))
          return 1;
        return _e_util_icon_theme_set(obj, icon, EINA_TRUE);
     }
   else
     {
        if (_e_util_icon_theme_set(obj, icon, EINA_FALSE))
          return 1;
        if (_e_util_icon_fdo_set(obj, icon))
          return 1;
        return _e_util_icon_theme_set(obj, icon, EINA_TRUE);
     }
}

int
_e_util_menu_item_edje_icon_set(E_Menu_Item *mi, const char *name, Eina_Bool fallback)
{
   const char *file;
   char buf[4096];

   if ((!name) || (!name[0])) return 0;

   if ((!fallback) && (name[0] == '/') && ecore_file_exists(name))
     {
        e_menu_item_icon_edje_set(mi, name, "icon");
        return 1;
     }
   snprintf(buf, sizeof(buf), "e/icons/%s", name);

   if (fallback)
     file = e_theme_edje_icon_fallback_file_get(buf);
   else
     file = e_theme_edje_file_get("base/theme/icons", buf);

   if (file[0])
     {
        e_menu_item_icon_edje_set(mi, file, buf);
        return 1;
     }
   return 0;
}

E_API unsigned int
e_util_icon_size_normalize(unsigned int desired)
{
   const unsigned int *itr, known_sizes[] =
   {
      16, 22, 24, 32, 36, 48, 64, 72, 96, 128, 192, 256, 0
   };

   for (itr = known_sizes; *itr > 0; itr++)
     if (*itr >= desired)
       return *itr;

   return 256; /* largest know size? */
}

static int
_e_util_menu_item_fdo_icon_set(E_Menu_Item *mi, const char *icon)
{
   const char *path = NULL;
   unsigned int size;

   if ((!icon) || (!icon[0])) return 0;
   size = e_util_icon_size_normalize(96 * e_scale);
   path = efreet_icon_path_find(e_config->icon_theme, icon, size);
   if (!path) return 0;
   e_menu_item_icon_file_set(mi, path);
   return 1;
}

E_API int
e_util_menu_item_theme_icon_set(E_Menu_Item *mi, const char *icon)
{
   if (e_config->icon_theme_overrides)
     {
        if (_e_util_menu_item_fdo_icon_set(mi, icon))
          return 1;
        if (_e_util_menu_item_edje_icon_set(mi, icon, EINA_FALSE))
          return 1;
        return _e_util_menu_item_edje_icon_set(mi, icon, EINA_TRUE);
     }
   else
     {
        if (_e_util_menu_item_edje_icon_set(mi, icon, EINA_FALSE))
          return 1;
        if (_e_util_menu_item_fdo_icon_set(mi, icon))
          return 1;
        return _e_util_menu_item_edje_icon_set(mi, icon, EINA_TRUE);
     }
}

E_API const char *
e_util_mime_icon_get(const char *mime, unsigned int size)
{
   char buf[1024];
   const char *file = NULL;

   if (e_config->icon_theme_overrides)
     file = efreet_mime_type_icon_get(mime, e_config->icon_theme, e_util_icon_size_normalize(size));
   if (file) return file;

   if (snprintf(buf, sizeof(buf), "e/icons/fileman/mime/%s", mime) >= (int)sizeof(buf))
     return NULL;

   file = e_theme_edje_file_get("base/theme/icons", buf);
   if (file && file[0]) return file;
   return efreet_mime_type_icon_get(mime, e_config->icon_theme, e_util_icon_size_normalize(size));
}

E_API E_Client *
e_util_desk_client_above(E_Client *ec)
{
   E_Client *ec2;

   E_OBJECT_CHECK_RETURN(ec, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, NULL);

   for (ec2 = e_client_above_get(ec); ec2; ec2 = e_client_above_get(ec2))
     {
        if ((ec2->desk == ec->desk) || (ec2->sticky)) return ec2;
     }
   return NULL;
}

E_API E_Client *
e_util_desk_client_below(E_Client *ec)
{
   E_Client *ec2;

   E_OBJECT_CHECK_RETURN(ec, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(ec, E_CLIENT_TYPE, NULL);

   for (ec2 = e_client_below_get(ec); ec2; ec2 = e_client_below_get(ec2))
     {
        if ((ec2->desk == ec->desk) || (ec2->sticky)) return ec2;
     }
   return NULL;
}

E_API int
e_util_edje_collection_exists(const char *file, const char *coll)
{
   Eina_List *clist;
   Eina_Stringshare *str;
   Eina_Bool ret = EINA_FALSE;

   clist = edje_file_collection_list(file);
   EINA_LIST_FREE(clist, str)
     {
        if ((!ret) && (!strcmp(coll, str)))
          ret = EINA_TRUE;
        eina_stringshare_del(str);
     }
   return ret;
}

E_API E_Dialog *
e_util_dialog_internal(const char *title, const char *txt)
{
   E_Dialog *dia;

   dia = e_dialog_new(NULL, "E", "_error_dialog");
   if (!dia) return NULL;
   e_dialog_title_set(dia, title);
   e_dialog_text_set(dia, txt);
   e_dialog_icon_set(dia, "dialog-error", 64);
   e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
   e_dialog_button_focus_num(dia, 0);
   elm_win_center(dia->win, 1, 1);
   e_dialog_show(dia);
   return dia;
}

E_API const char *
e_util_filename_escape(const char *filename)
{
   const char *p;
   char *q;
   static char buf[PATH_MAX];

   if (!filename) return NULL;
   p = filename;
   q = buf;
   while (*p)
     {
        if ((q - buf) > 4090) return NULL;
        if (
          (*p == ' ') || (*p == '\t') || (*p == '\n') ||
          (*p == '\\') || (*p == '\'') || (*p == '\"') ||
          (*p == ';') || (*p == '!') || (*p == '#') ||
          (*p == '$') || (*p == '%') || (*p == '&') ||
          (*p == '*') || (*p == '(') || (*p == ')') ||
          (*p == '[') || (*p == ']') || (*p == '{') ||
          (*p == '}') || (*p == '|') || (*p == '<') ||
          (*p == '>') || (*p == '?')
          )
          {
             *q = '\\';
             q++;
          }
        *q = *p;
        q++;
        p++;
     }
   *q = 0;
   return buf;
}


E_API char *
e_util_shell_env_path_eval(const char *path)
{
   /* evaluate things like:
    * $HOME/bling -> /home/user/bling
    * $HOME/bin/$HOSTNAME/blah -> /home/user/bin/localhost/blah
    * etc. etc.
    */
   const char *p, *v2, *v1 = NULL;
   char buf[PATH_MAX], *pd, *s, *vp;
   char *v = NULL;
   int esc = 0, invar = 0;

   for (p = path, pd = buf; (pd < (buf + sizeof(buf) - 1)); p++)
     {
        if (invar)
          {
             if (!((isalnum(*p)) || (*p == '_')))
               {
                  v2 = p;
                  invar = 0;
                  if ((v2 - v1) > 1)
                    {
                       s = alloca(v2 - v1);
                       strncpy(s, v1 + 1, v2 - v1 - 1);
                       s[v2 - v1 - 1] = 0;
                       if (strncmp(s, "XDG", 3))
                         v = getenv(s);
                       else
                         {
                            if (!strcmp(s, "XDG_CONFIG_HOME"))
                              v = (char *)efreet_config_home_get();
                            else if (!strcmp(s, "XDG_CACHE_HOME"))
                              v = (char *)efreet_cache_home_get();
                            else if (!strcmp(s, "XDG_DATA_HOME"))
                              v = (char *)efreet_data_home_get();
                            else if (!strcmp(s, "XDG_DESKTOP_DIR"))
                              v = (char *)efreet_desktop_dir_get();
                            else if (!strcmp(s, "XDG_DOWNLOAD_DIR"))
                              v = (char *)efreet_download_dir_get();
                            else if (!strcmp(s, "XDG_TEMPLATES_DIR"))
                              v = (char *)efreet_templates_dir_get();
                            else if (!strcmp(s, "XDG_PUBLICSHARE_DIR"))
                              v = (char *)efreet_public_share_dir_get();
                            else if (!strcmp(s, "XDG_DOCUMENTS_DIR"))
                              v = (char *)efreet_documents_dir_get();
                            else if (!strcmp(s, "XDG_MUSIC_DIR"))
                              v = (char *)efreet_music_dir_get();
                            else if (!strcmp(s, "XDG_PICTURES_DIR"))
                              v = (char *)efreet_pictures_dir_get();
                            else if (!strcmp(s, "XDG_VIDEOS_DIR"))
                              v = (char *)efreet_videos_dir_get();
                            else if (!strcmp(s, "XDG_RUNTIME_DIR"))
                              v = (char *)efreet_runtime_dir_get();
                         }

                       if (v)
                         {
                            vp = v;
                            while ((*vp) && (pd < (buf + sizeof(buf) - 1)))
                              {
                                 *pd = *vp;
                                 vp++;
                                 pd++;
                              }
                         }
                    }
                  if (pd < (buf + sizeof(buf) - 1))
                    {
                       *pd = *p;
                       pd++;
                    }
               }
          }
        else
          {
             if (esc)
               {
                  *pd = *p;
                  pd++;
               }
             else
               {
                  if (*p == '\\') esc = 1;
                  else if (*p == '$')
                    {
                       invar = 1;
                       v1 = p;
                    }
                  else
                    {
                       *pd = *p;
                       pd++;
                    }
               }
          }
        if (*p == 0) break;
     }
   *pd = 0;
   return strdup(buf);
}

E_API char *
e_util_size_string_get(off_t size)
{
   double dsize;
   char buf[256];

   dsize = (double)size;
   if (dsize < 1024.0) snprintf(buf, sizeof(buf), _("%'.0f bytes"), dsize);
   else
     {
        dsize /= 1024.0;
        if (dsize < 1024) snprintf(buf, sizeof(buf), _("%'.0f KiB"), dsize);
        else
          {
             dsize /= 1024.0;
             if (dsize < 1024) snprintf(buf, sizeof(buf), _("%'.1f MiB"), dsize);
             else
               {
                  dsize /= 1024.0;
                  if (dsize < 1024) snprintf(buf, sizeof(buf), _("%'.1f GiB"), dsize);
                  else
                    {
                       dsize /= 1024.0;
                       snprintf(buf, sizeof(buf), _("%'.1f TiB"), dsize);
                    }
               }
          }
     }
   return strdup(buf);
}

E_API char *
e_util_file_time_get(time_t ftime)
{
   time_t diff, ltime;
   char buf[256];
   char *s = NULL;
   long test;
   ltime = time(NULL);
   diff = ltime - ftime;
   buf[0] = 0;
   if (e_config->explicit_date_time)
     {
        struct tm tm;

        if (localtime_r(&ftime, &tm))
          {
             s = eina_strftime("%F %T", &tm);
             if (s) return s;
          }
     }
   else
     {
        if (ftime > ltime)
          snprintf(buf, sizeof(buf), _("In the future"));
        else
          {
             if (diff <= 60)
               snprintf(buf, sizeof(buf), _("In the last minute"));
             else if (diff >= 31526000)
               {
                  test = diff / 31526000;
                  snprintf(buf, sizeof(buf), P_("Last year", "%li Years ago", test), test);
               }
             else if (diff >= 2592000)
               {
                  test = diff / 2592000;
                  snprintf(buf, sizeof(buf), P_("Last month", "%li Months ago", test), test);
               }
             else if (diff >= 604800)
               {
                  test = diff / 604800;
                  snprintf(buf, sizeof(buf), P_("Last week", "%li Weeks ago", test), test);
               }
             else if (diff >= 86400)
               {
                  test = diff / 86400;
                  snprintf(buf, sizeof(buf), P_("Yesterday", "%li Days ago", test), test);
               }
             else if (diff >= 3600)
               {
                  test = diff / 3600;
                  snprintf(buf, sizeof(buf), P_("An hour ago", "%li Hours ago", test), test);
               }
             else if (diff > 60)
               {
                  test = diff / 60;
                  snprintf(buf, sizeof(buf), P_("A minute ago", "%li Minutes ago", test), test);
               }
          }
     }

   if (buf[0])
     s = strdup(buf);
   else
     s = strdup(_("Unknown"));
   return s;
}

E_API Evas_Object *
e_util_icon_add(const char *path, Evas *evas)
{
   return _e_util_icon_add(path, evas, 64);
}

E_API Evas_Object *
e_util_desktop_icon_add(Efreet_Desktop *desktop, unsigned int size, Evas *evas)
{
   if ((!desktop) || (!desktop->icon)) return NULL;
   return e_util_icon_theme_icon_add(desktop->icon, size, evas);
}

E_API Evas_Object *
e_util_icon_theme_icon_add(const char *icon_name, unsigned int size, Evas *evas)
{
   if (!icon_name) return NULL;
   if (icon_name[0] == '/') return _e_util_icon_add(icon_name, evas, size);
   else
     {
        Evas_Object *obj;
        const char *path;

        path = efreet_icon_path_find(e_config->icon_theme, icon_name, size);
        if (path)
          {
             obj = _e_util_icon_add(path, evas, size);
             return obj;
          }
     }
   return NULL;
}

E_API void
e_util_desktop_menu_item_icon_add(Efreet_Desktop *desktop, unsigned int size, E_Menu_Item *mi)
{
   const char *path = NULL;

   if ((!desktop) || (!desktop->icon)) return;

   if (desktop->icon[0] == '/') path = desktop->icon;
   else path = efreet_icon_path_find(e_config->icon_theme, desktop->icon, size);

   if (path)
     {
        const char *ext;

        ext = strrchr(path, '.');
        if (ext)
          {
             if (strcmp(ext, ".edj") == 0)
               e_menu_item_icon_edje_set(mi, path, "icon");
             else
               e_menu_item_icon_file_set(mi, path);
          }
        else
          e_menu_item_icon_file_set(mi, path);
     }
}

E_API int
e_util_dir_check(const char *dir)
{
   if (!ecore_file_exists(dir))
     {
        if (!ecore_file_mkpath(dir))
          {
             e_util_dialog_show(_("Error creating directory"), _("Failed to create directory: %s .<ps/>Check that you have correct permissions set."), dir);
             return 0;
          }
     }
   else
     {
        if (!ecore_file_is_dir(dir))
          {
             e_util_dialog_show(_("Error creating directory"), _("Failed to create directory: %s .<ps/>A file of that name already exists."), dir);
             return 0;
          }
     }
   return 1;
}

static Eina_Array *_delay_del_array = NULL;
static Ecore_Idle_Enterer *_delay_del_idler = NULL;

static Eina_Bool
_e_util_del_array_contains(E_Object *obj)
{
   unsigned long c, i;

   if (!_delay_del_array) return EINA_FALSE;

   c = eina_array_count_get(_delay_del_array);
   for (i = 0; i < c; i++)
     {
        if (eina_array_data_get(_delay_del_array, i) == obj) return EINA_TRUE;
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_util_cb_delayed_del(void *data EINA_UNUSED)
{
   _delay_del_idler = NULL;

   while (_delay_del_array)
     {
        Eina_Array *arr = _delay_del_array;
        unsigned long c, i;

        _delay_del_array = NULL;
        c = eina_array_count_get(arr);
        for (i = 0; i < c; i++)
          {
             E_Object *obj = eina_array_data_get(arr, i);
             if (obj) e_object_del(obj);
          }
        eina_array_free(arr);
     }
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_util_cb_delayed_cancel(void *data EINA_UNUSED, void *obj)
{
   unsigned long c, i;

   if (!_delay_del_array) return;

   c = eina_array_count_get(_delay_del_array);
   for (i = 0; i < c; i++)
     {
        if (eina_array_data_get(_delay_del_array, i) == obj)
          eina_array_data_set(_delay_del_array, i, NULL);
     }
}

E_API void
e_util_defer_object_del(E_Object *obj)
{
   if (stopping)
     e_object_del(obj);
   else
     {
        if (!_e_util_del_array_contains(obj))
          {
             if (!_delay_del_array)
               {
                  _delay_del_array = eina_array_new(8);
                  if (!_delay_del_idler)
                    _delay_del_idler = ecore_idle_enterer_before_add
                      (_e_util_cb_delayed_del, NULL);
               }
             if (_delay_del_array)
               {
                  if (eina_array_push(_delay_del_array, obj))
                    e_object_delfn_add(obj, _e_util_cb_delayed_cancel, NULL);
               }
          }
     }
}

E_API const char *
e_util_winid_str_get(Ecore_X_Window win)
{
   const char *vals = "qWeRtYuIoP5-$&<~";
   static char id[9];
   unsigned int val;

   val = (unsigned int)win;
   id[0] = vals[(val >> 28) & 0xf];
   id[1] = vals[(val >> 24) & 0xf];
   id[2] = vals[(val >> 20) & 0xf];
   id[3] = vals[(val >> 16) & 0xf];
   id[4] = vals[(val >> 12) & 0xf];
   id[5] = vals[(val >> 8) & 0xf];
   id[6] = vals[(val >> 4) & 0xf];
   id[7] = vals[(val) & 0xf];
   id[8] = 0;
   return id;
}

static int
_win_auto_size_calc(int max, int min)
{
   const float *itr, scales[] = {0.25, 0.3, 0.5, 0.75, 0.8, 0.9, 0.95, -1};

   for (itr = scales; *itr > 0; itr++)
     {
        int value = *itr * max;
        if (value > min) /* not >=, try a bit larger */
          {
             if (min > 10)
               value = E_CLAMP(value, min, min * 1.5);
             return value;
          }
     }

   return min;
}

E_API void
e_util_win_auto_resize_fill(Evas_Object *win)
{
   E_Zone *zone = NULL;
   E_Client *ec;

   ec = e_win_client_get(win);
   if (ec)
     zone = ec->zone;
   if (!zone)
     zone = e_zone_current_get();

   if (zone)
     {
        int w, h, mw, mh;

        e_zone_useful_geometry_get(zone, NULL, NULL, &w, &h);

        evas_object_size_hint_min_get(win, &mw, &mh);
        w = _win_auto_size_calc(w, mw);
        h = _win_auto_size_calc(h, mh);
        evas_object_resize(win, w, h);
     }
}

/* local subsystem functions */

static Evas_Object *
_e_util_icon_add(const char *path, Evas *evas, int size)
{
   Evas_Object *o = NULL;
   const char *ext;

   if (!path) return NULL;
   if (!ecore_file_exists(path)) return NULL;

   o = e_icon_add(evas);
   e_icon_scale_size_set(o, size);
   e_icon_preload_set(o, 1);
   ext = strrchr(path, '.');
   if (ext)
     {
        if (!strcmp(ext, ".edj"))
          e_icon_file_edje_set(o, path, "icon");
        else
          e_icon_file_set(o, path);
     }
   else
     e_icon_file_set(o, path);
   e_icon_fill_inside_set(o, 1);

   return o;
}

static Eina_Bool
_e_util_wakeup_cb(void *data EINA_UNUSED)
{
   _e_util_dummy_timer = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_util_conf_timer_old(void *data)
{
   char *module_name = data;
   char buf[4096];
   char *msg = _("Configuration data needed upgrading. Your old configuration<ps/>"
                 "has been wiped and a new set of defaults initialized. This<ps/>"
                 "will happen regularly during development, so don't report a<ps/>"
                 "bug. This means the module needs new configuration<ps/>"
                 "data by default for usable functionality that your old<ps/>"
                 "configuration lacked. This new set of defaults will fix<ps/>"
                 "that by adding it in. You can re-configure things now to your<ps/>"
                 "liking. Sorry for the hiccup in your configuration.<ps/>");

   snprintf(buf, sizeof(buf), N_("%s Configuration Updated"), module_name);
   e_util_dialog_internal(buf, msg);
   E_FREE(module_name);

   return ECORE_CALLBACK_CANCEL;
}

static Eina_Bool
_e_util_conf_timer_new(void *data)
{
   char *module_name = data;
   char buf[4096];
   char *msg =
     _("Your module configuration is NEWER "
       "than the module version. This is "
       "very<ps/>strange. This should not happen unless"
       " you downgraded<ps/>the module or "
       "copied the configuration from a place where"
       "<ps/>a newer version of the module "
       "was running. This is bad and<ps/>as a "
       "precaution your configuration has been now "
       "restored to<ps/>defaults. Sorry for the "
       "inconvenience.<ps/>");

   snprintf(buf, sizeof(buf), _("%s Configuration Updated"), module_name);
   e_util_dialog_internal(buf, msg);
   E_FREE(module_name);

   return ECORE_CALLBACK_CANCEL;
}

E_API Eina_Bool
e_util_module_config_check(const char *module_name, int loaded, int current)
{
   if (loaded > current)
     {
        ecore_timer_loop_add(1.0, _e_util_conf_timer_new, strdup(module_name));
        return EINA_FALSE;
     }
   loaded -= loaded % 1000000, current -= current % 1000000;
   if (loaded < current)
     {
        ecore_timer_loop_add(1.0, _e_util_conf_timer_old, strdup(module_name));
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/**
 * Checks whenever the current manager/comp/zone have fullscreen windows.
 */
E_API Eina_Bool
e_util_fullscreen_current_any(void)
{
   E_Zone *zone = e_zone_current_get();
   E_Desk *desk;

   if ((zone) && (zone->fullscreen > 0)) return EINA_TRUE;
   desk = e_desk_current_get(zone);
   if ((desk) && (desk->fullscreen_clients)) return EINA_TRUE;
   return EINA_FALSE;
}

/**
 * Checks whenever any manager/comp/zone have fullscreen windows.
 */
E_API Eina_Bool
e_util_fullscreen_any(void)
{
   E_Zone *zone;
   const Eina_List *lz;
   E_Desk *desk;
   int x, y;

   EINA_LIST_FOREACH(e_comp->zones, lz, zone)
     {
        if (zone->fullscreen > 0) return EINA_TRUE;

        for (x = 0; x < zone->desk_x_count; x++)
          for (y = 0; y < zone->desk_y_count; y++)
            {
               desk = e_desk_at_xy_get(zone, x, y);
               if ((desk) && (desk->fullscreen_clients))
                 return EINA_TRUE;
            }
     }
   return EINA_FALSE;
}

E_API const char *
e_util_time_str_get(long int seconds)
{
   static char buf[1024];
   long int test;

   if (seconds < 0)
     snprintf(buf, sizeof(buf), _("Never"));
   else
     {
        if (seconds <= 60)
          snprintf(buf, sizeof(buf), P_("A second", "%li Seconds", seconds), seconds);
        else if (seconds >= 31526000)
          {
             test = seconds / 31526000;
             snprintf(buf, sizeof(buf), P_("One year", "%li Years", test), test);
          }
        else if (seconds >= 2592000)
          {
             test = seconds / 2592000;
             snprintf(buf, sizeof(buf), P_("One month", "%li Months", test), test);
          }
        else if (seconds >= 604800)
          {
             test = seconds / 604800;
             snprintf(buf, sizeof(buf), P_("One week", "%li Weeks", test), test);
          }
        else if (seconds >= 86400)
          {
             test = seconds / 86400;
             snprintf(buf, sizeof(buf), P_("One day", "%li Days", test), test);
          }
        else if (seconds >= 3600)
          {
             test = seconds / 3600;
             snprintf(buf, sizeof(buf), P_("An hour", "%li Hours", test), test);
          }
        else if (seconds > 60)
          {
             test = seconds / 60;
             snprintf(buf, sizeof(buf), P_("A minute", "%li Minutes", test), test);
          }
     }
   return buf;
}

static void
_e_util_size_debug_free(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int x, y, w, h;
   const char *name;

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   name = evas_object_name_get(obj);
   fprintf(stderr, "FREE %s %d OBJ[%s%s%p]: (%d,%d) - %dx%d\n", evas_object_visible_get(obj) ? "VIS" : "HID", evas_object_layer_get(obj), name ?: "", name ? "|" : "", obj, x, y, w, h);
}

static void
_e_util_size_debug_del(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int x, y, w, h;
   const char *name;

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   name = evas_object_name_get(obj);
   fprintf(stderr, "DEL %s %d OBJ[%s%s%p]: (%d,%d) - %dx%d\n", evas_object_visible_get(obj) ? "VIS" : "HID", evas_object_layer_get(obj), name ?: "", name ? "|" : "", obj, x, y, w, h);
}

static void
_e_util_size_debug_stack(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int x, y, w, h;
   const char *name;

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   name = evas_object_name_get(obj);
   fprintf(stderr, "RESTACK %s %d OBJ[%s%s%p]: (%d,%d) - %dx%d\n", evas_object_visible_get(obj) ? "VIS" : "HID", evas_object_layer_get(obj), name ?: "", name ? "|" : "", obj, x, y, w, h);
}

E_API void
e_util_size_debug(Evas_Object *obj)
{
   int x, y, w, h;
   const char *name;

   evas_object_geometry_get(obj, &x, &y, &w, &h);
   name = evas_object_name_get(obj);
   fprintf(stderr, "%s %d OBJ[%s%s%p]: (%d,%d) - %dx%d\n", evas_object_visible_get(obj) ? "VIS" : "HID", evas_object_layer_get(obj), name ?: evas_object_type_get(obj), "|", obj, x, y, w, h);
}

static void
_e_util_size_debug(void *data EINA_UNUSED, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_util_size_debug(obj);
}

E_API void
e_util_size_debug_set(Evas_Object *obj, Eina_Bool enable)
{
   if (enable)
     {
        evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_RESTACK,
                                       _e_util_size_debug_stack, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
                                       _e_util_size_debug_del, NULL);
        evas_object_event_callback_add(obj, EVAS_CALLBACK_FREE,
                                       _e_util_size_debug_free, NULL);
     }
   else
     {
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOVE,
                                            _e_util_size_debug, NULL);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_RESIZE,
                                            _e_util_size_debug, NULL);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_SHOW,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_HIDE,
                                       _e_util_size_debug, NULL);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_DEL,
                                       _e_util_size_debug_del, NULL);
        evas_object_event_callback_del_full(obj, EVAS_CALLBACK_FREE,
                                       _e_util_size_debug_free, NULL);
     }
}

E_API void
e_util_string_list_free(Eina_List *l)
{
   char *s;

   EINA_LIST_FREE(l, s)
     free(s);
}

static Efreet_Desktop *
_e_util_default_terminal_get(const char *defaults_list)
{
   Efreet_Desktop *tdesktop = NULL;
   Efreet_Ini *ini;
   const char *s;

   ini = efreet_ini_new(defaults_list);
   if ((ini) && (ini->data) &&
       (efreet_ini_section_set(ini, "Default Applications")) &&
       (ini->section))
     {
        s = efreet_ini_string_get(ini, "x-scheme-handler/terminal");
        if (s) tdesktop = efreet_util_desktop_file_id_find(s);
     }
   if (ini) efreet_ini_free(ini);
   return tdesktop;
}

E_API Efreet_Desktop *
e_util_terminal_desktop_get(void)
{
   const char *terms[] =
   {
      "terminology.desktop",
      "rxvt.desktop",
      "gnome-terminal.desktop",
      "konsole.desktop",
      NULL
   };
   const char *s;
   char buf[PATH_MAX];
   Efreet_Desktop *tdesktop = NULL, *td;
   Eina_List *l;
   int i;

   s = efreet_data_home_get();
   if (s)
     {
        snprintf(buf, sizeof(buf), "%s/mimeapps.list",
                 efreet_config_home_get());
        tdesktop = _e_util_default_terminal_get(buf);
     }
   if (tdesktop) return tdesktop;
   EINA_LIST_FOREACH(efreet_data_dirs_get(), l, s)
     {
        snprintf(buf, sizeof(buf), "%s/applications/defaults.list", s);
        tdesktop = _e_util_default_terminal_get(buf);
        if (tdesktop) return tdesktop;
     }

   for (i = 0; terms[i]; i++)
     {
        tdesktop = efreet_util_desktop_file_id_find(terms[i]);
        if (tdesktop) return tdesktop;
     }
   if (!tdesktop)
     {
        l = efreet_util_desktop_category_list("TerminalEmulator");
        if (l)
          {
             // just take first one since above list doesn't work.
             tdesktop = l->data;
             EINA_LIST_FREE(l, td)
               {
                  // free/unref the desktops we are not going to use
                  if (td != tdesktop) efreet_desktop_free(td);
               }
          }
     }
   return tdesktop;
}

E_API E_Config_Binding_Key *
e_util_binding_match(const Eina_List *bindlist, Ecore_Event_Key *ev, unsigned int *num, const E_Config_Binding_Key *skip)
{
   E_Config_Binding_Key *bi;
   const Eina_List *l;
   unsigned int mod = E_BINDING_MODIFIER_NONE;

   if (num) *num = 0;

   if (ev->modifiers & ECORE_EVENT_MODIFIER_SHIFT)
     mod |= E_BINDING_MODIFIER_SHIFT;
   if (ev->modifiers & ECORE_EVENT_MODIFIER_CTRL)
     mod |= E_BINDING_MODIFIER_CTRL;
   if (ev->modifiers & ECORE_EVENT_MODIFIER_ALT)
     mod |= E_BINDING_MODIFIER_ALT;
   if (ev->modifiers & ECORE_EVENT_MODIFIER_WIN)
     mod |= E_BINDING_MODIFIER_WIN;
   /* see comment in e_bindings on numlock
      if (ev->modifiers & ECORE_X_LOCK_NUM)
      mod |= ECORE_X_LOCK_NUM;
    */
   EINA_LIST_FOREACH(bindlist ? : e_bindings->key_bindings, l, bi)
     {
        if (bi != skip)
          {
             if ((bi->modifiers == mod) && eina_streq(bi->key, ev->key))
               return bi;
          }
        if (num) (*num)++;
     }
   if (num) *num = 0;
   return NULL;
}

E_API void
e_util_gadcon_orient_icon_set(E_Gadcon_Orient orient, Evas_Object *obj)
{
   switch (orient)
     {
      case E_GADCON_ORIENT_LEFT:
        e_util_icon_theme_set(obj, "preferences-position-left");
        break;

      case E_GADCON_ORIENT_RIGHT:
        e_util_icon_theme_set(obj, "preferences-position-right");
        break;

      case E_GADCON_ORIENT_TOP:
        e_util_icon_theme_set(obj, "preferences-position-top");
        break;

      case E_GADCON_ORIENT_BOTTOM:
        e_util_icon_theme_set(obj, "preferences-position-bottom");
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        e_util_icon_theme_set(obj, "preferences-position-top-left");
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        e_util_icon_theme_set(obj, "preferences-position-top-right");
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        e_util_icon_theme_set(obj, "preferences-position-bottom-left");
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        e_util_icon_theme_set(obj, "preferences-position-bottom-right");
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        e_util_icon_theme_set(obj, "preferences-position-left-top");
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        e_util_icon_theme_set(obj, "preferences-position-right-top");
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        e_util_icon_theme_set(obj, "preferences-position-left-bottom");
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        e_util_icon_theme_set(obj, "preferences-position-right-bottom");
        break;

      default:
        e_util_icon_theme_set(obj, "enlightenment");
        break;
     }
}

E_API void
e_util_gadcon_orient_menu_item_icon_set(E_Gadcon_Orient orient, E_Menu_Item *mi)
{
   switch (orient)
     {
      case E_GADCON_ORIENT_LEFT:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-left");
        break;

      case E_GADCON_ORIENT_RIGHT:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-right");
        break;

      case E_GADCON_ORIENT_TOP:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-top");
        break;

      case E_GADCON_ORIENT_BOTTOM:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom");
        break;

      case E_GADCON_ORIENT_CORNER_TL:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-top-left");
        break;

      case E_GADCON_ORIENT_CORNER_TR:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-top-right");
        break;

      case E_GADCON_ORIENT_CORNER_BL:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom-left");
        break;

      case E_GADCON_ORIENT_CORNER_BR:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-bottom-right");
        break;

      case E_GADCON_ORIENT_CORNER_LT:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-left-top");
        break;

      case E_GADCON_ORIENT_CORNER_RT:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-right-top");
        break;

      case E_GADCON_ORIENT_CORNER_LB:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-left-bottom");
        break;

      case E_GADCON_ORIENT_CORNER_RB:
        e_util_menu_item_theme_icon_set(mi, "preferences-position-right-bottom");
        break;

      default:
        e_util_menu_item_theme_icon_set(mi, "enlightenment");
        break;
     }
}

E_API char *
e_util_string_append_char(char *str, size_t *size, size_t *len, char c)
{
   if (!str)
     {
        str = malloc(4096);
        if (!str) return NULL;
        str[0] = 0;
        *size = 4096;
        *len = 0;
     }

   if (*len >= *size - 1)
     {
        char *str2;

        *size += 1024;
        str2 = realloc(str, *size);
        if (!str2)
          {
             *size = 0;
             free(str);
             return NULL;
          }
        str = str2;
     }

   str[(*len)++] = c;
   str[*len] = 0;

   return str;
}

E_API char *
e_util_string_append_quoted(char *str, size_t *size, size_t *len, const char *src)
{
   str = e_util_string_append_char(str, size, len, '\'');
   if (!str) return NULL;

   while (*src)
     {
        if (*src == '\'')
          {
             str = e_util_string_append_char(str, size, len, '\'');
             if (!str) return NULL;
             str = e_util_string_append_char(str, size, len, '\\');
             if (!str) return NULL;
             str = e_util_string_append_char(str, size, len, '\'');
             if (!str) return NULL;
             str = e_util_string_append_char(str, size, len, '\'');
             if (!str) return NULL;
          }
        else
          {
             str = e_util_string_append_char(str, size, len, *src);
             if (!str) return NULL;
          }

        src++;
     }

   str = e_util_string_append_char(str, size, len, '\'');
   if (!str) return NULL;

   return str;
}

E_API void
e_util_evas_objects_above_print(Evas_Object *o)
{
   Evas_Object *a, *oo;

   EINA_SAFETY_ON_NULL_RETURN(o);
   a = o;
   while ((a = evas_object_above_get(a)))
     {
        const Eina_List *l, *ll;

        l = evas_object_clipees_get(a);
        if (l)
          {
             fprintf(stderr, "[%p] - %s(%s) %s :: CLIPPEES: ", a, evas_object_type_get(a), evas_object_name_get(a), evas_object_visible_get(a) ? "VISIBLE" : "HIDDEN");
             EINA_LIST_FOREACH(l, ll, oo)
               fprintf(stderr, "[%p] - %s(%s) %s", oo, evas_object_type_get(oo), evas_object_name_get(oo), ll->next ? "| " : "");
          }
        else
          fprintf(stderr, "[%p] - %s(%s) %s\n", a, evas_object_type_get(a), evas_object_name_get(a), evas_object_visible_get(a) ? "VISIBLE" : "HIDDEN");
     }
}

E_API void
e_util_evas_objects_above_print_smart(Evas_Object *o)
{
   Evas_Object *a, *oo;

   EINA_SAFETY_ON_NULL_RETURN(o);
   a = o;
   while ((a = evas_object_above_get(a)))
     {
        const Eina_List *l, *ll;

        l = evas_object_clipees_get(a);
        if (l)
          {
             fprintf(stderr, "[%p] - %s(%s) %s :: CLIPPEES: ", a, evas_object_type_get(a), evas_object_name_get(a), evas_object_visible_get(a) ? "VISIBLE" : "HIDDEN");
             EINA_LIST_FOREACH(l, ll, oo)
               fprintf(stderr, "[%p] - %s(%s) %s", oo, evas_object_type_get(oo), evas_object_name_get(oo), ll->next ? "| " : "");
             fprintf(stderr, "\n");
          }
        else if (evas_object_smart_data_get(a))
          {
             fprintf(stderr, "[%p] - %s(%s) %s :: SMART MEMBERS: ", a, evas_object_type_get(a), evas_object_name_get(a), evas_object_visible_get(a) ? "VISIBLE" : "HIDDEN");
             EINA_LIST_FOREACH(evas_object_smart_members_get(a), l, oo)
               fprintf(stderr, "[%p] - %s(%s) %s", oo, evas_object_type_get(oo), evas_object_name_get(oo), l->next ? "| " : "");
             fprintf(stderr, "\n");
          }
        else
          fprintf(stderr, "[%p] - %s(%s) %s\n", a, evas_object_type_get(a), evas_object_name_get(a), evas_object_visible_get(a) ? "VISIBLE" : "HIDDEN");
     }
}

#if   defined(HAVE_MEMSET_S)
#elif defined(HAVE_EXPLICIT_BZERO)
#elif defined(HAVE_EXPLICIT_MEMSET)
#else
void *(* const volatile __memset_ptr)(void *, int, size_t) = memset;
#endif

E_API void
e_util_memclear(void *s, size_t n)
{
   if (n == 0) return;
#if   defined(HAVE_MEMSET_S)
   memset_s(s, n, 0, n);
#elif defined(HAVE_EXPLICIT_BZERO)
   explicit_bzero(s, n);
#elif defined(HAVE_EXPLICIT_MEMSET)
   explicit_memset(s, 0, n);
#else
   __memset_ptr(s, 0, n);
   __asm__ __volatile__("": :"r"(s) : "memory");
#endif
}

E_API Ecore_Exe *
e_util_open(const char *exe, void *data)
{
   char *sb;
   size_t size = 65536, len;
   Ecore_Exe *ret;

   sb = malloc(size);
   snprintf(sb, size, "%s/enlightenment_open ", e_prefix_bin_get());
   len = strlen(sb);
   sb = e_util_string_append_quoted(sb, &size, &len, exe);
   ret = e_util_exe_safe_run(sb, data);
   free(sb);
   return ret;
}

E_API Ecore_Exe *
e_util_exe_safe_run(const char *cmd, void *data)
{
   Ecore_Exe_Flags flags = ECORE_EXE_NONE;

#if (ECORE_VERSION_MAJOR >= 1) && (ECORE_VERSION_MINOR >= 21)
   flags |= ECORE_EXE_ISOLATE_IO;
#else
   flags |= 1024; // isolate_io is bit 10 .... it will be ignored if
                  // efl doesn't do it, so harmless
#endif
   return ecore_exe_pipe_run(cmd, flags, data);
}

static Eina_Bool
str_matches_one(const char *instr, const char **strset)
{
   int i;

   if ((!instr) || (!strset)) return EINA_FALSE;
   for (i = 0; strset[i]; i++)
     {
        if (!strcmp(strset[i], instr)) return EINA_TRUE;
     }
   return EINA_FALSE;
}

E_API E_Util_Action
e_util_key_geometry_action_get(const char *key, int *x, int *y, int dx, int dy)
{
   const char *k_up[]    = { "Up",     "KP_Up",     "k", "K", "w", "W", NULL };
   const char *k_down[]  = { "Down",   "KP_Down",   "j", "J", "s", "S", NULL };
   const char *k_left[]  = { "Left",   "KP_Left",   "h", "H", "a", "A", NULL };
   const char *k_right[] = { "Right",  "KP_Right",  "l", "L", "d", "D", NULL };
   const char *k_done[]  = { "Return", "KP_Enter",  "space",  NULL };
   const char *k_abort[] = { "Escape", "BackSpace", "Delete", "x", "X", NULL };

   if      (str_matches_one(key, k_up))    *y -= dy;
   else if (str_matches_one(key, k_down))  *y += dy;
   else if (str_matches_one(key, k_left))  *x -= dx;
   else if (str_matches_one(key, k_right)) *x += dx;
   else if (str_matches_one(key, k_done))  return E_UTIL_ACTION_DONE;
   else if (str_matches_one(key, k_abort)) return E_UTIL_ACTION_ABORT;
   else return E_UTIL_ACTION_NONE;

   return E_UTIL_ACTION_DO;
}

E_API const char *
e_username_get(void)
{
   struct passwd *pw = getpwuid(getuid());

   if (pw) return pw->pw_name;
   return "";
}

typedef struct
{
   int        pid;
   int        pri_set, pri_adj;
   int        pri_only_filter;
   Eina_Bool  do_adj : 1;
   Eina_Bool  do_adj_children : 1;
   Eina_Bool  do_children : 1;
} E_Pri_Set_Adj_Data;

static void
_pri_adj(int pid, int pri_set, int pri_adj, int pri_only_filter,
         Eina_Bool do_adj, Eina_Bool do_adj_children, Eina_Bool do_children)
{
   Eina_List *files;
   char *file, buf[PATH_MAX];
   int pid2, ppid, newpri, ret;
   FILE *f;

   newpri = getpriority(PRIO_PROCESS, pid) + pri_adj;
   if ((pri_only_filter == -99) || (pri_only_filter != newpri))
     {
        if (do_adj) newpri += pri_adj;
        else        newpri = pri_set;
        if (newpri > 19) newpri = 19;
//        printf("PRI: %i -> %i (adj=%i, adj_ch=%i ch=%i)\n", pid, newpri, do_adj, do_adj_children, do_children);
        ret = setpriority(PRIO_PROCESS, pid, newpri);
//        if (ret < 0) printf("PRI: ret = %i | %s\n", ret, strerror(errno));
     }
// shouldn't need to do this as default ionice class is "none" (0), and
// this inherits io priority FROM nice level
//        ioprio_set(IOPRIO_WHO_PROCESS, pid,
//                   IOPRIO_PRIO_VALUE(2, 5));
   if (do_children)
     {
// yes - this is /proc specific... so this may not work on some
// os's - works on linux. too bad for others.
        files = ecore_file_ls("/proc");
        EINA_LIST_FREE(files, file)
          {
             if (isdigit(file[0]))
               {
                  snprintf(buf, sizeof(buf), "/proc/%s/stat", file);
                  f = fopen(buf, "r");
                  if (f)
                    {
                       pid2 = ppid = -1;
                       ret = fscanf(f, "%i %*s %*s %i %*s", &pid2, &ppid);
                       fclose(f);
                       if (ret == 2)
                         {
                            if (ppid == pid)
                              {
                                 if (do_adj_children)
                                   _pri_adj(pid2, pri_set, pri_adj,
                                            pri_only_filter,
                                            EINA_TRUE,
                                            do_adj_children,
                                            do_children);
                                 else
                                   _pri_adj(pid2, pri_set, pri_adj,
                                            pri_only_filter,
                                            do_adj,
                                            do_adj_children,
                                            do_children);
                              }
                         }
                    }
               }
             free(file);
          }
     }
}

static void
_e_pid_nice_priority_set_adjust_thread(void *data,
                                       Ecore_Thread *eth EINA_UNUSED)
{
   E_Pri_Set_Adj_Data *dat = data;

//   printf("PRI: --------\n");
   _pri_adj(dat->pid, dat->pri_set, dat->pri_adj, dat->pri_only_filter,
            dat->do_adj, dat->do_adj_children, dat->do_children);
   free(dat);
}

static void
_e_pid_nice_priority_set_adjust(int pid, int pri_set, int pri_adj,
                                int pri_only_filter,
                                Eina_Bool do_adj, Eina_Bool do_adj_children,
                                Eina_Bool do_children)
{
   E_Pri_Set_Adj_Data *dat = calloc(1, sizeof(E_Pri_Set_Adj_Data));

   if (!dat) return;
   dat->pid             = pid;
   dat->pri_set         = pri_set;
   dat->pri_adj         = pri_adj;
   dat->pri_only_filter = pri_only_filter;
   dat->do_adj          = do_adj;
   dat->do_adj_children = do_adj_children;
   dat->do_children     = do_children;
   ecore_thread_run(_e_pid_nice_priority_set_adjust_thread, NULL, NULL, dat);
}

static Eina_Bool
_e_pid_nice_priority_lower_can(void)
{
#ifdef RLIMIT_NICE
   static int checked = -1;
   struct rlimit rlim;

again:
   if      (checked == 0) return EINA_FALSE;
   else if (checked == 1) return EINA_TRUE;

   checked = 1;
   if (getrlimit(RLIMIT_NICE, &rlim) == 0)
     {
        // if we can't lower pri to at least 20 (nice 0 ...) then assume
        // we can only raise nice level never lower it
        if (rlim.rlim_cur < 20) checked = 0;
     }
   goto again; // set checked now - try again
#endif
   return EINA_TRUE;
}

E_API void
e_pid_nice_priority_fg(int pid)
{
   int pri = e_config->priority - 1;

   if (!_e_pid_nice_priority_lower_can()) pri = e_config->priority;
   _e_pid_nice_priority_set_adjust(pid, pri, -1, -99, // only these procs
                                   EINA_FALSE, EINA_FALSE, EINA_FALSE);
}

E_API void
e_pid_nice_priority_bg(int pid)
{
   int pri = e_config->priority;

   if (!_e_pid_nice_priority_lower_can()) pri = e_config->priority;
   _e_pid_nice_priority_set_adjust(pid, pri, 1, -99, // only these procs
                                   EINA_FALSE, EINA_FALSE, EINA_FALSE);
}
#include "e.h"

typedef struct _Video Video;

struct _Video
{
   Evas_Object_Smart_Clipped_Data __clipped_data;
   Evas_Object *clip, *o_vid;
   Eina_Bool lowqual E_BITFIELD;
};

typedef struct _Vidimg Vidimg;

struct _Vidimg
{
   Evas_Object *obj;
   Eina_List *copies;
   int iw, ih, piw, pih;
   Ecore_Job *restart_job;
   Ecore_Event_Handler *offhandler;
   Ecore_Event_Handler *onhandler;
   Ecore_Timer *offtimer;
};

static Evas_Smart *_smart = NULL;
static Evas_Smart_Class _parent_sc = EVAS_SMART_CLASS_INIT_NULL;

static Eina_List *vidimgs = NULL;

static void _ob_resize(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h);

static void
_cb_vid_frame(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   Vidimg *vi = data;
   Eina_List *l;
   Evas_Object *o, *oimg;

   EINA_LIST_FOREACH(vi->copies, l, o)
     {
        Evas_Coord ox, oy, ow, oh;
        Video *sd;

        obj = evas_object_data_get(o, "obj");
        oimg = emotion_object_image_get(vi->obj);
        evas_object_image_source_set(o, oimg);
        evas_object_image_source_clip_set(o, EINA_FALSE);
        evas_object_image_source_visible_set(o, EINA_FALSE);
        sd = evas_object_smart_data_get(obj);
        evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
        evas_object_show(sd->o_vid);
        evas_object_show(sd->clip);
        _ob_resize(obj, ox, oy, ow, oh);
     }
}

static void
_cb_vid_resize(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   Vidimg *vi = data;
   Eina_List *l;
   Evas_Object *o;

   EINA_LIST_FOREACH(vi->copies, l, o)
     {
        Evas_Coord ox, oy, ow, oh;

        obj = evas_object_data_get(o, "obj");
        evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
        _ob_resize(obj, ox, oy, ow, oh);
     }
}

static void
_cb_restart(void *data)
{
   Vidimg *vi = data;

   vi->restart_job = NULL;
   emotion_object_position_set(vi->obj, 0.0);
   emotion_object_play_set(vi->obj, EINA_TRUE);
}

static void
_cb_vid_stop(void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   Vidimg *vi = data;

   if (vi->restart_job) ecore_job_del(vi->restart_job);
   vi->restart_job = ecore_job_add(_cb_restart, vi);
}

static Eina_Bool
vidimg_cb_suspend(void *data)
{
   Vidimg *vi = data;

   vi->offtimer = NULL;
   emotion_object_play_set(vi->obj, EINA_FALSE);
   return EINA_FALSE;
}

static Eina_Bool
vidimg_cb_screensaver_off(void *data, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Vidimg *vi = data;

   if (vi->offtimer)
     {
        ecore_timer_del(vi->offtimer);
        vi->offtimer = NULL;
     }
   else
     {
        emotion_object_play_set(vi->obj, EINA_TRUE);
     }
   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
vidimg_cb_screensaver_on(void *data, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Vidimg *vi = data;

   if (vi->offtimer) ecore_timer_del(vi->offtimer);
   vi->offtimer = ecore_timer_loop_add(10.0, vidimg_cb_suspend, vi);
   return ECORE_CALLBACK_PASS_ON;
}

static Evas_Object *
vidimg_find(Evas_Object *obj, const char *file)
{
   Eina_List *l;
   Evas_Object *o, *oimg, *oproxy;

   EINA_LIST_FOREACH(vidimgs, l, o)
     {
        if (evas_object_evas_get(o) == evas_object_evas_get(obj))
          {
             const char *f = emotion_object_file_get(o);

             if ((f) && (file) && (!strcmp(f, file)))
               {
                  Vidimg *vi = evas_object_data_get(o, "vidimg");
                  oproxy = evas_object_image_filled_add(evas_object_evas_get(obj));
                  evas_object_data_set(oproxy, "source", o);
                  oimg = emotion_object_image_get(o);
                  evas_object_image_source_set(oproxy, oimg);
                  evas_object_image_source_clip_set(oproxy, EINA_FALSE);
                  evas_object_image_source_visible_set(oproxy, EINA_FALSE);
                  vi->copies = eina_list_append(vi->copies, oproxy);
                  evas_object_data_set(oproxy, "obj", obj);
                  return oproxy;
               }
          }
     }
   return NULL;
}

static Evas_Object *
vidimg_video_add(Evas_Object *obj, const char *file)
{
   Evas_Object *o;
   Vidimg *vi;

   o = emotion_object_add(evas_object_evas_get(obj));
   if (!emotion_object_init(o, "gstreamer1"))
     {
        evas_object_del(o);
        return NULL;
     }
   vi = calloc(1, sizeof(Vidimg));
   vi->obj = o;
   vi->offhandler = ecore_event_handler_add(E_EVENT_SCREENSAVER_OFF, vidimg_cb_screensaver_off, vi);
   vi->onhandler = ecore_event_handler_add(E_EVENT_SCREENSAVER_ON, vidimg_cb_screensaver_on, vi);
   evas_object_data_set(o, "vidimg", vi);
   evas_object_smart_callback_add(o, "frame_decode", _cb_vid_frame, vi);
   evas_object_smart_callback_add(o, "frame_resize", _cb_vid_resize, vi);
   evas_object_smart_callback_add(o, "decode_stop", _cb_vid_stop, vi);
   emotion_object_file_set(o, file);
   emotion_object_audio_mute_set(o, EINA_TRUE);
   emotion_object_keep_aspect_set(o, EMOTION_ASPECT_KEEP_NONE);
   emotion_object_play_set(o, EINA_TRUE);
   evas_object_pass_events_set(o, EINA_TRUE);
   evas_object_move(o, -999, -999);
   evas_object_resize(o, 9, 0);
   evas_object_show(o);
   vidimgs = eina_list_append(vidimgs, o);
   return o;
}

static void
vidimg_release(Evas_Object *o)
{
   Vidimg *vi;
   Evas_Object *src = evas_object_data_get(o, "source");
   if (!src) return;
   vi = evas_object_data_get(src, "vidimg");
   vi->copies = eina_list_remove(vi->copies, o);
   if (!vi->copies)
     {
        vidimgs = eina_list_remove(vidimgs, src);
        evas_object_data_del(src, "vidimg");
        if (vi->restart_job) ecore_job_del(vi->restart_job);
        ecore_event_handler_del(vi->offhandler);
        ecore_event_handler_del(vi->onhandler);
        if (vi->offtimer) ecore_timer_del(vi->offtimer);
        free(vi);
        evas_object_del(src);
     }
}





static void
_ob_resize(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Video *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_move(sd->o_vid, x, y);
   evas_object_resize(sd->o_vid, w, h);
}

static void _smart_calculate(Evas_Object *obj);

static void
_smart_add(Evas_Object *obj)
{
   Video *sd;
   Evas_Object *o;

   sd = calloc(1, sizeof(Video));
   EINA_SAFETY_ON_NULL_RETURN(sd);
   evas_object_smart_data_set(obj, sd);

   _parent_sc.add(obj);

   o = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   sd->clip = o;
   evas_object_color_set(o, 255, 255, 255, 255);
}

static void
_smart_del(Evas_Object *obj)
{
   Video *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (sd->clip) evas_object_del(sd->clip);

   vidimg_release(sd->o_vid);

   _parent_sc.del(obj);
}

static void
_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   Video *sd = evas_object_smart_data_get(obj);
   Evas_Coord ox, oy, ow, oh;
   if (!sd) return;
   evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
   if ((ow == w) && (oh == h)) return;
   evas_object_smart_changed(obj);
   evas_object_resize(sd->clip, ow, oh);
}

static void
_smart_calculate(Evas_Object *obj)
{
   Video *sd = evas_object_smart_data_get(obj);
   Evas_Coord ox, oy, ow, oh;

   if (!sd) return;
   evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
   _ob_resize(obj, ox, oy, ow, oh);
   evas_object_move(sd->clip, ox, oy);
   evas_object_resize(sd->clip, ow, oh);
}

static void
_smart_move(Evas_Object *obj, Evas_Coord x EINA_UNUSED, Evas_Coord y EINA_UNUSED)
{
   Video *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_smart_changed(obj);
}

static void
_smart_init(void)
{
   static Evas_Smart_Class sc;

   evas_object_smart_clipped_smart_set(&_parent_sc);
   sc           = _parent_sc;
   sc.name      = "video";
   sc.version   = EVAS_SMART_CLASS_VERSION;
   sc.add       = _smart_add;
   sc.del       = _smart_del;
   sc.resize    = _smart_resize;
   sc.move      = _smart_move;
   sc.calculate = _smart_calculate;
   _smart = evas_smart_class_new(&sc);
}

E_API Evas_Object *
e_video_add(Evas_Object *parent, const char *file, Eina_Bool lowq)
{
   Evas *e;
   Evas_Object *obj, *o;
   Video *sd;

   EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
   e = evas_object_evas_get(parent);
   if (!e) return NULL;

   if (!_smart) _smart_init();
   obj = evas_object_smart_add(e, _smart);
   sd = evas_object_smart_data_get(obj);
   if (!sd) return obj;

   sd->lowqual = lowq;

   o = sd->o_vid = vidimg_find(obj, file);
   if (!o)
     {
        o = vidimg_video_add(obj, file);
        if (!o) return obj;
        o = sd->o_vid = vidimg_find(obj, file);
        if (!o) return obj;
     }
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_image_smooth_scale_set(o, !sd->lowqual);
   return obj;
}

E_API const char *
e_video_file_get(Evas_Object *obj)
{
   Video *sd;
   Evas_Object *src;

   if (evas_object_smart_smart_get(obj) != _smart) return NULL;
   if (!(sd = evas_object_smart_data_get(obj))) return NULL;
   src = evas_object_data_get(sd->o_vid, "source");
   if (src) return emotion_object_file_get(src);
   return NULL;
}
#include <Elementary.h>
#include "config.h"

// a set of commonly found resolution buckets to try size the image down
// to to match width OR height so we can avoid decoding a larger image from
// disk making startup time faster by picking the right res that's encoded
// already in the file... the more resolutions we encode the bigger
// the file so it's a tradeoff, thus commenting out a lot of the resolutions
// because they are so close or can just miildly crop another res...
// only encode resolutiosn less than the original image a well as throw in
// the original as the highest res option
// pick onne of each y resolution and scale accordingly so e.g. Nx480, Nx600,
// Nx720, Nx788, Nx800, Nx900, ...
static const int resolutions[] =
{
   // not bothering below 640x480
//    640,  480,
//    800,  480,
    854,  480,
//    768,  576,
//   1024,  576,
//    800,  600,
   1024,  600,
   1280,  720,
//   1024,  768,
//   1152,  768
//   1280,  768
   1366,  768,
   1280,  800,
//   1280,  854,
//   1152,  864,
//   1152,  900,
//   1440,  900,
   1600,  900,
//   1280,  960,
   1440,  960,
//   1280, 1024,
//   1400, 1050,
//   1680, 1050,
//   1440, 1080,
//   1920, 1080,
//   2048, 1080,
//   2880, 1080,
   3840, 1080,
//   1600, 1200,
   1920, 1200,
//   1920, 1280,
   2560, 1440,
//   3840, 1440,
//   5120, 1440,
//   2048, 1536,
   2560, 1600,
//   2880, 1620,
//   2880, 1800,
   3200, 1800,
//   2560, 2048,
//   3840, 2160,
   4096, 2160,
//   5120, 2160,
   5120, 2880,
   6016, 3384,
//   7680, 4320,
   8192, 4320,

// one day... we may have to add more resolutions here. when that day comes...
// we'll do that. until then... I think 8k is "good enough"
   0, 0 // sentinel
};
// when we have more res's above bump this to be that + 1 at a minimum
#define MAX_RES_NUM 20

#define ETC1 -1
#define ETC2 -2

static Evas_Object *win = NULL, *subwin = NULL, *image = NULL, *rend = NULL;

typedef struct
{
   int from_w, from_h, to_w, to_h;
   Eina_Bool last : 1;
} Mip;

static Mip *
_resolutions_calc(int w, int h)
{
   int i, j = 0;
   int nw, nh, pw = 0, ph = 0;
   Mip *mips = calloc(1, sizeof(Mip) * MAX_RES_NUM);

   if (!mips) return NULL;
   for (i = 0; resolutions[i]; i += 2)
     {
        nh = resolutions[i + 1];
        nw = (w * nh) / h;
        if ((nh >= h) || (nw >= w))
          {
             mips[j].from_w = pw;
             mips[j].from_h = ph;
             mips[j].to_w = 1000000000;
             mips[j].to_h = 1000000000;
             mips[j].last = EINA_TRUE;
             break;
          }

        mips[j].from_w = pw;
        mips[j].from_h = ph;
        mips[j].to_w = nw;
        mips[j].to_h = nh;

        j++;
        pw = nw + 1;
        ph = nh + 1;
     }
   return mips;
}

EAPI int
elm_main(int argc, char **argv)
{
   const char *edje_cc, *mode, *file, *outfile;
   int bg_r = 64;
   int bg_g = 64;
   int bg_b = 64;
   char dir_buf[128], img_buf[256], edc_buf[256], cmd_buf[1024], qual_buf[64];
   const char *dir, *quality_string = NULL, *img_name = NULL;
   Mip *mips = NULL;
   int i, imw, imh, w, h, quality;
   int ret = 0, mips_num = 0;
   Eina_Bool alpha;
   FILE *f;

   if (argc <= 1)
     {
        printf("USAGE: enlightenment_wallpaper_gen EDJE FILL INPUT OUT QUALITY [R G B]\n"
               "\n"
               "  EDJE    is edje_cc command to use (full path or in $PATH)\n"
               "  FILL    is one of:\n"
               "    stretch\n"
               "    tile\n"
               "    center (requires R G B)\n"
               "    scale_in (requires R G B)\n"
               "    scale_out\n"
               "    pan\n"
               "  INPUT   is some image file to put into the wallpaper edj\n"
               "  OUT     is the paht/location of the wallpaper edj output\n"
               "  QUALITY is:\n"
               "    0-100 (0-99 is lossy, 100 is lossless compression)\n"
               "    etc1\n"
               "    etc2\n"
               "  R, G, B are Red, Green and Blue values 0 to 255\n"
               "\n"
               "  e.g.\n"
               "    enlightenment_wallpaper_gen edje_cc scale_out cat.jpg wallpaper.edj 80\n"
               "    enlightenment_wallpaper_gen edje_cc center mylogo.png wallpaper.edj 100 48 64 64\n"
               "    enlightenment_wallpaper_gen /usr/local/bin/edje_cc tile pattern.jpg wallpaper.edj 98\n"
               "    enlightenment_wallpaper_gen /opt/e/bin/edje_cc scale_out hugeimg.jpg wallpaper.edj etc1\n"
               );
        return 1;
     }

   elm_config_preferred_engine_set("buffer");
   win = elm_win_add(NULL, "Wallpaper-Gen", ELM_WIN_BASIC);
   elm_win_norender_push(win);
   evas_object_show(win);

   subwin = elm_win_add(win, "inlined", ELM_WIN_INLINED_IMAGE);
   rend = elm_win_inlined_image_object_get(subwin);
   elm_win_norender_push(subwin);
   evas_object_show(subwin);

   if (argc < 6) return 2;
   edje_cc = argv[1];
   mode = argv[2];
   file = argv[3];
   outfile = argv[4];
   if      (!strcmp(argv[5], "etc1")) quality = ETC1;
   else if (!strcmp(argv[5], "etc2")) quality = ETC2;
   else
     {
        quality = atoi(argv[5]);
        if      (quality < 0)   quality = 0;
        else if (quality > 100) quality = 100;
     }

   image = evas_object_image_filled_add(evas_object_evas_get(subwin));
   evas_object_image_file_set(image, file, NULL);
   evas_object_image_size_get(image, &w, &h);
   if ((w <= 0) || (h <= 0)) return 3;
   alpha = evas_object_image_alpha_get(image);
   elm_win_alpha_set(subwin, alpha);
   evas_object_show(image);

   snprintf(dir_buf, sizeof(dir_buf), "/tmp/e_bg-XXXXXX");
   dir = mkdtemp(dir_buf);
   if (!dir) return 4;

   if ((!strcmp(mode, "center")) ||
       (!strcmp(mode, "scale_in")))
     { // need backing rgb color here
        if (argc < 9) return 5;
        bg_r = atoi(argv[6]);
        bg_g = atoi(argv[7]);
        bg_b = atoi(argv[8]);
     }
   if ((!strcmp(mode, "stretch")) ||
       (!strcmp(mode, "scale_in")) ||
       (!strcmp(mode, "scale_out")) ||
       (!strcmp(mode, "pan")))
     { // need to produce multiple scaled versions
        mips = _resolutions_calc(w, h);
        if (!mips) return 6;
        for (i = 0; mips[i].to_w; i++)
          {
             mips_num++;
             if (mips[i].last)
               {
                  imw = w;
                  imh = h;
               }
             else
               {
                  imw = mips[i].to_w;
                  imh = mips[i].to_h;
               }
             evas_object_resize(subwin, imw, imh);
             evas_object_resize(image, imw, imh);
             elm_win_render(subwin);
             if (mips[i].last)
               snprintf(img_buf, sizeof(img_buf), "%s/img.png",
                        dir);
             else
               snprintf(img_buf, sizeof(img_buf), "%s/img-%ix%i.png",
                        dir, imw, imh);
             if (!evas_object_image_save(rend, img_buf, NULL, "compress=0"))
               {
                  ret = 7;
                  goto cleanup;
               }
          }
     }
   // no multiple resolutions -0 save out original
   if (!mips)
     {
        evas_object_resize(subwin, w, h);
        evas_object_resize(image, w, h);
        elm_win_render(subwin);
        snprintf(img_buf, sizeof(img_buf), "%s/img.png", dir);
        if (!evas_object_image_save(rend, img_buf, NULL, "compress=0"))
          {
             ret = 8;
             goto cleanup;
          }
     }
   if ((quality == ETC1) && (alpha)) quality = ETC2; // etc1 -> etc2 if alpha
   if (quality == 100) quality_string = "COMP";
   else
     {
        if      (quality == ETC1) quality_string = "LOSSY_ETC1";
        else if (quality == ETC2) quality_string = "LOSSY_ETC2";
        else
          {
             snprintf(qual_buf, sizeof(qual_buf), "LOSSY %i", quality);
             quality_string = qual_buf;
          }
     }
   // generate edc
   snprintf(edc_buf, sizeof(edc_buf), "%s/bg.edc", dir);
   f = fopen(edc_buf, "w");
   if (!f) goto cleanup;
   if ((mips) && (mips_num > 1))
     {
        fprintf(f,
                "images {\n"
                " set { name: \"img\";\n");
        for (i = mips_num - 1; i >= 0; i--)
          {
             fprintf(f,
                     "  image {\n");
             if (mips[i].last)
               {
                  fprintf(f,
                          "   image: \"img.png\" %s;\n",
                          quality_string);
               }
             else
               {
                  imw = mips[i].to_w;
                  imh = mips[i].to_h;
                  fprintf(f,
                          "   image: \"img-%ix%i.png\" %s;\n",
                          imw, imh, quality_string);
               }
             fprintf(f,
                     "   size: %i %i %i %i;\n"
                     "  }\n",
                     mips[i].from_w, mips[i].from_h,
                     mips[i].to_w, mips[i].to_h);
          }
        fprintf(f,
                " }\n"
                "}\n");
        img_name = "img";
     }
   else
     {
        fprintf(f, "images.image: \"img.png\" %s;\n", quality_string);
        img_name = "img.png";
     }
   fprintf(f,
           "collections {\n"
           " group { name: \"e/desktop/background\";\n"
           "  data.item: \"noanimation\" \"1\";\n");
   if (!strcmp(mode, "stretch"))
     {
        fprintf(f, "  data { item: \"style\" \"0\"; }\n");
        fprintf(f,
                "  parts {\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "      scale_hint: STATIC;\n"
                "     }\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , img_name);
     }
   else if (!strcmp(mode, "tile"))
     {
        fprintf(f, "  data { item: \"style\" \"1\"; }\n");
        fprintf(f,
                "  parts {\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "     }\n"
                "     fill.size.relative: 0 0;\n"
                "     fill.size.offset: %i %i;\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , img_name, w, h);
     }
   else if (!strcmp(mode, "center"))
     {
        fprintf(f, "  data { item: \"style\" \"2\"; }\n");
        fprintf(f,
                "  parts {\n"
                "   part { name: \"col\"; type: RECT; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     color: %i %i %i 255;\n"
                "    }\n"
                "   }\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "     }\n"
                "     min: %i %i; max: %i %i;\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , bg_r, bg_g, bg_b, img_name, w, h, w, h);
     }
   else if (!strcmp(mode, "scale_in"))
     {
        fprintf(f, "  data { item: \"style\" \"3\"; }\n");
        fprintf(f,
                "  parts {\n"
                "   part { name: \"col\"; type: RECT; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     color: %i %i %i 255;\n"
                "    }\n"
                "   }\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "      scale_hint: STATIC;\n"
                "     }\n"
                "     aspect: (%i/%i) (%i/%i); aspect_preference: BOTH;\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , bg_r, bg_g, bg_b, img_name, w, h, w, h);
     }
   else if (!strcmp(mode, "scale_out"))
     {
        fprintf(f, "  data { item: \"style\" \"4\"; }\n");
        fprintf(f,
                "  parts {\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "      scale_hint: STATIC;\n"
                "     }\n"
                "     aspect: (%i/%i) (%i/%i); aspect_preference: NONE;\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , img_name, w, h, w, h);
     }
   else if (!strcmp(mode, "pan"))
     {
        fprintf(f, "  data { item: \"style\" \"5\"; }\n");
        fprintf(f,
                "  script {\n"
                "   public cur_anim; public cur_x; public cur_y;\n"
                "   public prev_x; public prev_y;\n"
                "   public total_x; public total_y;\n"
                "   public pan_bg(val, Float:v) {\n"
                "    new Float:x, Float:y, Float:px, Float: py;\n"
                "    px = get_float(prev_x);\n"
                "    py = get_float(prev_y);\n"
                "    if (get_int(total_x) > 1) {\n"
                "     x = float(get_int(cur_x)) / (get_int(total_x) - 1);\n"
                "     x = px - (px - x) * v;\n"
                "    } else {\n"
                "     x = 0.0;\n"
                "     v = 1.0;\n"
                "    }\n"
                "    if (get_int(total_y) > 1) {\n"
                "     y = float(get_int(cur_y)) / (get_int(total_y) - 1);\n"
                "     y = py - (py - y) * v;\n"
                "    } else {\n"
                "     y = 0.0;\n"
                "     v = 1.0; }\n"
                "    set_state_val(PART:\"bg\", STATE_ALIGNMENT, x, y);\n"
                "    if (v >= 1.0) {\n"
                "     set_int(cur_anim, 0);\n"
                "     set_float(prev_x, x);\n"
                "     set_float(prev_y, y);\n"
                "     return 0;\n"
                "    }\n"
                "    return 1;\n"
                "   }\n"
                "   public message(Msg_Type:type, id, ...) {\n"
                "    if ((type == MSG_FLOAT_SET) && (id == 0)) {\n"
                "     new ani;\n"
                "     get_state_val(PART:\"bg\", STATE_ALIGNMENT, prev_x, prev_y);\n"
                "     set_int(cur_x, round(getfarg(3)));\n"
                "     set_int(total_x, round(getfarg(4)));\n"
                "     set_int(cur_y, round(getfarg(5)));\n"
                "     set_int(total_y, round(getfarg(6)));\n"
                "     ani = get_int(cur_anim);\n"
                "     if (ani > 0) cancel_anim(ani);\n"
                "     ani = anim(getfarg(2), \"pan_bg\", 0);\n"
                "     set_int(cur_anim, ani);\n"
                "    }\n"
                "   }\n"
                "  }\n"
                "  parts {\n"
                "   part { name: \"bg\"; mouse_events: 0;\n"
                "    description { state: \"default\" 0.0;\n"
                "     image {\n"
                "      normal: \"%s\";\n"
                "      scale_hint: STATIC;\n"
                "     }\n"
                "     aspect: (%i/%i) (%i/%i); aspect_preference: NONE;\n"
                "    }\n"
                "   }\n"
                "   program {\n"
                "    signal: \"load\"; source: \"\";\n"
                "    script {\n"
                "     custom_state(PART:\"bg\", \"default\", 0.0);\n"
                "     set_state(PART:\"bg\", \"custom\", 0.0);\n"
                "     set_float(prev_x, 0.0);\n"
                "     set_float(prev_y, 0.0);\n"
                "    }\n"
                "   }\n"
                "  }\n"
                , img_name, w, h, w, h);
     }
   fprintf(f,
           " }\n"
           "}\n");
   fclose(f);
   if (snprintf(cmd_buf, sizeof(cmd_buf),
                "%s -fastdecomp -threads -id %s -fd %s -sd %s -vd %s -dd %s -md %s "
                "%s/bg.edc %s",
                edje_cc, dir, dir, dir, dir, dir, dir,
                dir, outfile) >= (int)sizeof(cmd_buf))
     {
        ret = 9;
        goto cleanup;
     }
   ret = system(cmd_buf);
cleanup:
   free(mips);
   ecore_file_recursive_rm(dir);
   evas_object_del(win);
   return ret;
}
ELM_MAIN()
#include "e.h"

static Ecore_Thread *_watchdog_thread = NULL;
static Ecore_Pipe *_watchdog_pipe = NULL;
static unsigned long last_seq = 0;

static void
_cb_watchdog_thread_pingpong_pipe(void *data EINA_UNUSED, void *buf, unsigned int bytes)
{
   unsigned long long *seq = buf;
   unsigned long long seq_num = bytes / sizeof(int);

   if (seq_num < 1)
     {
        return;
     }
   last_seq = seq[seq_num - 1];
}

static void
_cb_watchdog_thread_pingpong(void *data EINA_UNUSED, Ecore_Thread *thread)
{
   unsigned long long *seq_new;
   unsigned long long seq = 0;

   while (!ecore_thread_check(thread))
     {
        // send ping
        seq_new = malloc(sizeof(unsigned long));
        if (seq_new)
          {
             seq++;
             *seq_new = seq;
             ecore_thread_feedback(thread, seq_new);
             // wait for ping from mainloop upto 10 sec
             if (ecore_pipe_wait(_watchdog_pipe, 1, 10.0) < 1)
               {
                  printf("WD: Enlightenment main loop hung. No response to ping for 10sec\n");
                  // do hard-exit as cleanup isnt doable
                  _exit(121);
               }
             // wait another 10 sec before pinging
             sleep(10);
          }
        else
          {
             printf("WD: Watchdog response alloc fail!!!!\n");
             // XXX: alloc fail
             break;
          }
     }
}

static void
_cb_watchdog_thread_pingpong_reply(void *data EINA_UNUSED, Ecore_Thread *thread EINA_UNUSED, void *msg)
{
   // repluy back to mainloop with same ping number
   unsigned long long *seq = msg;
   ecore_pipe_write(_watchdog_pipe, seq, sizeof(unsigned long long));
   free(seq);
}

static void
_cb_watchdog_thread_pingpong_end(void *data EINA_UNUSED, Ecore_Thread *thread EINA_UNUSED)
{
   ecore_pipe_del(_watchdog_pipe);
   _watchdog_pipe = NULL;
   _watchdog_thread = NULL;
}

E_API void
e_watchdog_begin(void)
{
   // set up main-loop ping-pong to a thread
   _watchdog_pipe = ecore_pipe_add(_cb_watchdog_thread_pingpong_pipe, NULL);
   // stop mainloop watching with fd handler as wer wait manually
   ecore_pipe_freeze(_watchdog_pipe);
   _watchdog_thread = ecore_thread_feedback_run
     (_cb_watchdog_thread_pingpong,
      _cb_watchdog_thread_pingpong_reply,
      _cb_watchdog_thread_pingpong_end,
      NULL,
      NULL, EINA_TRUE);
}

E_API void
e_watchdog_end(void)
{
   if (_watchdog_thread) ecore_thread_cancel(_watchdog_thread);
   _watchdog_thread = NULL;
}
/* TODO: support different preference modes ala edje */
#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *obj;

   Evas_Object *child;
   int          aspect_w, aspect_h;
   int          aspect_preference;
   double       align_x, align_y;
};

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   free(wd);
}

static void
_e_wid_reconfigure(E_Widget_Data *wd)
{
   int px, py, pw, ph;
   int cx, cy, cw, ch;
   double ap, ad;

   if (!wd->obj || !wd->child) return;

   evas_object_geometry_get(wd->obj, &px, &py, &pw, &ph);

   if (ph == 0)
     {
        evas_object_resize(wd->child, 0, 0);
        return;
     }

   ap = (double)pw / ph;
   ad = (double)(wd->aspect_w) / wd->aspect_h;
   if (ap >= ad)
     {
        ch = ph;
        cw = (ch * wd->aspect_w) / wd->aspect_h;
     }
   else
     {
        cw = pw;
        ch = (cw * wd->aspect_h) / wd->aspect_w;
     }

   cx = px + (wd->align_x * (pw - cw));
   cy = py + (wd->align_y * (ph - ch));

   evas_object_resize(wd->child, cw, ch);
   evas_object_move(wd->child, cx, cy);
}

static void
_cb_reconfigure(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   _e_wid_reconfigure(wd);
}

E_API Evas_Object *
e_widget_aspect_add(Evas *evas, int w, int h)
{
   Evas_Object *obj;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   wd = calloc(1, sizeof(E_Widget_Data));

   wd->obj = obj;
   e_widget_data_set(obj, wd);
   e_widget_del_hook_set(obj, _e_wid_del_hook);

   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _cb_reconfigure, wd);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOVE, _cb_reconfigure, wd);
   wd->align_x = 0.5;
   wd->align_y = 0.5;
   wd->aspect_w = w;
   wd->aspect_h = h;
   _e_wid_reconfigure(wd);

   return obj;
}

E_API void
e_widget_aspect_aspect_set(Evas_Object *obj, int w, int h)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   wd->aspect_w = w;
   wd->aspect_h = h;
   _e_wid_reconfigure(wd);
}

E_API void
e_widget_aspect_align_set(Evas_Object *obj, double align_x, double align_y)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   wd->align_x = align_x;
   wd->align_y = align_y;
   _e_wid_reconfigure(wd);
}

E_API void
e_widget_aspect_child_set(Evas_Object *obj, Evas_Object *child)
{
   E_Widget_Data *wd;
   int mw, mh;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   wd->child = child;
   e_widget_size_min_get(child, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   e_widget_sub_object_add(obj, child);
   _e_wid_reconfigure(wd);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *obj, *table;
   Eina_List   *desks;
   int          w, h, dx, dy, cx, cy;
};
typedef struct _E_Widget_Desk_Data E_Widget_Desk_Data;
struct _E_Widget_Desk_Data
{
   Evas_Object         *icon, *live, *cont;
   int                  zone, x, y;
   Ecore_Event_Handler *bg_upd_hdl;
   Ecore_Job           *resize_job;
   Eina_Bool            configurable E_BITFIELD;
   Eina_Bool            thumb E_BITFIELD;
};

/* local function prototypes */
static void      _e_wid_data_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void      _e_wid_livethumb_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void      _e_wid_del_hook(Evas_Object *obj);
static void      _e_wid_reconfigure(E_Widget_Data *wd);
static void      _e_wid_desk_cb_config(void *data, Evas *evas, Evas_Object *obj, void *event);
static void      _e_wid_cb_resize(void *data, Evas *evas, Evas_Object *obj, void *event);
static Eina_Bool _e_wid_cb_bg_update(void *data, int type, void *event);

E_API Evas_Object *
e_widget_bgpreview_add(Evas *evas, int nx, int ny)
{
   Evas_Object *obj;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   wd = E_NEW(E_Widget_Data, 1);
   wd->obj = obj;
   wd->dx = nx;
   wd->dy = ny;
   e_widget_data_set(obj, wd);
   e_widget_del_hook_set(obj, _e_wid_del_hook);

   wd->table = evas_object_table_add(evas);
   evas_object_table_padding_set(wd->table, 0, 0);
   evas_object_table_align_set(wd->table, 0.5, 0.5);
   e_widget_resize_object_set(wd->obj, wd->table);
   evas_object_show(wd->table);
   e_widget_sub_object_add(wd->obj, wd->table);

   e_widget_bgpreview_num_desks_set(obj, wd->dx, wd->dy);

   evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE,
                                  _e_wid_cb_resize, NULL);
   return obj;
}

E_API void
e_widget_bgpreview_num_desks_set(Evas_Object *obj, int nx, int ny)
{
   E_Widget_Data *wd;

   if (!(wd = e_widget_data_get(obj))) return;
   wd->dx = nx;
   wd->dy = ny;
   _e_wid_reconfigure(wd);
}

E_API Evas_Object *
e_widget_bgpreview_desk_add(Evas *e, E_Zone *zone, int x, int y)
{
   E_Widget_Desk_Data *dd;
   Evas_Object *o;
   const char *bgfile;

   bgfile = e_bg_file_get(zone->num, x, y);

   dd = E_NEW(E_Widget_Desk_Data, 1);
   dd->zone = zone->num;
   dd->x = x;
   dd->y = y;

   dd->cont = evas_object_table_add(e);

   dd->thumb = EINA_FALSE;
   if (eina_str_has_extension(bgfile, ".edj"))
     {
        dd->live = o = e_thumb_icon_add(e);
        e_thumb_icon_size_set(o, zone->w / 8, zone->h / 8);
        e_thumb_icon_file_set(o, bgfile, "e/desktop/background");
        e_thumb_desk_pan_set(o, x, y, zone->desk_x_count, zone->desk_y_count);
        e_icon_fill_inside_set(o, EINA_FALSE);
        e_thumb_icon_begin(o);
        dd->thumb = EINA_TRUE;
     }
   else if ((eina_str_has_extension(bgfile, ".gif")) ||
            (eina_str_has_extension(bgfile, ".png")) ||
            (eina_str_has_extension(bgfile, ".jpg")) ||
            (eina_str_has_extension(bgfile, ".jpeg")) ||
            (eina_str_has_extension(bgfile, ".bmp")))
     {
        dd->live = o = e_icon_add(e);
        e_icon_file_key_set(o, bgfile, NULL);
        e_icon_fill_inside_set(o, 0);
     }
   else
     {
        dd->live = o = e_video_add(e, bgfile, EINA_TRUE);
     }
   eina_stringshare_del(bgfile);

   evas_object_size_hint_weight_set(dd->live, 1, 1);
   evas_object_size_hint_align_set(dd->live, -1, -1);
   evas_object_table_pack(dd->cont, dd->live, 0, 0, 1, 1);
   evas_object_show(dd->live);

   evas_object_data_set(dd->cont, "desk_data", dd);
   evas_object_event_callback_add(dd->cont, EVAS_CALLBACK_DEL, _e_wid_data_del, dd);
   evas_object_event_callback_add(dd->cont, EVAS_CALLBACK_RESIZE, _e_wid_livethumb_resize, dd);

   dd->bg_upd_hdl = ecore_event_handler_add(E_EVENT_BG_UPDATE,
                                            _e_wid_cb_bg_update, dd);

   return dd->cont;
}

E_API void
e_widget_bgpreview_desk_configurable_set(Evas_Object *obj, Eina_Bool enable)
{
   E_Widget_Desk_Data *dd;

   EINA_SAFETY_ON_NULL_RETURN(obj);
   dd = evas_object_data_get(obj, "desk_data");
   EINA_SAFETY_ON_NULL_RETURN(dd);
   enable = !!enable;
   if (dd->configurable == enable) return;
   if (enable)
     evas_object_event_callback_add(dd->icon, EVAS_CALLBACK_MOUSE_UP,
                                    _e_wid_desk_cb_config, dd);
   else
     evas_object_event_callback_del_full(dd->icon, EVAS_CALLBACK_MOUSE_UP,
                                         _e_wid_desk_cb_config, dd);
   dd->configurable = enable;
}

/* local function prototypes */
static void
_e_wid_livethumb_resize_job(void *data)
{
   E_Widget_Desk_Data *dd = data;
   E_Zone *zone;
   int w, h;

   if (dd->thumb)
     {
        const char *bgfile;

        bgfile = e_bg_file_get(dd->zone, dd->x, dd->y);
        zone = e_comp_object_util_zone_get(dd->live);
        if (!zone) zone = eina_list_data_get(e_comp->zones);
        evas_object_geometry_get(dd->cont, NULL, NULL, &w, &h);
        if ((w != zone->w) || (h != zone->h))
          {
             w *= 2;
             h *= 2;
             if (w > 128)
               {
                  w = 128;
                  h = (zone->h * w) / zone->w;
               }
             if (h > 128)
               {
                  h = 128;
                  w = (zone->w * h) / zone->h;
               }
          }
        e_thumb_icon_size_set(dd->live, w, h);
        e_thumb_icon_file_set(dd->live, bgfile, "e/desktop/background");
        e_icon_fill_inside_set(dd->live, EINA_FALSE);
        e_thumb_icon_rethumb(dd->live);
     }
   dd->resize_job = NULL;
}

static void
_e_wid_livethumb_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Desk_Data *dd = data;

   if (!dd->resize_job)
     dd->resize_job = ecore_job_add(_e_wid_livethumb_resize_job, dd);
}

static void
_e_wid_data_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Desk_Data *dd;
   dd = data;
   if (!dd) return;
   if (dd->bg_upd_hdl) ecore_event_handler_del(dd->bg_upd_hdl);
   evas_object_del(dd->live);
   ecore_job_del(dd->resize_job);
   free(dd);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;
   Evas_Object *o;

   if (!(wd = e_widget_data_get(obj))) return;

   EINA_LIST_FREE(wd->desks, o)
     evas_object_del(o);

   E_FREE(wd);
}

static void
_e_wid_reconfigure(E_Widget_Data *wd)
{
   Eina_List *l, *ll;
   Evas_Object *dw;
   E_Zone *zone;
   int tw, th, mw, mh, y;
   E_Widget_Desk_Data *dd;
   double zone_ratio, desk_ratio;

   zone = e_zone_current_get();

   evas_object_geometry_get(wd->table, NULL, NULL, &tw, &th);
   if ((tw == 0) || (th == 0))
     {
        mw = mh = 0;
     }
   else
     {
        zone_ratio = (double)zone->w / zone->h;
        desk_ratio = (tw / wd->dx) / (th / wd->dy);

        if (zone_ratio > desk_ratio)
          {
             mw = tw / wd->dx;
             mh = mw / zone_ratio;
          }
        else
          {
             mh = th / wd->dy;
             mw = mh * zone_ratio;
          }
     }

   EINA_LIST_FOREACH_SAFE(wd->desks, l, ll, dw)
     {
        if (!(dd = evas_object_data_get(dw, "desk_data"))) continue;
        if (!((dd->x < wd->dx) && (dd->y < wd->dy)))
          {
             evas_object_del(dd->live);
             evas_object_del(dw);
             wd->desks = eina_list_remove(wd->desks, dw);
          }
     }

   for (y = 0; y < wd->dy; y++)
     {
        int x, sx;

        if (y >= wd->cy) sx = 0;
        else sx = wd->cx;
        for (x = sx; x < wd->dx; x++)
          {
             Evas_Object *dp;
             Evas *e;

             e = evas_object_evas_get(wd->obj);
             dp = e_widget_bgpreview_desk_add(e, zone, x, y);

             dd = evas_object_data_get(dp, "desk_data");
             dp = dd->icon = edje_object_add(e);
             e_theme_edje_object_set(dd->icon, "base/theme/widgets",
                                     "e/widgets/deskpreview/desk");

             edje_object_part_swallow(dd->icon, "e.swallow.content", dd->cont);
             dd->configurable = EINA_TRUE;
             evas_object_event_callback_add(dd->icon, EVAS_CALLBACK_MOUSE_UP,
                                            _e_wid_desk_cb_config, dd);
             evas_object_show(dd->icon);
             evas_object_data_set(dd->icon, "desk_data", dd);
             evas_object_size_hint_align_set(dp, -1, -1);
             evas_object_size_hint_weight_set(dp, 1, 1);
             evas_object_table_pack(wd->table, dp, x, y, 1, 1);
             wd->desks = eina_list_append(wd->desks, dp);
          }
     }

   wd->cx = wd->dx;
   wd->cy = wd->dy;
}

static void
_e_wid_desk_cb_config(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   E_Widget_Desk_Data *dd;
   Evas_Event_Mouse_Down *ev;

   ev = event;
   if (!(dd = data)) return;
   if (ev->button == 1)
     {
        char buff[256];

        snprintf(buff, sizeof(buff), "%i %i %i",
                 dd->zone, dd->x, dd->y);
        e_configure_registry_call("internal/desk", NULL, buff);
     }
}

static void
_e_wid_cb_resize(void *data EINA_UNUSED, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED)
{
   E_Widget_Data *wd;

   if (!(wd = e_widget_data_get(obj))) return;
   _e_wid_reconfigure(wd);
}

static Eina_Bool
_e_wid_cb_bg_update(void *data, int type, void *event)
{
   E_Event_Bg_Update *ev;
   E_Widget_Desk_Data *dd;

   if (type != E_EVENT_BG_UPDATE) return ECORE_CALLBACK_PASS_ON;
   if (!(dd = data)) return ECORE_CALLBACK_PASS_ON;
   ev = event;

   if (((ev->zone < 0) || (dd->zone == ev->zone)) &&
       ((ev->desk_x < 0) || (dd->x == ev->desk_x)) &&
       ((ev->desk_y < 0) || (dd->y == ev->desk_y)))
     {
        E_Zone *zone;
        const char *bgfile;
        Evas_Object *o;
        Evas *e = evas_object_evas_get(dd->cont);

        zone = e_comp_zone_number_get(dd->zone);
        bgfile = e_bg_file_get(dd->zone, dd->x, dd->y);

        if (dd->live) evas_object_del(dd->live);
        dd->live = NULL;

        dd->thumb = EINA_FALSE;
        if (eina_str_has_extension(bgfile, ".edj"))
          {
             dd->live = o = e_thumb_icon_add(e);
             e_thumb_icon_size_set(o, zone->w / 8, zone->h / 8);
             e_thumb_icon_file_set(o, bgfile, "e/desktop/background");
             e_thumb_desk_pan_set(o, dd->x, dd->y, zone->desk_x_count, zone->desk_y_count);
             e_icon_fill_inside_set(o, EINA_FALSE);
             e_thumb_icon_begin(o);
             dd->thumb = EINA_TRUE;
             _e_wid_livethumb_resize_job(dd);
          }
        else if ((eina_str_has_extension(bgfile, ".gif")) ||
                 (eina_str_has_extension(bgfile, ".png")) ||
                 (eina_str_has_extension(bgfile, ".jpg")) ||
                 (eina_str_has_extension(bgfile, ".jpeg")) ||
                 (eina_str_has_extension(bgfile, ".bmp")))
          {
             dd->live = o = e_icon_add(e);
             e_icon_file_key_set(o, bgfile, NULL);
             e_icon_fill_inside_set(o, 0);
          }
        else
          {
             dd->live = o = e_video_add(e, bgfile, EINA_TRUE);
          }
        eina_stringshare_del(bgfile);

        evas_object_size_hint_weight_set(dd->live, 1, 1);
        evas_object_size_hint_align_set(dd->live, -1, -1);
        evas_object_table_pack(dd->cont, dd->live, 0, 0, 1, 1);
        evas_object_show(dd->live);
     }

   return ECORE_CALLBACK_PASS_ON;
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object         *o_button;
   Evas_Object         *o_icon;
   void                 (*func)(void *data, void *data2);
   void                *data;
   void                *data2;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_activate_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _click(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_button_add(Evas *evas, const char *label, const char *icon, void (*func)(void *data, void *data2), void *data, void *data2)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_activate_hook_set(obj, _e_wid_activate_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   wd->func = func;
   wd->data = data;
   wd->data2 = data2;
   e_widget_data_set(obj, wd);

   o = elm_button_add(e_win_evas_win_get(evas));
   evas_object_smart_callback_add(o, "clicked", _click, obj);
   wd->o_button = o;
   if ((label) && (label[0] != 0))
     {
        elm_object_text_set(o, label);
     }

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);
   evas_object_show(o);

   if (icon)
     {
        o = e_icon_add(evas);
        wd->o_icon = o;
        e_util_icon_theme_set(o, icon);
        elm_object_content_set(wd->o_button, o);
        e_widget_sub_object_add(obj, o);
        evas_object_show(o);
     }

   return obj;
}

E_API void
e_widget_button_label_set(Evas_Object *obj, const char *label)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_text_set(wd->o_button, label);
}

E_API void
e_widget_button_icon_set(Evas_Object *obj, Evas_Object *icon)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->o_icon)
     {
        e_widget_sub_object_del(obj, wd->o_icon);
        evas_object_hide(wd->o_icon);
        elm_object_content_unset(wd->o_button);
        evas_object_del(wd->o_icon);
        wd->o_icon = NULL;
     }
   if (icon)
     {
        wd->o_icon = icon;
        elm_object_content_set(wd->o_button, icon);
        evas_object_show(icon);
        e_widget_sub_object_add(obj, icon);
     }
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_focus_set(wd->o_button, e_widget_focus_get(obj));
}

static void
_e_wid_activate_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->func) wd->func(wd->data, wd->data2);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_disabled_set(wd->o_button, e_widget_disabled_get(obj));
}

static void
_click(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Object *wid;

   wid = data;
   if ((!wid) || (e_widget_disabled_get(wid))) return;
   e_widget_focus_steal(wid);
   e_widget_change(wid);
   _e_wid_activate_hook(wid);
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}
#include "e.h"

#define SMART_NAME     "e_widget"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;

struct _E_Smart_Data
{
   Evas_Object  *parent_obj;
   Evas_Coord    x, y, w, h, minw, minh;
   Eina_List    *subobjs;
   Evas_Object  *resize_obj;
   void          (*del_func)(Evas_Object *obj);
   void          (*focus_func)(Evas_Object *obj);
   void          (*activate_func)(Evas_Object *obj);
   void          (*disable_func)(Evas_Object *obj);
   void          (*on_focus_func)(void *data, Evas_Object *obj);
   void         *on_focus_data;
   void          (*on_change_func)(void *data, Evas_Object *obj);
   void         *on_change_data;
   void          (*on_disable_func)(void *data, Evas_Object *obj);
   void         *on_disable_data;
   void         *data;
   unsigned char can_focus E_BITFIELD;
   unsigned char child_can_focus E_BITFIELD;
   unsigned char focused E_BITFIELD;
   unsigned char disabled E_BITFIELD;
};

/* local subsystem functions */
static void _e_smart_reconfigure(E_Smart_Data *sd);
static void _e_smart_add(Evas_Object *obj);
static void _e_smart_del(Evas_Object *obj);
static void _e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _e_smart_show(Evas_Object *obj);
static void _e_smart_hide(Evas_Object *obj);
static void _e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _e_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void _e_smart_clip_unset(Evas_Object *obj);
static void _e_smart_init(void);

static void
_e_widget_hint(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int w, h;

   evas_object_size_hint_min_get(obj, &w, &h);
   e_widget_size_min_set(data, w, h);
}

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_widget_add(Evas *evas)
{
   _e_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_widget_del_hook_set(Evas_Object *obj, void (*func)(Evas_Object *obj))
{
   API_ENTRY return;
   sd->del_func = func;
}

E_API void
e_widget_focus_hook_set(Evas_Object *obj, void (*func)(Evas_Object *obj))
{
   API_ENTRY return;
   sd->focus_func = func;
}

E_API void
e_widget_activate_hook_set(Evas_Object *obj, void (*func)(Evas_Object *obj))
{
   API_ENTRY return;
   sd->activate_func = func;
}

E_API void
e_widget_disable_hook_set(Evas_Object *obj, void (*func)(Evas_Object *obj))
{
   API_ENTRY return;
   sd->disable_func = func;
}

E_API void
e_widget_on_focus_hook_set(Evas_Object *obj, void (*func)(void *data, Evas_Object *obj), void *data)
{
   API_ENTRY return;
   sd->on_focus_func = func;
   sd->on_focus_data = data;
}

E_API void
e_widget_on_change_hook_set(Evas_Object *obj, void (*func)(void *data, Evas_Object *obj), void *data)
{
   API_ENTRY return;
   sd->on_change_func = func;
   sd->on_change_data = data;
}

E_API void
e_widget_on_disable_hook_set(Evas_Object *obj, void (*func)(void *data, Evas_Object *obj), void *data)
{
   API_ENTRY return;
   sd->on_disable_func = func;
   sd->on_disable_data = data;
}

E_API void
e_widget_data_set(Evas_Object *obj, void *data)
{
   API_ENTRY return;
   sd->data = data;
}

E_API void *
e_widget_data_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->data;
}

E_API void
e_widget_size_min_set(Evas_Object *obj, Evas_Coord minw, Evas_Coord minh)
{
   evas_object_size_hint_min_set(obj, minw, minh);
}

E_API void
e_widget_size_min_get(Evas_Object *obj, Evas_Coord *minw, Evas_Coord *minh)
{
   evas_object_size_hint_min_get(obj, minw, minh);
}

static void
_sub_obj_del(void *data,
             Evas *e EINA_UNUSED,
             Evas_Object *obj,
             void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd = data;

   sd->subobjs = eina_list_remove(sd->subobjs, obj);
   evas_object_event_callback_del(obj, EVAS_CALLBACK_DEL, _sub_obj_del);
}

E_API void
e_widget_sub_object_add(Evas_Object *obj, Evas_Object *sobj)
{
   API_ENTRY return;

   if (eina_list_data_find(sd->subobjs, sobj)) return;

   sd->subobjs = eina_list_append(sd->subobjs, sobj);
   evas_object_event_callback_add(sobj, EVAS_CALLBACK_DEL, _sub_obj_del, sd);
   if (!sd->child_can_focus)
     {
        if (e_widget_can_focus_get(sobj)) sd->child_can_focus = 1;
     }
   if (!evas_object_smart_parent_get(sobj))
     evas_object_smart_member_add(sobj, obj);
   if (strcmp(evas_object_type_get(sobj), SMART_NAME)) return;

   sd = evas_object_smart_data_get(sobj);
   if (sd)
     {
        if (sd->parent_obj) e_widget_sub_object_del(sd->parent_obj, sobj);
        sd->parent_obj = obj;
     }
   evas_object_event_callback_del_full(sobj, EVAS_CALLBACK_DEL, _sub_obj_del, sd);
}

E_API void
e_widget_sub_object_del(Evas_Object *obj, Evas_Object *sobj)
{
   API_ENTRY return;
   if (evas_object_smart_parent_get(sobj) == obj)
     evas_object_smart_member_del(sobj);
   evas_object_event_callback_del(sobj, EVAS_CALLBACK_DEL, _sub_obj_del);
   sd->subobjs = eina_list_remove(sd->subobjs, sobj);
   if (!sd->child_can_focus)
     {
        if (e_widget_can_focus_get(sobj)) sd->child_can_focus = 0;
     }
}

E_API void
e_widget_resize_object_set(Evas_Object *obj, Evas_Object *sobj)
{
   int w, h;
   API_ENTRY return;
   if (sd->resize_obj) evas_object_smart_member_del(sd->resize_obj);
   sd->resize_obj = sobj;
   evas_object_event_callback_add(sobj, EVAS_CALLBACK_CHANGED_SIZE_HINTS, _e_widget_hint, obj);
   evas_object_smart_member_add(sobj, obj);
   _e_smart_reconfigure(sd);
   evas_object_size_hint_min_get(obj, &w, &h);
   if ((w > 0) || (h > 0)) return; //already set
   evas_object_size_hint_min_get(sobj, &w, &h);
   evas_object_size_hint_min_set(obj, w, h);
}

E_API void
e_widget_can_focus_set(Evas_Object *obj, int can_focus)
{
   API_ENTRY return;
   sd->can_focus = can_focus;
}

E_API int
e_widget_can_focus_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   if (sd->disabled) return 0;
   if (sd->can_focus) return 1;
   if (sd->child_can_focus) return 1;
   return 0;
}

E_API int
e_widget_focus_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->focused;
}

E_API Evas_Object *
e_widget_focused_object_get(Evas_Object *obj)
{
   Eina_List *l = NULL;
   Evas_Object *sobj = NULL;

   API_ENTRY return NULL;
   if (!sd->focused) return NULL;
   EINA_LIST_FOREACH(sd->subobjs, l, sobj)
     {
        sobj = e_widget_focused_object_get(sobj);
        if (sobj) return sobj;
     }
   return obj;
}

E_API int
e_widget_focus_jump(Evas_Object *obj, int forward)
{
   API_ENTRY return 0;
   if (!e_widget_can_focus_get(obj)) return 0;

   /* if it has a focus func its an end-point widget like a button */
   if (sd->focus_func)
     {
        if (!sd->focused) sd->focused = 1;
        else sd->focused = 0;
        sd->focus_func(obj);
        if (sd->on_focus_func) sd->on_focus_func(sd->on_focus_data, obj);
        return sd->focused;
     }
   /* its some container */
   else
     {
        Eina_List *l = NULL;
        Evas_Object *sobj = NULL;
        int focus_next = 0;

        if ((!sd->disabled) && (!sd->focused))
          {
             e_widget_focus_set(obj, forward);
             sd->focused = 1;
             if (sd->on_focus_func) sd->on_focus_func(sd->on_focus_data, obj);
             return 1;
          }
        else
          {
             if (forward)
               {
                  EINA_LIST_FOREACH(sd->subobjs, l, sobj)
                    {
                       if (!e_widget_can_focus_get(sobj)) continue;
                       if (focus_next)
                         {
                            /* the previous focused item was unfocused - so focus
                             * the next one (that can be focused) */
                            if (e_widget_focus_jump(sobj, forward))
                              return 1;
                            break;
                         }
                       else
                         {
                            if (e_widget_focus_get(sobj))
                              {
                                 /* jump to the next focused item or focus this item */
                                 if (e_widget_focus_jump(sobj, forward))
                                   return 1;
                                 /* it returned 0 - it got to the last item and is past it */
                                 focus_next = 1;
                              }
                         }
                    }
               }
             else
               {
                  EINA_LIST_REVERSE_FOREACH(sd->subobjs, l, sobj)
                    {
                       if (e_widget_can_focus_get(sobj))
                         {
                            if ((focus_next) &&
                                (!e_widget_disabled_get(sobj)))
                              {
                                 /* the previous focused item was unfocused - so focus
                                  * the next one (that can be focused) */
                                 if (e_widget_focus_jump(sobj, forward))
                                   return 1;
                                 else break;
                              }
                            else
                              {
                                 if (e_widget_focus_get(sobj))
                                   {
                                      /* jump to the next focused item or focus this item */
                                      if (e_widget_focus_jump(sobj, forward))
                                        return 1;
                                      /* it returned 0 - it got to the last item and is past it */
                                      focus_next = 1;
                                   }
                              }
                         }
                    }
               }
          }
     }
   /* no next item can be focused */
   sd->focused = 0;
   return 0;
}

E_API void
e_widget_focus_set(Evas_Object *obj, int first)
{
   API_ENTRY return;
   if (!sd->focused)
     {
        sd->focused = 1;
        if (sd->on_focus_func) sd->on_focus_func(sd->on_focus_data, obj);
     }
   if (sd->focus_func)
     {
        sd->focus_func(obj);
        return;
     }
   else
     {
        Eina_List *l = NULL;
        Evas_Object *sobj;

        if (first)
          {
             EINA_LIST_FOREACH(sd->subobjs, l, sobj)
               {
                  if ((e_widget_can_focus_get(sobj)) &&
                      (!e_widget_disabled_get(sobj)))
                    {
                       e_widget_focus_set(sobj, first);
                       break;
                    }
               }
          }
        else
          {
             EINA_LIST_REVERSE_FOREACH(sd->subobjs, l, sobj)
               {
                  if ((e_widget_can_focus_get(sobj)) &&
                      (!e_widget_disabled_get(sobj)))
                    {
                       e_widget_focus_set(sobj, first);
                       break;
                    }
               }
          }
     }
}

E_API Evas_Object *
e_widget_parent_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->parent_obj;
}

E_API void
e_widget_focused_object_clear(Evas_Object *obj)
{
   Eina_List *l = NULL;
   Evas_Object *sobj = NULL;

   API_ENTRY return;
   if (!sd->focused) return;
   sd->focused = 0;
   EINA_LIST_FOREACH(sd->subobjs, l, sobj)
     {
        if (e_widget_focus_get(sobj))
          {
             e_widget_focused_object_clear(sobj);
             break;
          }
     }
   if (sd->focus_func) sd->focus_func(obj);
}

E_API void
e_widget_focus_steal(Evas_Object *obj)
{
   Evas_Object *parent = NULL, *o = NULL;

   API_ENTRY return;
   if ((sd->focused) || (sd->disabled) || (!sd->can_focus)) return;
   parent = obj;
   for (;; )
     {
        o = e_widget_parent_get(parent);
        if (!o) break;
        parent = o;
     }
   e_widget_focused_object_clear(parent);
   parent = obj;
   for (;; )
     {
        sd = evas_object_smart_data_get(parent);
        sd->focused = 1;
        if (sd->on_focus_func) sd->on_focus_func(sd->on_focus_data, parent);
        o = e_widget_parent_get(parent);
        if (!o) break;
        parent = o;
     }
   sd = evas_object_smart_data_get(obj);
   if (sd->focus_func) sd->focus_func(obj);
}

E_API void
e_widget_activate(Evas_Object *obj)
{
   API_ENTRY return;
   e_widget_change(obj);
   if (sd->activate_func) sd->activate_func(obj);
}

E_API void
e_widget_change(Evas_Object *obj)
{
   API_ENTRY return;
   if (sd->parent_obj) e_widget_change(sd->parent_obj);
   if (sd->on_change_func) sd->on_change_func(sd->on_change_data, obj);
}

E_API void
e_widget_disabled_set(Evas_Object *obj, int disabled)
{
   API_ENTRY return;
   if (sd->disabled == !!disabled) return;
   sd->disabled = !!disabled;
   if (sd->focused && sd->disabled)
     {
        Evas_Object *o = NULL, *parent = NULL;

        parent = obj;
        for (;; )
          {
             o = e_widget_parent_get(parent);
             if (!o) break;
             parent = o;
          }
        e_widget_focus_jump(parent, 1);
        if (sd->focused)
          {
             sd->focused = 0;
             if (sd->focus_func) sd->focus_func(obj);
          }
     }
   if (sd->disable_func) sd->disable_func(obj);
   if (sd->on_disable_func) sd->on_disable_func(sd->on_disable_data, obj);
}

E_API int
e_widget_disabled_get(Evas_Object *obj)
{
   API_ENTRY return 0;
   return sd->disabled;
}

E_API E_Pointer *
e_widget_pointer_get(Evas_Object *obj)
{
   Evas_Object *win = NULL;

   API_ENTRY return NULL;
   win = e_win_evas_object_win_get(obj);
   if (win) return e_win_pointer_get(win);
   return NULL;
}

E_API void
e_widget_size_min_resize(Evas_Object *obj)
{
   Evas_Coord minw, minh;
   evas_object_size_hint_min_get(obj, &minw, &minh);
   evas_object_resize(obj, minw, minh);
}

/* local subsystem functions */
static void
_e_smart_reconfigure(E_Smart_Data *sd)
{
   if (sd->resize_obj)
     {
        evas_object_move(sd->resize_obj, sd->x, sd->y);
        evas_object_resize(sd->resize_obj, sd->w, sd->h);
     }
}

static void
_e_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd = NULL;

   sd = calloc(1, sizeof(E_Smart_Data));
   if (!sd) return;
   sd->x = 0;
   sd->y = 0;
   sd->w = 0;
   sd->h = 0;
   sd->can_focus = 1;
   evas_object_smart_data_set(obj, sd);
}

static void
_e_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;

   if (sd->del_func) sd->del_func(obj);
   while (sd->subobjs)
     {
        Evas_Object *sobj = sd->subobjs->data;
        /* DO NOT MACRO THIS!
         * list gets reshuffled during object delete callback chain
         * and breaks the world.
         * BORKER CERTIFICATION: GOLD
         * -discomfitor, 7/4/2012
         */
        evas_object_event_callback_del(sobj, EVAS_CALLBACK_DEL, _sub_obj_del);
        sd->subobjs = eina_list_remove_list(sd->subobjs, sd->subobjs);
        evas_object_del(sobj);
     }
   free(sd);
}

static void
_e_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_smart_reconfigure(sd);
}

static void
_e_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->resize_obj)
     evas_object_show(sd->resize_obj);
}

static void
_e_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->resize_obj)
     evas_object_hide(sd->resize_obj);
}

static void
_e_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   if (sd->resize_obj)
     evas_object_color_set(sd->resize_obj, r, g, b, a);
}

static void
_e_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   if (sd->resize_obj)
     evas_object_clip_set(sd->resize_obj, clip);
}

static void
_e_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->resize_obj)
     evas_object_clip_unset(sd->resize_obj);
}

/* never need to touch this */

static void
_e_smart_init(void)
{
   if (_e_smart) return;
   {
      static const Evas_Smart_Class sc =
      {
         SMART_NAME,
         EVAS_SMART_CLASS_VERSION,
         _e_smart_add,
         _e_smart_del,
         _e_smart_move,
         _e_smart_resize,
         _e_smart_show,
         _e_smart_hide,
         _e_smart_color_set,
         _e_smart_clip_set,
         _e_smart_clip_unset,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL,
         NULL
      };
      _e_smart = evas_smart_class_new(&sc);
   }
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_check;
   Evas_Object *o_icon;
   int         *valptr;
   Eina_List *widgets_enable;
   Eina_List *widgets_disable;
};

/* local subsystem functions */

static void
_extern_obj_enable_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd = data;
   wd->widgets_enable = eina_list_remove(wd->widgets_enable, obj);
}

static void
_extern_obj_disable_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd = data;
   wd->widgets_disable = eina_list_remove(wd->widgets_disable, obj);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;
   Evas_Object *o;

   wd = e_widget_data_get(obj);
   EINA_LIST_FREE(wd->widgets_enable, o)
     evas_object_event_callback_del_full(o, EVAS_CALLBACK_DEL, _extern_obj_enable_del, wd);
   EINA_LIST_FREE(wd->widgets_disable, o)
     evas_object_event_callback_del_full(o, EVAS_CALLBACK_DEL, _extern_obj_disable_del, wd);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(wd->o_check, "e,state,focused", "e");
        evas_object_focus_set(wd->o_check, 1);
     }
   else
     {
        edje_object_signal_emit(wd->o_check, "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_check, 0);
     }
}

static void
_e_wid_do(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (e_widget_disabled_get(obj)) return;

   wd = e_widget_data_get(obj);
   if (wd->valptr)
     {
        Eina_List *l;
        Evas_Object *o;

        if (*(wd->valptr) == 0)
          {
             *(wd->valptr) = 1;
             edje_object_signal_emit(wd->o_check, "e,state,checked", "e");
          }
        else
          {
             *(wd->valptr) = 0;
             edje_object_signal_emit(wd->o_check, "e,state,unchecked", "e");
          }
        EINA_LIST_FOREACH(wd->widgets_enable, l, o)
          e_widget_disabled_set(o, *wd->valptr);
        EINA_LIST_FOREACH(wd->widgets_disable, l, o)
          e_widget_disabled_set(o, !(*wd->valptr));
     }
   evas_object_smart_callback_call(obj, "changed", NULL);
   e_widget_change(obj);
}

static void
_e_wid_activate_hook(Evas_Object *obj)
{
   _e_wid_do(obj);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_disabled_get(obj))
     edje_object_signal_emit(wd->o_check, "e,state,disabled", "e");
   else
     edje_object_signal_emit(wd->o_check, "e,state,enabled", "e");
}

static void
_e_wid_signal_cb1(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_wid_do(data);
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

/* externally accessible functions */

/**
 * Creates a check box widget
 *
 * @param evas pointer
 * @param label the text to assign to check widget
 * @param val pointer to int where changes are stored
 * @return the newly created evas object
 */
E_API Evas_Object *
e_widget_check_add(Evas *evas, const char *label, int *val)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_activate_hook_set(obj, _e_wid_activate_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   wd->valptr = val;
   e_widget_data_set(obj, wd);

   o = edje_object_add(evas);
   wd->o_check = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/check");
   edje_object_signal_callback_add(o, "e,action,toggle", "*", _e_wid_signal_cb1, obj);
   edje_object_part_text_set(o, "e.text.label", label);
   evas_object_show(o);
   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   if (wd->valptr)
     {
        if (*(wd->valptr)) edje_object_signal_emit(o, "e,state,checked", "e");
     }

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);

   return obj;
}

/**
 * Sets the value of the check box
 *
 * @param check the check box widget
 * @param checked the value to set the widget too
 */
E_API void
e_widget_check_checked_set(Evas_Object *check, int checked)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(check);
   if (wd->valptr)
     *(wd->valptr) = checked;
   if (checked)
     edje_object_signal_emit(wd->o_check, "e,state,checked", "e");
   else
     edje_object_signal_emit(wd->o_check, "e,state,unchecked", "e");
}

/**
 * Sets the value of the check box
 *
 * @param check the check box widget
 * @param val pointer to int where changes are stored
 */
E_API void
e_widget_check_valptr_set(Evas_Object *check, int *val)
{
   E_Widget_Data *wd;
   int state;

   wd = e_widget_data_get(check);
   state = wd->valptr ? *wd->valptr : 0;
   wd->valptr = val;
   if (state == (wd->valptr && *wd->valptr)) return;
   if (wd->valptr && *wd->valptr)
     edje_object_signal_emit(wd->o_check, "e,state,checked", "e");
   else
     edje_object_signal_emit(wd->o_check, "e,state,unchecked", "e");
}

/**
 * Get the value of the check box
 *
 * @param check the check box widget
 * @return the value of the check box
 */
E_API int
e_widget_check_checked_get(Evas_Object *check)
{
   E_Widget_Data *wd;
   int ret;

   wd = e_widget_data_get(check);
   if (wd->valptr)
     ret = *(wd->valptr);
   else
     ret = -1;

   return ret;
}

/**
 * Add widget to disable when check object is checked
 * @param check the check box widget
 * @param obj the object to disable when @p check is checked
 */
E_API void
e_widget_check_widget_disable_on_checked_add(Evas_Object *check, Evas_Object *obj)
{
   E_Widget_Data *wd;

   EINA_SAFETY_ON_NULL_RETURN(check);
   EINA_SAFETY_ON_NULL_RETURN(obj);
   wd = e_widget_data_get(check);
   EINA_SAFETY_ON_NULL_RETURN(wd);
   if (wd->valptr)
     e_widget_disabled_set(obj, *wd->valptr);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _extern_obj_enable_del, wd);
   wd->widgets_enable = eina_list_append(wd->widgets_enable, obj);
}

/**
 * Add widget to disable when check object is unchecked
 * @param check the check box widget
 * @param obj the object to disable when @p check is not unchecked
 */
E_API void
e_widget_check_widget_disable_on_unchecked_add(Evas_Object *check, Evas_Object *obj)
{
   E_Widget_Data *wd;

   EINA_SAFETY_ON_NULL_RETURN(check);
   EINA_SAFETY_ON_NULL_RETURN(obj);
   wd = e_widget_data_get(check);
   EINA_SAFETY_ON_NULL_RETURN(wd);
   if (wd->valptr)
     e_widget_disabled_set(obj, !(*wd->valptr));
   evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _extern_obj_disable_del, wd);
   wd->widgets_disable = eina_list_append(wd->widgets_disable, obj);
}


/**
 * Creates a check box widget with icon
 *
 * @param evas pointer
 * @param label the text to assign to check widget
 * @param icon the path to the icon file
 * @param icon_w the minimum wdith of the icon
 * @param icon_h the minimum height of the icon
 * @param val pointer to int where changes are stored
 * @return the newly created evas object
 */
E_API Evas_Object *
e_widget_check_icon_add(Evas *evas, const char *label, const char *icon, int icon_w, int icon_h, int *val)
{
   Evas_Object *obj, *o, *o2;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_activate_hook_set(obj, _e_wid_activate_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   wd->valptr = val;
   e_widget_data_set(obj, wd);

   o = edje_object_add(evas);
   wd->o_check = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/check_icon");
   edje_object_signal_callback_add(o, "e,action,toggle", "*", _e_wid_signal_cb1, obj);
   edje_object_part_text_set(o, "e.text.label", label);
   evas_object_show(o);
   if (label)
     {
        edje_object_signal_emit(o, "e,state,labeled", "e");
        edje_object_message_signal_process(o);
     }
   if (icon)
     {
        if (icon[0] == '/')
          {
             o2 = e_icon_add(evas);
             e_icon_file_set(o2, icon);
          }
        else
          {
             o2 = edje_object_add(evas);
             e_util_edje_icon_set(o2, icon);
          }
        evas_object_size_hint_min_set(o2, icon_w, icon_h);
        edje_object_part_swallow(wd->o_check, "e.swallow.icon", o2);
        evas_object_show(o2);
        e_widget_sub_object_add(obj, o2);
        wd->o_icon = o2;
     }

   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   if (wd->valptr)
     {
        if (*(wd->valptr)) edje_object_signal_emit(o, "e,state,checked", "e");
     }

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);

   return obj;
}
#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object    *obj;
   Evas_Object    *o_edje;
   Evas_Object    *o_rect;

   E_Color_Dialog *dia;
   E_Color        *color;
   Eina_Bool       show_color_dialog;
   Eina_Bool       alpha_enabled;
};

static void _e_wid_update(E_Widget_Data *wd);
static void _e_wid_signal_cb1(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _e_wid_color_select_cb(E_Color_Dialog *dia, E_Color *color, void *data);
static void _e_wid_color_cancel_cb(E_Color_Dialog *dia, E_Color *color, void *data);
static void _e_wid_color_change_cb(E_Color_Dialog *dia, E_Color *color, void *data);
static void _e_wid_disable_hook(Evas_Object *obj);

static void
_e_wid_update(E_Widget_Data *wd)
{
   if (!wd) return;

   evas_object_color_set(wd->o_rect,
                         (wd->color->r * wd->color->a) / 255,
                         (wd->color->g * wd->color->a) / 255,
                         (wd->color->b * wd->color->a) / 255,
                         wd->color->a);
   e_widget_change(wd->obj);
}

static void
_e_wid_signal_cb1(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   Evas_Object *wid;
   E_Widget_Data *wd;

   wid = data;
   wd = e_widget_data_get(wid);

   if (!wd->show_color_dialog) return;
   if (!wd->dia)
     {
        wd->dia = e_color_dialog_new(wd->color, wd->alpha_enabled);
        e_color_dialog_select_callback_set(wd->dia, _e_wid_color_select_cb, wd);
        e_color_dialog_cancel_callback_set(wd->dia, _e_wid_color_cancel_cb, wd);
        e_color_dialog_change_callback_set(wd->dia, _e_wid_color_change_cb, wd);
     }
   e_color_dialog_show(wd->dia);
}

static void
_e_wid_color_select_cb(E_Color_Dialog *dia EINA_UNUSED, E_Color *color, void *data)
{
   E_Widget_Data *wd;

   wd = data;
   e_color_copy(color, wd->color);
   _e_wid_update(wd);
   wd->dia = NULL;
}

static void
_e_wid_color_cancel_cb(E_Color_Dialog *dia EINA_UNUSED, E_Color *color, void *data)
{
   E_Widget_Data *wd;

   wd = data;
   /* cancel sends the original color back */
   e_color_copy(color, wd->color);
   _e_wid_update(wd);
   wd->dia = NULL;
}

static void
_e_wid_color_change_cb(E_Color_Dialog *dia EINA_UNUSED, E_Color *color, void *data)
{
   E_Widget_Data *wd;

   wd = data;
   e_color_copy(color, wd->color);
   _e_wid_update(wd);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   if (wd->dia) e_object_unref(E_OBJECT(wd->dia));
   E_FREE(wd);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_disabled_get(obj))
     {
        wd->show_color_dialog = 0;
        edje_object_signal_emit(wd->o_edje, "e,state,disabled", "e");
     }
   else
     {
        wd->show_color_dialog = 1;
        edje_object_signal_emit(wd->o_edje, "e,state,enabled", "e");
     }
}

/**
 * Add a color well widget to an evas.
 * If not NULL, when clicked a color dialog will pop up.
 */
Evas_Object *
e_widget_color_well_add_full(Evas *evas, E_Color *color, Eina_Bool show_color_dialog, Eina_Bool alpha_enabled)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);

   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);
   wd->obj = obj;

   wd->color = color;

   wd->show_color_dialog = show_color_dialog;
   wd->alpha_enabled = alpha_enabled;

   o = edje_object_add(evas);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/color_well");
   edje_object_signal_callback_add(o, "e,action,click", "",
                                   _e_wid_signal_cb1, obj);
   evas_object_show(o);
   wd->o_edje = o;

   e_widget_size_min_set(obj, 50, 50);

   o = evas_object_rectangle_add(evas);
   e_widget_sub_object_add(obj, o);
   evas_object_color_set(o,
                         (color->r * color->a) / 255,
                         (color->g * color->a) / 255,
                         (color->b * color->a) / 255,
                         color->a);
   edje_object_part_swallow(wd->o_edje, "e.swallow.content", o);
   evas_object_show(o);
   wd->o_rect = o;

   return obj;
}

/**
 * Add a color well widget to an evas.
   If not NULL, when clicked a color dialog will pop up.
 */
Evas_Object *
e_widget_color_well_add(Evas *evas, E_Color *color, Eina_Bool show_color_dialog)
{
   return e_widget_color_well_add_full
            (evas, color, show_color_dialog, EINA_FALSE);
}

/**
 * Let the color well know that its color data has changed.
 */
void
e_widget_color_well_update(Evas_Object *obj)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   _e_wid_update(wd);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;

/* subsystem functions */
static void _list_cb_change(void *data, Evas_Object *obj);
static void _button_cb_add(void *data, void *obj);
static void _button_cb_remove(void *data, void *obj);
static void _button_cb_up(void *data, void *obj);
static void _button_cb_down(void *data, void *obj);
static void _list_select_num(E_Widget_Data *wd, int indx);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _e_wid_del_hook(Evas_Object *obj);

struct _E_Widget_Data
{
   /* Current Data */
   char *cur_entry;
   int   cur_enabled;

   struct
   {
      Evas_Object *list;   /* Selecting a list entry starts edit*/
      Evas_Object *table;
      Evas_Object *entry;       /* Generic Entry */
      Evas_Object *up;   /* Move selected list entry up */
      Evas_Object *down;   /* Move selected list entry down */
      Evas_Object *add;   /* create and select a new list entry */
      Evas_Object *remove;   /* remove the selected entry */
   } gui;
};

/* Externally accessible functions */
E_API Evas_Object *
e_widget_config_list_add(Evas *evas, Evas_Object * (*func_entry_add)(Evas_Object *, char **val, void (*func)(void *data, void *data2), void *data, void *data2), const char *label, int listspan EINA_UNUSED)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = E_NEW(E_Widget_Data, 1);
   e_widget_data_set(obj, wd);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->gui.table = o;
   e_widget_sub_object_add(obj, o);

   o = e_widget_label_add(evas, label);
   e_widget_table_object_append(wd->gui.table, o, 0, 1, 2, 1, 1, 0, 1, 0);

   o = func_entry_add(e_win_evas_win_get(evas), &(wd->cur_entry), NULL, NULL, NULL);
   wd->gui.entry = o;
   e_widget_disabled_set(o, 1);
   e_widget_size_min_set(o, 100, 25);
   e_widget_table_object_append(wd->gui.table, o, 0, 2, 2, 1, 1, 0, 1, 0);

   o = e_widget_button_add(evas, _("Add"), "list-add", _button_cb_add,
                           wd, obj);
   wd->gui.add = o;
   e_widget_disabled_set(o, 1);
   e_widget_table_object_append(wd->gui.table, o, 0, 3, 1, 1, 1, 0, 1, 0);

   o = e_widget_button_add(evas, _("Remove"), "list-remove",
                           _button_cb_remove, wd, obj);
   wd->gui.remove = o;
   e_widget_disabled_set(o, 1);
   e_widget_table_object_append(wd->gui.table, o, 1, 3, 1, 1, 1, 0, 1, 0);

   o = e_widget_ilist_add(evas, 0, 0, NULL);
   wd->gui.list = o;
   e_widget_disabled_set(o, 1);
   e_widget_size_min_set(o, 80, 40);
   e_widget_on_change_hook_set(o, _list_cb_change, wd);
   e_widget_ilist_go(o);
   e_widget_table_object_append(wd->gui.table, o, 0, 4, 2, 1, 1, 1, 1, 1);

   o = e_widget_button_add(evas, _("Up"), "go-up",
                           _button_cb_up, wd, NULL);
   wd->gui.up = o;
   e_widget_disabled_set(o, 1);
   e_widget_table_object_append(wd->gui.table, o, 0, 5, 1, 1, 1, 0, 1, 0);

   o = e_widget_button_add(evas, _("Down"), "go-down",
                           _button_cb_down, wd, NULL);
   wd->gui.down = o;
   e_widget_disabled_set(o, 1);
   e_widget_table_object_append(wd->gui.table, o, 1, 5, 1, 1, 1, 0, 1, 0);

   e_widget_size_min_get(wd->gui.table, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   e_widget_resize_object_set(obj, wd->gui.table);

   return obj;
}

E_API int
e_widget_config_list_count(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   return e_widget_ilist_count(wd->gui.list);
}

E_API void
e_widget_config_list_clear(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_widget_ilist_clear(wd->gui.list);
   _list_select_num(wd, -1);
}

E_API const char *
e_widget_config_list_nth_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   return e_widget_ilist_nth_label_get(wd->gui.list, n);
}

E_API void
e_widget_config_list_append(Evas_Object *obj, const char *entry)
{
   E_Widget_Data *wd;
   int count;

   wd = e_widget_data_get(obj);
   e_widget_ilist_append(wd->gui.list, NULL, entry, NULL, NULL, NULL);
   e_widget_ilist_go(wd->gui.list);
   count = e_widget_ilist_count(wd->gui.list);
   e_widget_ilist_selected_set(wd->gui.list, count - 1);
}

E_API void
e_widget_config_list_object_append(Evas_Object *obj, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   wd = e_widget_data_get(obj);
   e_widget_table_object_append(wd->gui.table, sobj, col, row,
                                colspan, rowspan, fill_w, fill_h,
                                expand_w, expand_h);
   e_widget_size_min_get(wd->gui.table, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
}

/* Private Function defs */
static void
_list_cb_change(void *data, Evas_Object *obj EINA_UNUSED)
{
   E_Widget_Data *wd;
   int indx;

   if (!(wd = data)) return;

   indx = e_widget_ilist_selected_get(wd->gui.list);
   _list_select_num(wd, indx);
}

static void
_button_cb_add(void *data, void *obj)
{
   E_Widget_Data *wd;
   Evas_Object *o;

   o = obj;
   if (!o) return;
   if (!(wd = data)) return;
   if ((wd->cur_entry) && (wd->cur_entry[0]))
     {
        int i;

        /* If it already esists just select the existing one */
        for (i = 0; i < e_widget_ilist_count(wd->gui.list); i++)
          {
             const char *label;

             label = e_widget_ilist_nth_label_get(wd->gui.list, i);
             if ((label) && (!strcmp(label, wd->cur_entry)))
               {
                  e_widget_ilist_selected_set(wd->gui.list, i);
                  return;
               }
          }
        e_widget_config_list_append(o, wd->cur_entry);
        e_widget_entry_text_set(wd->gui.entry, "");
     }
}

static void
_button_cb_remove(void *data, void *obj EINA_UNUSED)
{
   E_Widget_Data *wd;
   int indx, count;

   if (!(wd = data)) return;

   indx = e_widget_ilist_selected_get(wd->gui.list);
   count = e_widget_ilist_count(wd->gui.list);

   e_widget_ilist_remove_num(wd->gui.list, indx);

   e_widget_ilist_go(wd->gui.list);
   e_widget_ilist_selected_set(wd->gui.list, indx);
   if (count == 1) _list_select_num(wd, -1);
}

static void
_button_cb_up(void *data, void *obj EINA_UNUSED)
{
   E_Widget_Data *wd;
   int idx_sel;
   const char *label_sel;
   const char *label_rep;

   if (!(wd = data)) return;

   idx_sel = e_widget_ilist_selected_get(wd->gui.list);

   label_sel = e_widget_ilist_nth_label_get(wd->gui.list, idx_sel);
   label_rep = e_widget_ilist_nth_label_get(wd->gui.list, idx_sel - 1);

   e_widget_ilist_nth_label_set(wd->gui.list, idx_sel - 1, label_sel);
   e_widget_ilist_nth_label_set(wd->gui.list, idx_sel, label_rep);

   e_widget_ilist_selected_set(wd->gui.list, idx_sel - 1);
}

static void
_button_cb_down(void *data, void *obj EINA_UNUSED)
{
   E_Widget_Data *wd;
   int idx_sel;
   const char *label_sel;
   const char *label_rep;

   if (!(wd = data)) return;

   idx_sel = e_widget_ilist_selected_get(wd->gui.list);

   label_sel = e_widget_ilist_nth_label_get(wd->gui.list, idx_sel);
   label_rep = e_widget_ilist_nth_label_get(wd->gui.list, idx_sel + 1);

   e_widget_ilist_nth_label_set(wd->gui.list, idx_sel + 1, label_sel);
   e_widget_ilist_nth_label_set(wd->gui.list, idx_sel, label_rep);

   e_widget_ilist_selected_set(wd->gui.list, idx_sel + 1);
}

static void
_list_select_num(E_Widget_Data *wd, int indx)
{
   int count;

   /* Disable selecting the list while we are disabled */
   if (indx >= 0 && !wd->cur_enabled) return;

   count = e_widget_ilist_count(wd->gui.list);

   if ((count == 0) || (indx < 0))
     e_widget_disabled_set(wd->gui.remove, 1);
   else
     e_widget_disabled_set(wd->gui.remove, 0);

   if ((count == 1) || (indx < 0))
     {
        e_widget_disabled_set(wd->gui.up, 1);
        e_widget_disabled_set(wd->gui.down, 1);
     }
   else if (indx == 0)
     {
        e_widget_disabled_set(wd->gui.up, 1);
        e_widget_disabled_set(wd->gui.down, 0);
     }
   else if ((indx + 1) == count)
     {
        e_widget_disabled_set(wd->gui.up, 0);
        e_widget_disabled_set(wd->gui.down, 1);
     }
   else
     {
        e_widget_disabled_set(wd->gui.up, 0);
        e_widget_disabled_set(wd->gui.down, 0);
     }
}

/* Callback Functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   E_FREE(wd->cur_entry);
   E_FREE(wd);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;

   wd->cur_enabled = !e_widget_disabled_get(obj);

   if (wd->cur_enabled)
     {
        e_widget_disabled_set(wd->gui.list, 0);
        e_widget_disabled_set(wd->gui.add, 0);
        e_widget_disabled_set(wd->gui.entry, 0);
     }
   else
     {
        _list_select_num(wd, -1);
        e_widget_disabled_set(wd->gui.list, 1);
        e_widget_disabled_set(wd->gui.entry, 1);
        e_widget_disabled_set(wd->gui.add, 1);
     }
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_entry, *o_inout;
   char       **text_location;
   void         (*func)(void *data, void *data2);
   char        *text;
   void        *data;
   void        *data2;
};

/* local subsystem functions */
static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_changed_cb(void *data, Evas_Object *obj, void *event_info);
static void _e_wid_keydown(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_movresz(void *data, Evas *e, Evas_Object *obj, void *event_info);

/* externally accessible functions */

/**
 * Creates a new entry widget
 *
 * @param evas the evas where to add the new entry widget
 * @param text_location the location where to store the text of the entry.
 * @param func DOCUMENT ME!
 * @param data  DOCUMENT ME!
 * @param data2 DOCUMENT ME!
 * The current value will be used to initialize the entry
 * @return Returns the new entry widget
 */
E_API Evas_Object *
e_widget_entry_add(Evas_Object *parent, char **text_location, void (*func)(void *data, void *data2), void *data, void *data2)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord minw, minh;
   Evas *evas;

   evas = evas_object_evas_get(parent);
   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);

   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);
   wd->text_location = text_location;

   o = elm_entry_add(parent);
   elm_object_text_set(o, "test");
   elm_entry_scrollable_set(o, 1);
   elm_entry_single_line_set(o, 1);
   elm_scroller_policy_set(o, ELM_SCROLLER_POLICY_AUTO, ELM_SCROLLER_POLICY_OFF);
   wd->o_entry = o;
   evas_object_size_hint_min_get(wd->o_entry, &minw, &minh);
   e_widget_size_min_set(obj, minw, minh);
   elm_object_text_set(o, NULL);

   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_show(o);

   evas_object_event_callback_add(o, EVAS_CALLBACK_KEY_DOWN, _e_wid_keydown, obj);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOVE, _e_wid_movresz, obj);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE, _e_wid_movresz, obj);

   o = evas_object_rectangle_add(evas);
   wd->o_inout = o;
   evas_object_repeat_events_set(o, EINA_TRUE);
   evas_object_color_set(o, 0, 0, 0, 0);
   e_widget_sub_object_add(obj, o);
   evas_object_smart_member_add(o, obj);
   evas_object_show(o);

   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);

   o = wd->o_entry;
   if ((text_location) && (*text_location))
     {
        char *tmp = elm_entry_utf8_to_markup(*text_location);

        if (tmp)
          {
             elm_object_text_set(o, tmp);
             free(tmp);
          }
        else elm_object_text_set(wd->o_entry, NULL);
     }

   wd->func = func;
   wd->data = data;
   wd->data2 = data2;
   evas_object_smart_callback_add(o, "changed", _e_wid_changed_cb, obj);

   return obj;
}

/**
 * Sets the text of the entry widget
 *
 * @param entry an entry widget
 * @param text the text to set
 */
E_API void
e_widget_entry_text_set(Evas_Object *entry, const char *text)
{
   E_Widget_Data *wd;
   char *tmp;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;
   tmp = elm_entry_utf8_to_markup(text);
   if (tmp)
     {
        elm_object_text_set(wd->o_entry, tmp);
        free(tmp);
     }
   else elm_object_text_set(wd->o_entry, NULL);
}

/**
 * Gets the text of the entry widget
 *
 * @param entry an entry widget
 * @return Returns the text of the entry widget
 */
E_API const char *
e_widget_entry_text_get(Evas_Object *entry)
{
   E_Widget_Data *wd;
   char *tmp;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return NULL;
   tmp = elm_entry_markup_to_utf8(elm_object_text_get(wd->o_entry));
   if (tmp)
     {
        if (wd->text) free(wd->text);
        wd->text = tmp;
        return wd->text;
     }
   if (wd->text) free(wd->text);
   wd->text = NULL;
   return NULL;
}

/**
 * Clears the entry widget
 *
 * @param entry an entry widget
 */
E_API void
e_widget_entry_clear(Evas_Object *entry)
{
   E_Widget_Data *wd;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;
   elm_object_text_set(wd->o_entry, NULL);
}

/**
 * Sets whether or not the entry widget is in password mode. In password mode,
 * the entry displays '*' instead of the characters
 *
 * @param entry an entry widget
 * @param password_mode 1 to turn on password mode, 0 to turn it off
 */
E_API void
e_widget_entry_password_set(Evas_Object *entry, int password_mode)
{
   E_Widget_Data *wd;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;
   elm_entry_password_set(wd->o_entry, !!password_mode);
}

/**
 * Sets whether or not the entry widget is user-editable. This still
 * allows copying and selecting, just no inserting or deleting of text.
 *
 * @param entry an entry widget
 * @param readonly_mode 1 to enable read-only mode, 0 to turn it off
 */
E_API void
e_widget_entry_readonly_set(Evas_Object *entry, int readonly_mode)
{
   E_Widget_Data *wd;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;

   elm_entry_editable_set(wd->o_entry, !readonly_mode);
}

/**
 * Selects the content of the entry.
 *
 * @param entry an entry widget
 */
E_API void
e_widget_entry_select_all(Evas_Object *entry)
{
   E_Widget_Data *wd;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;
   elm_entry_select_all(wd->o_entry);
}

E_API void
e_widget_entry_select_set(Evas_Object *entry, int start, int end)
{
   E_Widget_Data *wd;

   if (!(entry) || (!(wd = e_widget_data_get(entry))))
     return;
   elm_entry_select_region_set(wd->o_entry, start, end);
}

/* Private functions */

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!(obj) || (!(wd = e_widget_data_get(obj))))
     return;
   if (wd->text) free(wd->text);
   evas_object_del(wd->o_entry);
   evas_object_del(wd->o_inout);
   wd->o_entry = NULL;
   wd->o_inout = NULL;
   wd->text = NULL;
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!(obj) || (!(wd = e_widget_data_get(obj))))
     return;

   elm_object_focus_set(wd->o_entry, !!e_widget_focus_get(obj));
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!(obj) || (!(wd = e_widget_data_get(obj))))
     return;

   elm_object_disabled_set(wd->o_entry, !!e_widget_disabled_get(obj));
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_e_wid_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Object *entry;
   E_Widget_Data *wd;
   char *tmp;

   if (!(entry = data) || (!(wd = e_widget_data_get(entry))))
     return;

   tmp = elm_entry_markup_to_utf8(elm_object_text_get(wd->o_entry));
   if (tmp)
     {
        if (wd->text) free(wd->text);
        wd->text = tmp;
     }
   else
     {
        if (wd->text) free(wd->text);
        wd->text = NULL;
     }
   if (wd->text_location)
     {
        free(*wd->text_location);
        *wd->text_location = wd->text ? strdup(wd->text) : NULL;
     }
   e_widget_change(data);

   if (wd->func) wd->func(wd->data, wd->data2);
}

static void
_e_wid_keydown(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   evas_object_smart_callback_call(data, "key_down", event_info);
}

static void
_e_wid_movresz(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h;

   if (!(data) || (!(wd = e_widget_data_get(data))))
     return;
   evas_object_geometry_get(obj, &x, &y, &w, &h);
   if (wd->o_inout)
     {
        evas_object_move(wd->o_inout, x, y);
        evas_object_resize(wd->o_inout, w, h);
     }
}

#include "e.h"
#include "e_fm_device.h"
#include <sys/statvfs.h>

#define FILEPREVIEW_TEXT_PREVIEW_SIZE 8192

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object  *obj;
   Evas_Object  *o_preview_list;
   Evas_Object  *o_preview_preview_table;
   Evas_Object  *o_preview_properties_table;
   Evas_Object  *o_preview_scroll;
   Evas_Object  *o_preview_extra;
   Evas_Object  *o_preview_extra_entry;
   Evas_Object  *o_preview_size;
   Evas_Object  *o_preview_size_entry;
   Evas_Object  *o_preview_resolution;
   Evas_Object  *o_preview_resolution_entry;
   Evas_Object  *o_preview_owner;
   Evas_Object  *o_preview_owner_entry;
   Evas_Object  *o_preview_perms;
   Evas_Object  *o_preview_perms_entry;
   Evas_Object  *o_preview_time;
   Evas_Object  *o_preview_time_entry;
   Evas_Object  *o_preview_preview;
   Evas_Object  *o_preview_scrollframe;
   Evas_Object  *o_preview_artwork;

   Evas_Coord    preview_w, preview_h;
   int           w, h;

   Eio_File     *preview_text_file_thread;
   Eina_File    *preview_text_file;

   Eio_Monitor  *monitor;
   Eina_List    *handlers;
   char         *preview_extra_text;
   char         *preview_size_text;
   char         *preview_owner_text;
   char         *preview_perms_text;
   char         *preview_time_text;
   const char   *path;
   const char   *mime;
   double        vid_pct;

   Eina_Bool     mime_icon E_BITFIELD;
   Eina_Bool     is_dir E_BITFIELD;
   Eina_Bool     is_txt E_BITFIELD;
   Eina_Bool     is_font E_BITFIELD;
   Eina_Bool     prev_is_fm E_BITFIELD;
   Eina_Bool     prev_is_txt E_BITFIELD;
   Eina_Bool     prev_is_font E_BITFIELD;
   Eina_Bool     prev_is_video E_BITFIELD;
   Eina_Bool     clamp_video E_BITFIELD;
   Eina_Bool     delete_me E_BITFIELD;
   Eina_Bool     preview_text_file_next E_BITFIELD;
   Eina_Bool     vid_sized E_BITFIELD;
};

static void  _e_wid_fprev_preview_update(void *data, Evas_Object *obj, void *event_info);
static void  _e_wid_fprev_preview_file(E_Widget_Data *wd);
static char *_e_wid_file_size_get(off_t st_size);
static char *_e_wid_file_user_get(uid_t st_uid);
static char *_e_wid_file_perms_get(mode_t st_mode, uid_t st_uid, gid_t gid);
static char *_e_wid_file_time_get(time_t st_modtime);
static void  _e_wid_fprev_img_update(E_Widget_Data *wd, const char *path, const char *key);
static void  _e_wid_del_hook(Evas_Object *obj);
static void  _e_wid_fprev_preview_reset(E_Widget_Data *wd);
static void  _e_wid_fprev_preview_txt(E_Widget_Data *wd);
static void  _e_wid_fprev_preview_font(E_Widget_Data *wd);
static void  _e_wid_fprev_preview_fm(E_Widget_Data *wd);

static void
_e_wid_fprev_preview_update(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd = data;
   Evas_Object *o;
   char buf[256];
   int iw = 0, ih = 0;

   if (!wd->o_preview_extra) return;
   if (evas_object_image_extension_can_load_get(wd->path))
     {
        o = evas_object_image_add(evas_object_evas_get(obj));
        evas_object_image_file_set(o, wd->path, NULL);
        evas_object_image_size_get(o, &iw, &ih);
        evas_object_del(o);
     }
   if ((iw > 0) && (ih > 0))
     {
        e_widget_label_text_set(wd->o_preview_extra, _("Resolution:"));
        snprintf(buf, sizeof(buf), _("%i×%i"), iw, ih);
        e_widget_entry_text_set(wd->o_preview_extra_entry, buf);
     }
   else if (wd->mime)
     {
        if (wd->mime_icon)
          {
             e_widget_label_text_set(wd->o_preview_extra, _("Mime-type:"));
             e_widget_entry_text_set(wd->o_preview_extra_entry, wd->mime);
          }
        else
          {
             const char *mime;
             Efreet_Desktop *ed = NULL;
             unsigned int size;
             char group[1024];
             Eina_Bool edj;

             wd->mime_icon = EINA_TRUE;
             size = wd->w;
             mime = e_util_mime_icon_get(wd->mime, size);
             if (mime)
               {
                  edj = eina_str_has_extension(mime, "edj");
                  if (edj)
                    snprintf(group, sizeof(group), "e/icons/fileman/mime/%s", wd->mime);
                  _e_wid_fprev_img_update(wd, mime, edj ? group : NULL);
                  return;
               }
             if (eina_str_has_extension(wd->path, "desktop"))
               {
                  ed = efreet_desktop_new(wd->path);
                  if (ed)
                    mime = efreet_icon_path_find(e_config->icon_theme, ed->icon, size);
               }
             if (!mime)
               mime = efreet_icon_path_find(e_config->icon_theme, "unknown", size);
             if (!mime)
               mime = efreet_icon_path_find(e_config->icon_theme, "text/plain", size);
             _e_wid_fprev_img_update(wd, mime, NULL);
             if (ed) efreet_desktop_free(ed);
          }
     }
   e_widget_table_object_repack(wd->o_preview_preview_table,
                                wd->o_preview_preview,
                                0, 0, 1, 1, 0, 0, 1, 1);
}

static void
_e_wid_fprev_img_update(E_Widget_Data *wd, const char *path, const char *key)
{
   if (!path) return;
   if (wd->is_dir || wd->is_txt || wd->is_font) return;
   evas_object_smart_callback_del_full(wd->o_preview_preview, "preview_update", _e_wid_fprev_preview_update, wd);
   if (eina_str_has_extension(path, ".gif"))
     {
        e_widget_preview_file_set(wd->o_preview_preview, path, key);
        _e_wid_fprev_preview_update(wd, wd->o_preview_preview, NULL);
     }
   else if (eina_str_has_extension(path, ".edj"))
     {
        if (key)
          e_widget_preview_edje_set(wd->o_preview_preview, path, key);
        else
          {
             Eina_List *l;
             Eina_Stringshare *str;
             Eina_Bool set = EINA_FALSE;

             l = edje_file_collection_list(path);
             if (eina_list_count(l) < 2)
               {
                  e_widget_preview_thumb_set(wd->o_preview_preview, path, eina_list_data_get(l), wd->w, wd->h);
                  set = EINA_TRUE;
                  edje_file_collection_list_free(l), l = NULL;
               }
             EINA_LIST_FREE(l, str)
               {
                  Eina_Bool use = EINA_FALSE;

                  if (!strcmp(str, "icon")) use = EINA_TRUE;
                  else if (!strcmp(str, "e/desktop/background")) use = EINA_TRUE;
                  else if (!strcmp(str, "e/init/splash")) use = EINA_TRUE;

                  if (use)
                    {
                       e_widget_preview_thumb_set(wd->o_preview_preview, path, str, wd->w, wd->h);
                       set = EINA_TRUE;
                       break;
                    }
                  eina_stringshare_del(str);
               }
             if (!set)
               {
                  e_widget_preview_edje_set(wd->o_preview_preview, path, NULL);
                  _e_wid_fprev_preview_update(wd, wd->o_preview_preview, NULL);
               }
             if (l) edje_file_collection_list_free(l);
          }
     }
   else
     {
        e_widget_preview_thumb_set(wd->o_preview_preview, path, key, wd->w, wd->h);
        evas_object_smart_callback_add(wd->o_preview_preview, "preview_update", _e_wid_fprev_preview_update, wd);
     }
}

static void
_e_wid_fprev_clear_widgets(E_Widget_Data *wd)
{
#define CLRWID(xx) \
  do { if (wd->xx) { evas_object_del(wd->xx); wd->xx = NULL; } } while (0)

   CLRWID(o_preview_extra);
   CLRWID(o_preview_extra_entry);
   CLRWID(o_preview_size);
   CLRWID(o_preview_size_entry);
   CLRWID(o_preview_resolution);
   CLRWID(o_preview_resolution_entry);
   CLRWID(o_preview_owner);
   CLRWID(o_preview_owner_entry);
   CLRWID(o_preview_perms);
   CLRWID(o_preview_perms_entry);
   CLRWID(o_preview_time);
   CLRWID(o_preview_time_entry);
   CLRWID(o_preview_preview);
   CLRWID(o_preview_scrollframe);
   CLRWID(o_preview_artwork);
   CLRWID(o_preview_scroll);
   CLRWID(o_preview_preview_table);
   CLRWID(o_preview_properties_table);
   wd->is_dir = wd->is_txt = wd->is_font = wd->prev_is_fm = wd->prev_is_video = EINA_FALSE;
   wd->vid_pct = 0;

   if (wd->preview_text_file_thread) eio_file_cancel(wd->preview_text_file_thread);
}

static void
_fprev_video_size(E_Widget_Data *wd, Evas_Object *obj)
{
   double ratio;
   int iw, ih;
   Evas_Coord w, h, mw, mh, ow, oh;

   if (wd->vid_sized) return;
   evas_object_geometry_get(wd->o_preview_properties_table, NULL, NULL, &w, &h);
   if (w < 10) return;
   evas_object_geometry_get(wd->o_preview_preview, NULL, NULL, &ow, &oh);
   wd->vid_sized = 1;
   w -= 4;
   emotion_object_size_get(obj, &iw, &ih);
   ratio = emotion_object_ratio_get(obj);
   if (ratio > 0.0) iw = (ih * ratio) + 0.5;
   if (iw < 1) iw = 1;
   if (ih < 1) ih = 1;

   h = (w * ih) / iw;
   e_widget_size_min_set(wd->o_preview_preview, w, h);
   e_widget_table_object_repack(wd->o_preview_properties_table,
                                wd->o_preview_preview, 0, 0, 2, 2, 1, 1, 1, 1);
   e_widget_list_object_repack(wd->o_preview_list,
                               wd->o_preview_properties_table,
                               1, 1, 0.5);
   e_widget_size_min_get(wd->o_preview_list, &mw, &mh);
   e_widget_size_min_set(wd->obj, mw, mh);
}

static void
_e_wid_fprev_preview_video_position(E_Widget_Data *wd, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   double t, tot;

   if (!wd->o_preview_time) return;
   tot = emotion_object_play_length_get(obj);
   if (!EINA_DBL_NONZERO(tot)) return;
   wd->vid_pct = t = (emotion_object_position_get(obj) * 100.0) / emotion_object_play_length_get(obj);
   e_widget_slider_value_double_set(wd->o_preview_time, t);
   _fprev_video_size(wd, obj);
}

static void
_e_wid_fprev_preview_video_opened(E_Widget_Data *wd, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   e_widget_entry_text_set(wd->o_preview_extra_entry, e_util_time_str_get(emotion_object_play_length_get(obj)));
   _fprev_video_size(wd, obj);
}

static void
_e_wid_fprev_preview_video_resize(E_Widget_Data *wd, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   int w, h;
   char buf[128];

   _fprev_video_size(wd, obj);
   emotion_object_size_get(obj, &w, &h);

   snprintf(buf, sizeof(buf), "%dx%d", w, h);
   e_widget_entry_text_set(wd->o_preview_resolution_entry, buf);
   if (!wd->clamp_video) return;
   evas_object_size_hint_max_set(wd->o_preview_preview, w, h);
}

static void
_e_wid_fprev_preview_video_change(void *data, Evas_Object *obj)
{
   double pos, tot, t;

   tot = emotion_object_play_length_get(data);
   t = emotion_object_position_get(data);
   e_widget_slider_value_double_get(obj, &pos);
   pos = (pos * tot) / 100.0;
   t = pos - t;
   if (t < 0.0) t = -t;
   if (t > 0.25) emotion_object_position_set(data, pos);
}

static void
_e_wid_fprev_preview_video_widgets(E_Widget_Data *wd)
{
   Evas *evas = evas_object_evas_get(wd->obj);
   Evas_Object *table, *o, *em, *win;
   const char *mime, *path;
   char *ext;
   Eina_Bool audio_artwork;
   int mw, mh, y = 3;

   win = e_win_evas_win_get(evas);
   _e_wid_fprev_clear_widgets(wd);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_preview_properties_table = o;

#define WIDROW(lab, labob, entob, entw)                                           \
  do {                                                                            \
       o = e_widget_label_add(evas, lab);                                         \
       wd->labob = o;                                                             \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          wd->labob,                              \
                                          0, y, 1, 1, 0, 1, 0, 0, 1.0, 0.0);      \
       o = evas_object_rectangle_add(evas);                                       \
       evas_object_size_hint_min_set(o, entw * e_scale, 1);                       \
       evas_object_color_set(o, 0, 0, 0, 0);                                      \
       evas_object_pass_events_set(o, EINA_TRUE);                                 \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          o,                                      \
                                          1, y, 1, 1, 1, 1, 1, 0, 0.0, 0.0);      \
       o = e_widget_entry_add(win, &(wd->preview_extra_text), NULL, NULL, NULL);  \
       e_widget_entry_readonly_set(o, 1);                                         \
       wd->entob = o;                                                             \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          wd->entob,                              \
                                          1, y, 1, 1, 1, 1, 1, 0, 0.0, 0.0);      \
       y++;                                                                       \
    } while (0)

   table = o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   e_widget_size_min_set(o, wd->w, wd->h);
   e_widget_table_object_append(wd->o_preview_properties_table,
                                o, 0, 0, 2, 2, 1, 1, 1, 1);

   ext = strrchr(wd->path, '.');
   if ((ext) &&
       ((!strcasecmp(ext, ".mp3")) ||
        (!strcasecmp(ext, ".m4a")) ||
        (!strcasecmp(ext, ".ogg")) ||
        (!strcasecmp(ext, ".aac")) ||
        (!strcasecmp(ext, ".flac"))
      )) audio_artwork = EINA_TRUE;
   else
     audio_artwork = EINA_FALSE;

   em = o = emotion_object_add(evas);
   emotion_object_file_set(o, wd->path);
   emotion_object_play_set(o, EINA_TRUE);
   emotion_object_init(o, "gstreamer1");

   mime = efreet_mime_type_get(wd->path);
   if (mime)
     {
        path = efreet_mime_type_icon_get(mime, e_config->icon_theme, 128);
        if (path && !audio_artwork)
          {
             wd->o_preview_artwork = elm_icon_add(o);
             elm_image_file_set(wd->o_preview_artwork, path, NULL);
          }
        else
          {
             wd->o_preview_artwork = emotion_file_meta_artwork_get(o, wd->path, EMOTION_ARTWORK_PREVIEW_IMAGE);
             if (!wd->o_preview_artwork)
               wd->o_preview_artwork = emotion_file_meta_artwork_get(o, wd->path, EMOTION_ARTWORK_IMAGE);
          }
     }

   if (wd->o_preview_artwork)
     {
        if (mime && !strncmp(mime, "audio/",6))
          e_widget_size_min_set(table, 192, 192);
	evas_object_resize(wd->o_preview_artwork, 256, 256);
	evas_object_image_fill_set(wd->o_preview_artwork, 0, 0, 256, 256);
	evas_object_size_hint_max_set(wd->o_preview_artwork, 256, 256);
        e_widget_table_object_append(wd->o_preview_properties_table,
                                     wd->o_preview_artwork, 0, 0, 2, 2, 1, 1, 1, 1);
        evas_object_show(wd->o_preview_artwork);
     }

   wd->o_preview_preview = e_widget_image_add_from_object(evas, o, 4, 4);
   e_widget_table_object_append(wd->o_preview_properties_table,
                                wd->o_preview_preview, 0, 0, 2, 2, 1, 1, 1, 1);

   evas_object_smart_callback_add(o, "frame_resize", (Evas_Smart_Cb)_e_wid_fprev_preview_video_resize, wd);
   evas_object_smart_callback_add(o, "open_done", (Evas_Smart_Cb)_e_wid_fprev_preview_video_opened, wd);
   evas_object_smart_callback_add(o, "frame_decode", (Evas_Smart_Cb)_e_wid_fprev_preview_video_position, wd);

   o = e_widget_slider_add(evas, 1, 0, _("%3.1f%%"), 0, 100, 0.5, 0, &wd->vid_pct, NULL, 40);
   wd->o_preview_time = o;
   e_widget_table_object_align_append(wd->o_preview_properties_table,
                                      wd->o_preview_time,
                                      0, 2, 2, 1, 1, 0, 1, 0, 0.5, 0.5);
   e_widget_on_change_hook_set(o, _e_wid_fprev_preview_video_change, em);
   WIDROW(_("Length:"), o_preview_extra, o_preview_extra_entry, 40);
   WIDROW(_("Size:"), o_preview_size, o_preview_size_entry, 40);
   WIDROW(_("Resolution:"), o_preview_resolution, o_preview_resolution_entry, 40);
   /* FIXME: other infos? */

   e_widget_list_object_append(wd->o_preview_list,
                               wd->o_preview_properties_table,
                               1, 1, 0.5);

   e_widget_size_min_get(wd->o_preview_list, &mw, &mh);
   e_widget_size_min_set(wd->obj, mw, mh);
   evas_object_show(wd->o_preview_preview_table);
   evas_object_show(wd->o_preview_extra);
   evas_object_show(wd->o_preview_extra_entry);
   evas_object_show(wd->o_preview_size);
   evas_object_show(wd->o_preview_size_entry);
   evas_object_show(wd->o_preview_resolution);
   evas_object_show(wd->o_preview_resolution_entry);
   evas_object_show(wd->o_preview_owner);
   evas_object_show(wd->o_preview_owner_entry);
   evas_object_show(wd->o_preview_perms);
   evas_object_show(wd->o_preview_perms_entry);
   evas_object_show(wd->o_preview_time);
   evas_object_show(wd->o_preview_time_entry);
   evas_object_show(wd->o_preview_properties_table);
   wd->prev_is_video =  EINA_TRUE;
#undef WIDROW
}

static void
_e_wid_fprev_preview_fs_widgets(E_Widget_Data *wd, Eina_Bool mount_point)
{
   Evas *evas = evas_object_evas_get(wd->obj);
   Evas_Object *o, *win;
   int mw, mh, y = 0;

   win = e_win_evas_win_get(evas);
   _e_wid_fprev_clear_widgets(wd);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_preview_properties_table = o;

#define WIDROW(lab, labob, entob, entw)                                           \
  do {                                                                            \
       o = e_widget_label_add(evas, lab);                                         \
       wd->labob = o;                                                             \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          wd->labob,                              \
                                          0, y, 1, 1, 0, 1, 0, 0, 1.0, 0.0);      \
       o = evas_object_rectangle_add(evas);                                       \
       evas_object_size_hint_min_set(o, entw * e_scale, 1);                       \
       evas_object_color_set(o, 0, 0, 0, 0);                                      \
       evas_object_pass_events_set(o, EINA_TRUE);                                 \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          o,                                      \
                                          1, y, 1, 1, 1, 1, 1, 0, 0.0, 0.0);      \
       o = e_widget_entry_add(win, &(wd->preview_extra_text), NULL, NULL, NULL);  \
       e_widget_entry_readonly_set(o, 1);                                         \
       wd->entob = o;                                                             \
       e_widget_table_object_align_append(wd->o_preview_properties_table,         \
                                          wd->entob,                              \
                                          1, y, 1, 1, 1, 1, 1, 0, 0.0, 0.0);      \
       y++;                                                                       \
    } while (0)

   WIDROW(_("Used:"), o_preview_extra, o_preview_extra_entry, 100);
   WIDROW(_("Size:"), o_preview_size, o_preview_size_entry, 100);
   WIDROW(_("Reserved:"), o_preview_owner, o_preview_owner_entry, 100);
   WIDROW(_("Mount status:"), o_preview_perms, o_preview_perms_entry, 100);
   if (mount_point)
     WIDROW(_("Type:"), o_preview_time, o_preview_time_entry, 100);

   e_widget_list_object_append(wd->o_preview_list,
                               wd->o_preview_properties_table,
                               1, 1, 0.5);

   e_widget_size_min_get(wd->o_preview_list, &mw, &mh);
   e_widget_size_min_set(wd->obj, mw, mh);
   evas_object_show(wd->o_preview_preview_table);
   evas_object_show(wd->o_preview_extra);
   evas_object_show(wd->o_preview_extra_entry);
   evas_object_show(wd->o_preview_size);
   evas_object_show(wd->o_preview_size_entry);
   evas_object_show(wd->o_preview_owner);
   evas_object_show(wd->o_preview_owner_entry);
   evas_object_show(wd->o_preview_perms);
   evas_object_show(wd->o_preview_perms_entry);
   evas_object_show(wd->o_preview_time);
   evas_object_show(wd->o_preview_time_entry);
   evas_object_show(wd->o_preview_properties_table);
}

static void
_e_wid_fprev_preview_file_widgets(E_Widget_Data *wd, Eina_Bool dir, Eina_Bool txt, Eina_Bool font)
{
   Evas *evas = evas_object_evas_get(wd->obj);
   Evas_Object *o, *win;
   int mw, mh, y = 0;

   win = e_win_evas_win_get(evas);
   _e_wid_fprev_clear_widgets(wd);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_preview_preview_table = o;
   e_widget_size_min_set(o, 32, 32);

   e_widget_list_object_append(wd->o_preview_list,
                               wd->o_preview_preview_table,
                               0, 1, 0.5);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_preview_properties_table = o;
   wd->is_dir = dir;
   wd->is_txt = txt;
   wd->is_font = font;

   if (!dir)
     {
        if (!txt)
          WIDROW(_("Resolution:"), o_preview_extra, o_preview_extra_entry, 100);
        WIDROW(_("Size:"), o_preview_size, o_preview_size_entry, 100);
     }
   WIDROW(_("Owner:"), o_preview_owner, o_preview_owner_entry, 100);
   WIDROW(_("Permissions:"), o_preview_perms, o_preview_perms_entry, 100);
   WIDROW(_("Modified:"), o_preview_time, o_preview_time_entry, 100);

   e_widget_list_object_append(wd->o_preview_list,
                               wd->o_preview_properties_table,
                               1, 1, 0.5);

   e_widget_size_min_get(wd->o_preview_list, &mw, &mh);
   e_widget_size_min_set(wd->obj, mw, mh);
   evas_object_show(wd->o_preview_preview_table);
   evas_object_show(wd->o_preview_extra);
   evas_object_show(wd->o_preview_extra_entry);
   evas_object_show(wd->o_preview_size);
   evas_object_show(wd->o_preview_size_entry);
   evas_object_show(wd->o_preview_owner);
   evas_object_show(wd->o_preview_owner_entry);
   evas_object_show(wd->o_preview_perms);
   evas_object_show(wd->o_preview_perms_entry);
   evas_object_show(wd->o_preview_time);
   evas_object_show(wd->o_preview_time_entry);
   evas_object_show(wd->o_preview_properties_table);
}

static void
_e_wid_fprev_preview_file(E_Widget_Data *wd)
{
   char *size, *owner, *perms, *mtime;
   struct stat st;
   Eina_Bool is_fs = EINA_FALSE;

   if (stat(wd->path, &st) < 0) return;
   // if its a desktop file treat is specially
   if (((wd->mime) && (!strcasecmp(wd->mime, "application/x-desktop"))) ||
       (eina_str_has_extension(wd->path, "desktop")))
     {
        Efreet_Desktop *desktop;
        const char *type, *file;

        // load it and if its a special removable or mount point
        // desktop file for e, then we want to do something special
        desktop = efreet_desktop_new(wd->path);
        if ((desktop) && (desktop->url) &&
            (desktop->type == EFREET_DESKTOP_TYPE_LINK) &&
            (desktop->x) &&
            ((type = eina_hash_find(desktop->x, "X-Enlightenment-Type"))) &&
            ((!strcmp(type, "Mount")) || (!strcmp(type, "Removable"))))
          {
             // find the "mountpoint" or "link" - it's stringshared
             if ((file = e_fm2_desktop_url_eval(desktop->url)))
               {
                  struct statvfs stfs;
                  Eina_Bool ok = EINA_FALSE;

                  memset(&stfs, 0, sizeof(stfs));
                  if (statvfs(file, &stfs) == 0) ok = EINA_TRUE;
                  else
                    {
                       E_Volume *v;

                       v = e_fm2_device_volume_find(file);
                       if (v && v->mount_point)
                         {
                            if (statvfs(v->mount_point, &stfs) == 0)
                              {
                                 ok = EINA_TRUE;
                                 eina_stringshare_replace(&file, v->mount_point);
                              }
                         }
                    }

                  if (ok)
                    {
                       unsigned long fragsz;
                       unsigned long long blknum, blkused, blkres;
                       off_t bsize, bused, bres;
                       Eina_Bool rdonly = EINA_FALSE;
                       Eina_Bool f_is_dir = EINA_FALSE;
                       Eina_Bool f_is_dev = EINA_FALSE;
                       char buf[PATH_MAX], mdev[4096], mpoint[4096], fstype[4096];
                       FILE *f;

                       fstype[0] = 0;
                       mpoint[0] = 0;
                       mdev[0] = 0;
                       f = fopen("/etc/mtab", "r");
                       if (f)
                         {
                            while (fgets(buf, sizeof(buf), f))
                              {
                                 if (sscanf(buf, "%4000s %4000s %4000s %*s",
                                            mdev, mpoint, fstype) == 3)
                                   {
                                      if (!strcmp(mdev, file))
                                        {
                                           f_is_dev = EINA_TRUE;
                                           break;
                                        }
                                      if (!strcmp(mpoint, file))
                                        {
                                           f_is_dir = EINA_TRUE;
                                           break;
                                        }
                                   }
                                 fstype[0] = 0;
                                 mpoint[0] = 0;
                                 mdev[0] = 0;
                              }
                            fclose(f);
                         }
                       if ((f_is_dev) && (!f_is_dir))
                         {
                            if (statvfs(mpoint, &stfs) != 0) ok = EINA_FALSE;
                         }

                       fragsz = stfs.f_frsize;
                       blknum = stfs.f_blocks;
                       blkused = stfs.f_blocks - stfs.f_bfree;
                       blkres = stfs.f_bfree - stfs.f_bavail;

                       if ((ok) && (mpoint[0]) && (blknum > blkres))
                         {
                            is_fs = EINA_TRUE;

                            bres = blkres * fragsz;
                            bsize = blknum * fragsz;
                            bused = bsize * ((double)blkused / blknum);
#ifdef ST_RDONLY
                            if (stfs.f_flag & ST_RDONLY) rdonly = EINA_TRUE;
#endif
                            _e_wid_fprev_preview_fs_widgets(wd, EINA_TRUE);

                            //-------------------
                            size = e_util_size_string_get(bused);
                            e_widget_entry_text_set(wd->o_preview_extra_entry, size);
                            free(size);
                            //-------------------
                            size = e_util_size_string_get(bsize);
                            e_widget_entry_text_set(wd->o_preview_size_entry, size);
                            free(size);
                            //-------------------
                            size = e_util_size_string_get(bres);
                            e_widget_entry_text_set(wd->o_preview_owner_entry, size);
                            free(size);
                            //-------------------
                            if (mpoint[0])
                              {
                                 if (rdonly)
                                   e_widget_entry_text_set(wd->o_preview_perms_entry, _("Read Only"));
                                 else
                                   e_widget_entry_text_set(wd->o_preview_perms_entry, _("Read-Write"));
                              }
                            else
                              e_widget_entry_text_set(wd->o_preview_perms_entry, _("Unmounted"));
                            //-------------------
                            e_widget_entry_text_set(wd->o_preview_time_entry, fstype);
                         }
                    }
                  if (!is_fs)
                    {
                       _e_wid_fprev_preview_fs_widgets(wd, EINA_FALSE);
                       e_widget_entry_text_set(wd->o_preview_extra_entry, _("Unknown"));
                       e_widget_entry_text_set(wd->o_preview_size_entry, _("Unknown"));
                       e_widget_entry_text_set(wd->o_preview_owner_entry, _("Unknown"));
                       e_widget_entry_text_set(wd->o_preview_perms_entry, _("Unmounted"));
                       is_fs = EINA_TRUE;
                    }
                  eina_stringshare_del(file);
               }
          }
        else if ((desktop) && (desktop->url) &&
                 (desktop->type == EFREET_DESKTOP_TYPE_LINK))
          {
             const char *url;
             /* we don't know what the desktop points to here,
              * so we'll just run recursively until we get a real file
              */
             url = e_fm2_desktop_url_eval(desktop->url);
             if (url)
               {
                  eina_stringshare_del(wd->path);
                  wd->path = url;
                  _e_wid_fprev_preview_file(wd);
                  return;
               }
          }
        if (desktop) efreet_desktop_free(desktop);
     }
   else if (wd->mime && (emotion_object_extension_may_play_get(wd->path)))
     {
        size_t sz;

        _e_wid_fprev_preview_video_widgets(wd);
        e_widget_entry_text_set(wd->o_preview_extra_entry, _("Unknown"));
        sz = ecore_file_size(wd->path);
        if (sz)
          {
             char *s;

             s = e_util_size_string_get(sz);
             e_widget_entry_text_set(wd->o_preview_size_entry, s);
             free(s);
          }
        else
          e_widget_entry_text_set(wd->o_preview_size_entry, _("Unknown"));
        is_fs = EINA_TRUE;
     }
   if (is_fs) return;

   wd->mime_icon = EINA_FALSE;
   size = _e_wid_file_size_get(st.st_size);
   owner = _e_wid_file_user_get(st.st_uid);
   perms = _e_wid_file_perms_get(st.st_mode, st.st_uid, st.st_gid);
   mtime = _e_wid_file_time_get(st.st_mtime);
   wd->is_dir = S_ISDIR(st.st_mode);
   if (wd->mime && st.st_size && (!wd->is_dir))
     {
        wd->is_txt = !strncmp(wd->mime, "text/", 5);
        if (!wd->is_txt)
          wd->is_txt = !strcmp(wd->mime, "application/x-shellscript");
        if (!wd->is_txt)
          {
             wd->is_font = !strcmp(wd->mime, "application/x-font");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-ttf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-type1");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-afm");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-snf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-otf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/vnd.font-fontforge-sfd");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-linux-psf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-ttx");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-speedo");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-pcf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-font-bdf");
             if (!wd->is_font)
               wd->is_font = !strcmp(wd->mime, "application/x-cisco-vpn-settings");
          }
     }
   _e_wid_fprev_preview_file_widgets(wd, wd->is_dir, wd->is_txt, wd->is_font);

   _e_wid_fprev_preview_reset(wd);
   _e_wid_fprev_preview_fm(wd);
   _e_wid_fprev_preview_txt(wd);
   _e_wid_fprev_preview_font(wd);
   _e_wid_fprev_img_update(wd, wd->path, NULL);

   if (!wd->is_dir)
     {
        if (!wd->is_txt)
          e_widget_entry_text_set(wd->o_preview_extra_entry, "");
        e_widget_entry_text_set(wd->o_preview_size_entry, size);
     }
   e_widget_entry_text_set(wd->o_preview_owner_entry, owner);
   e_widget_entry_text_set(wd->o_preview_perms_entry, perms);
   e_widget_entry_text_set(wd->o_preview_time_entry, mtime);

   free(size);
   free(owner);
   free(perms);
   free(mtime);
}

static char *
_e_wid_file_size_get(off_t st_size)
{
   return e_util_size_string_get(st_size);
}

static char *
_e_wid_file_user_get(uid_t st_uid)
{
   char name[4096];
   struct passwd *pwd;

   if (getuid() == st_uid)
     snprintf(name, sizeof(name), _("You"));
   else
     {
        pwd = getpwuid(st_uid);
        if (pwd)
          snprintf(name, sizeof(name), "%s", pwd->pw_name);
        else
          snprintf(name, sizeof(name), "%-8d", (int)st_uid);
     }
   return strdup(name);
}

static char *
_e_wid_file_perms_get(mode_t st_mode, uid_t st_uid, gid_t st_gid)
{
   char perms[256];
   int acc = 0;
   int owner = 0;
   int group = 0;
   int user_read = 0;
   int user_write = 0;
   int group_read = 0;
   int group_write = 0;
   int other_read = 0;
   int other_write = 0;

   if (getuid() == st_uid)
     owner = 1;
   if (getgid() == st_gid)
     group = 1;

   if ((S_IRUSR & st_mode) == S_IRUSR)
     user_read = 1;
   if ((S_IWUSR & st_mode) == S_IWUSR)
     user_write = 1;

   if ((S_IRGRP & st_mode) == S_IRGRP)
     group_read = 1;
   if ((S_IWGRP & st_mode) == S_IWGRP)
     group_write = 1;

   if ((S_IROTH & st_mode) == S_IROTH)
     other_read = 1;
   if ((S_IWOTH & st_mode) == S_IWOTH)
     other_write = 1;

   if (owner)
     {
        if ((!user_read) && (!user_write))
          snprintf(perms, sizeof(perms), _("Protected"));
        else if ((user_read) && (!user_write))
          snprintf(perms, sizeof(perms), _("Read Only"));
        else if ((user_read) && (user_write))
          acc = 1;
     }
   else if (group)
     {
        if ((!group_read) && (!group_write))
          snprintf(perms, sizeof(perms), _("Forbidden"));
        else if ((group_read) && (!group_write))
          snprintf(perms, sizeof(perms), _("Read Only"));
        else if ((group_read) && (group_write))
          acc = 1;
     }
   else
     {
        if ((!other_read) && (!other_write))
          snprintf(perms, sizeof(perms), _("Forbidden"));
        else if ((other_read) && (!other_write))
          snprintf(perms, sizeof(perms), _("Read Only"));
        else if ((other_read) && (other_write))
          acc = 1;
     }
   if (!acc)
     return strdup(perms);
   else
     return strdup(_("Read-Write"));
}

static char *
_e_wid_file_time_get(time_t st_modtime)
{
   return e_util_file_time_get(st_modtime);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   E_FREE(wd->preview_extra_text);
   E_FREE(wd->preview_size_text);
   E_FREE(wd->preview_owner_text);
   E_FREE(wd->preview_perms_text);
   E_FREE(wd->preview_time_text);
   eina_stringshare_del(wd->path);
   eina_stringshare_del(wd->mime);
   if (wd->monitor) eio_monitor_del(wd->monitor);
   E_FREE_LIST(wd->handlers, ecore_event_handler_del);
   wd->preview_text_file_next = EINA_FALSE;
   if (wd->preview_text_file_thread)
     {
        wd->delete_me = EINA_TRUE;
        eio_file_cancel(wd->preview_text_file_thread);
        return ;
     }
   free(wd);
}

static void
_e_wid_fprev_preview_reset(E_Widget_Data *wd)
{
   Evas_Object *o;

   evas_object_del(wd->o_preview_scrollframe);
   if (wd->o_preview_artwork) evas_object_del(wd->o_preview_artwork);
   wd->o_preview_scrollframe = wd->o_preview_preview = wd->o_preview_artwork = NULL;
   if (wd->preview_text_file_thread) eio_file_cancel(wd->preview_text_file_thread);
   wd->preview_text_file_thread = NULL;
   if (wd->is_dir || wd->is_txt || wd->is_font) return;
   o = e_widget_preview_add(evas_object_evas_get(wd->obj), wd->w, wd->h);
   wd->prev_is_txt = wd->prev_is_fm = wd->prev_is_font = EINA_FALSE;
   wd->o_preview_preview = o;
   e_widget_table_object_append(wd->o_preview_preview_table,
                                wd->o_preview_preview,
                                0, 0, 2, 1, 0, 0, 1, 1);
   e_widget_list_object_repack(wd->o_preview_list,
                               wd->o_preview_preview_table,
                               0, 1, 0.5);
   e_widget_list_object_repack(wd->o_preview_list,
                               wd->o_preview_properties_table,
                               1, 1, 0.5);
}

static void
_e_wid_cb_selected(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "selected", obj);
}

static void
_e_wid_cb_selection_change(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "selection_change", obj);
}

static void
_e_wid_cb_dir_changed(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "dir_changed", obj);
}

static void
_e_wid_cb_changed(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "changed", obj);
}

static void
_e_wid_cb_file_deleted(void *data, Evas_Object *obj, void *event EINA_UNUSED)
{
   evas_object_smart_callback_call(data, "files_deleted", obj);
}

static void
_e_wid_fprev_preview_txt_map_success(void *data, Eio_File *handler EINA_UNUSED, void *map, size_t length)
{
   E_Widget_Data *wd = data;
   Eina_Strbuf *buf;
   char *msg;
   Evas_Coord mw, mh;

   buf = eina_strbuf_new();
   eina_strbuf_append_length(buf, map, length);
   msg = evas_textblock_text_utf8_to_markup(NULL, eina_strbuf_string_get(buf));
   eina_strbuf_reset(buf);

   eina_strbuf_append(buf, "<align=left>");
   eina_strbuf_append(buf, msg);
   free(msg);

   edje_object_part_text_set(wd->o_preview_preview, "e.textblock.message", eina_strbuf_string_get(buf));
   edje_object_size_min_calc(wd->o_preview_preview, &mw, &mh);
   evas_object_resize(wd->o_preview_preview, mw, mh);

   eina_strbuf_free(buf);
   eina_file_map_free(wd->preview_text_file, map);
   eina_file_close(wd->preview_text_file);
   wd->preview_text_file = NULL;
   wd->preview_text_file_thread = NULL;
}

static void _e_wid_fprev_preview_txt_open_success(void *data, Eio_File *handler, Eina_File *file);

static void
_e_wid_fprev_preview_txt_error(void *data, Eio_File *handler EINA_UNUSED, int error EINA_UNUSED)
{
   E_Widget_Data *wd = data;

   wd->preview_text_file_thread = NULL;
   if (wd->preview_text_file) eina_file_close(wd->preview_text_file);
   wd->preview_text_file = NULL;

   if (wd->preview_text_file_next)
     {
        wd->preview_text_file_thread = eio_file_open(wd->path, EINA_FALSE,
                                                     _e_wid_fprev_preview_txt_open_success,
                                                     _e_wid_fprev_preview_txt_error, wd);
        wd->preview_text_file_next = EINA_FALSE;
     }

   if (wd->delete_me) free(wd);
}

static void
_e_wid_fprev_preview_txt_open_success(void *data, Eio_File *handler EINA_UNUSED, Eina_File *file)
{
   E_Widget_Data *wd = data;
   unsigned int length = eina_file_size_get(file);

   if (length > FILEPREVIEW_TEXT_PREVIEW_SIZE) length = FILEPREVIEW_TEXT_PREVIEW_SIZE;

   wd->preview_text_file = file;
   wd->preview_text_file_thread = eio_file_map_new(file, EINA_FILE_POPULATE,
                                                   0, length,
                                                   NULL, _e_wid_fprev_preview_txt_map_success, _e_wid_fprev_preview_txt_error, wd);
}

static void
_e_wid_fprev_preview_txt(E_Widget_Data *wd)
{
   Evas_Object *o;
   int mw;

   if (!wd->is_txt) return;
   if (!wd->path) return;
   if (wd->o_preview_preview && (!wd->prev_is_txt))
     {
        evas_object_del(wd->o_preview_preview);
        wd->o_preview_preview = NULL;
     }
   if (!wd->o_preview_preview)
     {
        Evas *evas;

        evas = evas_object_evas_get(wd->obj);
        o = edje_object_add(evas);
        /* using dialog theme for now because it's simple, common, and doesn't require all
         * themes to be updated
         */
        e_theme_edje_object_set(o, "base/theme/dialog", "e/widgets/dialog/text");
        edje_object_signal_emit(o, "e,state,left", "e");
        edje_object_message_signal_process(o);
        wd->o_preview_preview = o;
        wd->prev_is_txt = EINA_TRUE;
        evas_object_resize(o, wd->w, wd->h);
        o = e_widget_scrollframe_simple_add(evas, o);
        wd->o_preview_scrollframe = o;
        e_widget_size_min_get(wd->o_preview_list, &mw, NULL);
        e_widget_size_min_set(o, wd->w, wd->h);
        evas_object_propagate_events_set(wd->o_preview_preview, 0);
        e_widget_table_object_append(wd->o_preview_preview_table,
                                     o, 0, 0, 2, 1, 1, 1, 1, 1);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_preview_table,
                                    1, 1, 0.5);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_properties_table,
                                    1, 1, 0.5);
     }

   if (wd->preview_text_file_thread)
     {
        eio_file_cancel(wd->preview_text_file_thread);
        wd->preview_text_file_next = EINA_TRUE;
     }
   else
     {
        wd->preview_text_file_thread = eio_file_open(wd->path, EINA_FALSE,
                                                     _e_wid_fprev_preview_txt_open_success,
                                                     _e_wid_fprev_preview_txt_error, wd);
     }
}

static void
_e_wid_fprev_preview_font(E_Widget_Data *wd)
{
   Evas_Object *o;
   int mw;

   if (!wd->is_font) return;
   if (!wd->path) return;
   if (wd->o_preview_preview && (!wd->prev_is_font))
     {
        evas_object_del(wd->o_preview_preview);
        wd->o_preview_preview = NULL;
     }
   if (!wd->o_preview_preview)
     {
        Evas *evas;
        char *buf, *escaped;

        evas = evas_object_evas_get(wd->obj);
        o = edje_object_add(evas);
        /* using dialog theme for now because it's simple, common, and doesn't require all
         * themes to be updated
         */
        e_theme_edje_object_set(o, "base/theme/dialog", "e/widgets/dialog/text");
        edje_object_signal_emit(o, "e,state,left", "e");
        edje_object_message_signal_process(o);
        escaped = eina_str_escape(wd->path);
        if (escaped)
          {
             buf = alloca(strlen(escaped) + 4096);
             snprintf(buf, strlen(wd->path) + 4096,
                      "<font=%s>"

                      "<font_size=28>"
                      "28 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"

                      "<font_size=20>"
                      "20 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"

                      "<font_size=16>"
                      "16 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"

                      "<font_size=12>"
                      "12 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"

                      "<font_size=10>"
                      "10 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"

                      "<font_size=8>"
                      "8 - ABC abc 0123 @!?#$*{}<ps/>"
                      "The quick brown fox jumps.<ps/>"
                      , escaped
                     );
             edje_object_part_text_set(o, "e.textblock.message", buf);
             free(escaped);
          }
        wd->o_preview_preview = o;
        wd->prev_is_font = EINA_TRUE;
        evas_object_resize(o, wd->w, wd->h);
        o = e_widget_scrollframe_simple_add(evas, o);
        wd->o_preview_scrollframe = o;
        e_widget_size_min_get(wd->o_preview_list, &mw, NULL);
        e_widget_size_min_set(o, wd->w, wd->h);
        evas_object_propagate_events_set(wd->o_preview_preview, 0);
        e_widget_table_object_append(wd->o_preview_preview_table,
                                     o, 0, 0, 2, 1, 1, 1, 1, 1);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_preview_table,
                                    1, 1, 0.5);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_properties_table,
                                    1, 1, 0.5);
        evas_object_show(o);
     }
}

static void
_e_wid_fprev_preview_fm(E_Widget_Data *wd)
{
   E_Fm2_Config fmc;
   Evas_Object *o;
   int mw;

   if (!wd->is_dir) return;
   if (!wd->path) return;
   if (wd->o_preview_preview && (!wd->prev_is_fm))
     {
        evas_object_del(wd->o_preview_preview);
        wd->o_preview_preview = NULL;
     }
   if (!wd->o_preview_preview)
     {
        Evas *evas;

        evas = evas_object_evas_get(wd->obj);
        o = e_fm2_add(evas);
        wd->o_preview_preview = o;
        wd->prev_is_fm = EINA_TRUE;
        memset(&fmc, 0, sizeof(E_Fm2_Config));
        fmc.view.mode = E_FM2_VIEW_MODE_LIST;
        fmc.view.open_dirs_in_place = 1;
        fmc.view.selector = 1;
        fmc.view.single_click = 0;
        fmc.view.no_subdir_jump = 1;
        fmc.view.no_subdir_drop = 1;
        fmc.view.link_drop = 1;
        fmc.icon.list.w = 24;
        fmc.icon.list.h = 24;
        fmc.icon.fixed.w = 1;
        fmc.icon.fixed.h = 1;
        fmc.list.sort.no_case = 1;
        fmc.view.no_click_rename = 1;
        fmc.selection.single = 1;
        e_fm2_config_set(o, &fmc);
        e_fm2_icon_menu_flags_set(o, E_FM2_MENU_NO_VIEW_MENU);
        evas_object_smart_callback_add(o, "dir_changed",
                                       _e_wid_cb_dir_changed, wd->obj);
        evas_object_smart_callback_add(o, "selection_change",
                                       _e_wid_cb_selection_change, wd->obj);
        evas_object_smart_callback_add(o, "changed",
                                       _e_wid_cb_changed, wd->obj);
        evas_object_smart_callback_add(o, "files_deleted",
                                       _e_wid_cb_file_deleted, wd->obj);
        evas_object_smart_callback_add(o, "selected",
                                       _e_wid_cb_selected, wd->obj);
        o = e_widget_scrollframe_pan_add(evas, wd->o_preview_preview,
                                         e_fm2_pan_set,
                                         e_fm2_pan_get,
                                         e_fm2_pan_max_get,
                                         e_fm2_pan_child_size_get);
        wd->o_preview_scrollframe = o;
        e_widget_size_min_get(wd->o_preview_list, &mw, NULL);
//        e_widget_size_min_set(o, MAX(wd->w, mw), wd->h);
        e_widget_size_min_set(o, 0, wd->h);
        evas_object_propagate_events_set(wd->o_preview_preview, 0);
        e_widget_scrollframe_focus_object_set(o, wd->o_preview_preview);
        e_widget_table_object_append(wd->o_preview_preview_table,
                                     o, 0, 0, 2, 1, 1, 1, 1, 1);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_preview_table,
                                    1, 1, 0.5);
        e_widget_list_object_repack(wd->o_preview_list,
                                    wd->o_preview_properties_table,
                                    1, 1, 0.5);
     }
   e_fm2_path_set(wd->o_preview_preview, "/", wd->path);
}

static Eina_Bool
_e_wid_fprev_cb_del(E_Widget_Data *wd, int type EINA_UNUSED, Eio_Monitor_Event *ev)
{
   if (wd->monitor != ev->monitor) return ECORE_CALLBACK_RENEW;
   _e_wid_fprev_clear_widgets(wd);
   if (wd->monitor) eio_monitor_del(wd->monitor);
   wd->monitor = NULL;
   E_FREE_LIST(wd->handlers, ecore_event_handler_del);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_e_wid_fprev_cb_mod(E_Widget_Data *wd, int type EINA_UNUSED, Eio_Monitor_Event *ev)
{
   if (wd->monitor != ev->monitor) return ECORE_CALLBACK_RENEW;
   _e_wid_fprev_preview_file(wd);
   return ECORE_CALLBACK_RENEW;
}

E_API Evas_Object *
e_widget_filepreview_add(Evas *evas, int w, int h, int horiz)
{
   Evas_Object *obj, *o;
   int mw, mh;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = E_NEW(E_Widget_Data, 1);
   e_widget_data_set(obj, wd);
   wd->obj = obj;

   wd->w = w, wd->h = h;

   o = e_widget_list_add(evas, 0, horiz);
   wd->o_preview_list = o;
   e_widget_resize_object_set(obj, o);
   e_widget_sub_object_add(obj, o);

   e_widget_size_min_get(wd->o_preview_list, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   evas_object_show(wd->o_preview_preview_table);
   evas_object_show(wd->o_preview_extra);
   evas_object_show(wd->o_preview_extra_entry);
   evas_object_show(wd->o_preview_size);
   evas_object_show(wd->o_preview_size_entry);
   evas_object_show(wd->o_preview_owner);
   evas_object_show(wd->o_preview_owner_entry);
   evas_object_show(wd->o_preview_perms);
   evas_object_show(wd->o_preview_perms_entry);
   evas_object_show(wd->o_preview_time);
   evas_object_show(wd->o_preview_time_entry);
   evas_object_show(wd->o_preview_properties_table);
   evas_object_show(wd->o_preview_list);
   return obj;
}

E_API void
e_widget_filepreview_path_set(Evas_Object *obj, const char *path, const char *mime)
{
   E_Widget_Data *wd;

   if (!obj) return;
   wd = e_widget_data_get(obj);
   if (!wd) return;
   eina_stringshare_replace(&wd->path, path);
   eina_stringshare_replace(&wd->mime, mime);
   if (wd->monitor) eio_monitor_del(wd->monitor);
   wd->monitor = eio_monitor_stringshared_add(wd->path);
   if (!wd->handlers)
     {
        E_LIST_HANDLER_APPEND(wd->handlers, EIO_MONITOR_FILE_MODIFIED, _e_wid_fprev_cb_mod, wd);
        E_LIST_HANDLER_APPEND(wd->handlers, EIO_MONITOR_FILE_DELETED, _e_wid_fprev_cb_del, wd);
        E_LIST_HANDLER_APPEND(wd->handlers, EIO_MONITOR_ERROR, _e_wid_fprev_cb_del, wd);
        E_LIST_HANDLER_APPEND(wd->handlers, EIO_MONITOR_SELF_DELETED, _e_wid_fprev_cb_del, wd);
     }
   _e_wid_fprev_preview_file(wd);
}

E_API void
e_widget_filepreview_filemode_force(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!obj) return;
   wd = e_widget_data_get(obj);
   if (!wd) return;
   _e_wid_fprev_preview_file_widgets(wd, 0, 0, 0);
}

E_API void
e_widget_filepreview_clamp_video_set(Evas_Object *obj, Eina_Bool clamp)
{
   E_Widget_Data *wd;

   EINA_SAFETY_ON_NULL_RETURN(obj);
   wd = e_widget_data_get(obj);
   EINA_SAFETY_ON_NULL_RETURN(wd);
   wd->clamp_video = !!clamp;
}
#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_scroll, *o_fm;
   E_Fm2_Config fmc;
};

/* private function prototypes */
static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *evas, Evas_Object *obj, void *event);
static void _e_wid_cb_selected(void *data, Evas_Object *obj, void *event);
static void _e_wid_cb_dir_changed(void *data, Evas_Object *obj, void *event);
static void _e_wid_cb_changed(void *data, Evas_Object *obj, void *event);
static void _e_wid_cb_file_deleted(void *data, Evas_Object *obj, void *event);

E_API Evas_Object *
e_widget_flist_add(Evas *evas)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   wd = E_NEW(E_Widget_Data, 1);
   if (!wd) return NULL;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_data_set(obj, wd);

   o = e_scrollframe_add(evas);
   wd->o_scroll = o;
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);

   o = e_fm2_add(evas);
   wd->o_fm = o;
   memset(&wd->fmc, 0, sizeof(E_Fm2_Config));
   wd->fmc.view.mode = E_FM2_VIEW_MODE_LIST;
   wd->fmc.view.open_dirs_in_place = 1;
   wd->fmc.view.selector = 1;
   wd->fmc.view.single_click = 0;
   wd->fmc.view.no_subdir_jump = 0;
   wd->fmc.icon.list.w = 48;
   wd->fmc.icon.list.h = 48;
   wd->fmc.icon.fixed.w = 1;
   wd->fmc.icon.fixed.h = 1;
   wd->fmc.icon.extension.show = 0;
   wd->fmc.icon.key_hint = NULL;
   wd->fmc.list.sort.no_case = 1;
   wd->fmc.list.sort.dirs.first = 0;
   wd->fmc.list.sort.dirs.last = 0;
   wd->fmc.selection.single = 1;
   wd->fmc.selection.windows_modifiers = 0;
   e_fm2_config_set(wd->o_fm, &wd->fmc);
   e_fm2_icon_menu_flags_set(wd->o_fm, E_FM2_MENU_NO_SHOW_HIDDEN);

   evas_object_smart_callback_add(wd->o_fm, "dir_changed",
                                  _e_wid_cb_dir_changed, obj);
   evas_object_smart_callback_add(wd->o_fm, "selection_change",
                                  _e_wid_cb_selected, obj);
   evas_object_smart_callback_add(wd->o_fm, "changed",
                                  _e_wid_cb_changed, obj);
   evas_object_smart_callback_add(wd->o_fm, "files_deleted",
                                  _e_wid_cb_file_deleted, obj);

   e_scrollframe_key_navigation_set(wd->o_scroll, EINA_FALSE);
   e_scrollframe_extern_pan_set(wd->o_scroll, o, e_fm2_pan_set,
                                e_fm2_pan_get, e_fm2_pan_max_get,
                                e_fm2_pan_child_size_get);

   e_widget_sub_object_add(obj, o);
   evas_object_show(o);
   evas_object_smart_callback_add(o, "selected",
                                  _e_wid_cb_selected, obj);

   evas_object_resize(obj, 32, 32);
   e_widget_size_min_set(obj, 32, 32);
   return obj;
}

E_API E_Fm2_Config *
e_widget_flist_config_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   return e_fm2_config_get(wd->o_fm);
}

E_API void
e_widget_flist_path_set(Evas_Object *obj, const char *dev, const char *path)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   e_fm2_path_set(wd->o_fm, dev, path);
   e_scrollframe_child_pos_set(wd->o_scroll, 0, 0);
}

E_API Eina_List *
e_widget_flist_all_list_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   return e_fm2_all_list_get(wd->o_fm);
}

E_API Eina_List *
e_widget_flist_selected_list_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   return e_fm2_selected_list_get(wd->o_fm);
}

E_API const char *
e_widget_flist_real_path_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   return e_fm2_real_path_get(wd->o_fm);
}

E_API int
e_widget_flist_has_parent_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   return e_fm2_has_parent_get(wd->o_fm);
}

E_API void
e_widget_flist_select_set(Evas_Object *obj, const char *file, int s)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   e_fm2_select_set(wd->o_fm, file, s);
}

E_API void
e_widget_flist_file_show(Evas_Object *obj, const char *file)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   e_fm2_file_show(wd->o_fm, file);
}

E_API void
e_widget_flist_parent_go(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   e_fm2_parent_go(wd->o_fm);
   e_scrollframe_child_pos_set(wd->o_scroll, 0, 0);
}

E_API void
e_widget_flist_refresh(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   e_fm2_refresh(wd->o_fm);
}

/* private functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   E_FREE(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scroll),
                                "e,state,focused", "e");
        evas_object_focus_set(wd->o_fm, 1);
     }
   else
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scroll),
                                "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_fm, 0);
     }
}

static void
_e_wid_focus_steal(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_e_wid_cb_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event)
{
   evas_object_smart_callback_call(data, "selection_change", event);
}

static void
_e_wid_cb_dir_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event)
{
   evas_object_smart_callback_call(data, "dir_changed", event);
}

static void
_e_wid_cb_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event)
{
   evas_object_smart_callback_call(data, "changed", event);
}

static void
_e_wid_cb_file_deleted(void *data, Evas_Object *obj EINA_UNUSED, void *event)
{
   evas_object_smart_callback_call(data, "files_deleted", event);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_scrollframe;
   Evas_Object *o_text;
};
static void _e_wid_cb_scrollframe_resize(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_del_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_font_preview_add(Evas *evas, const char *text)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   /* Add Scrollpane */
   o = e_scrollframe_add(evas);
   wd->o_scrollframe = o;
   e_scrollframe_policy_set(o, E_SCROLLFRAME_POLICY_OFF, E_SCROLLFRAME_POLICY_OFF);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);

   /* Add Text */
   o = edje_object_add(evas);
   wd->o_text = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/fontpreview");
   evas_object_event_callback_add(wd->o_scrollframe, EVAS_CALLBACK_RESIZE, _e_wid_cb_scrollframe_resize, wd);
   e_scrollframe_child_set(wd->o_scrollframe, o);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);

   edje_object_part_text_set(wd->o_text, "e.fontpreview.text", text);

   evas_object_resize(obj, 40, 40);
   e_widget_size_min_set(obj, 40, 40);

   return obj;
}

E_API void
e_widget_font_preview_font_set(Evas_Object *obj, const char *font, Evas_Font_Size size)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);

   if (size < 0)
     size = (-size * 10) / 100;
   if (size == 0)
     size = 10;

   edje_object_text_class_set(wd->o_text, "_e_font_preview", font, size);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_cb_scrollframe_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh, vw, vh;

   wd = data;
   e_scrollframe_child_viewport_size_get(obj, &vw, &vh);
   edje_object_size_min_calc(wd->o_text, &mw, &mh);
   e_scrollframe_child_viewport_size_get(wd->o_scrollframe, &vw, &vh);
   if (vw > mw) mw = vw;
   if (vh > mh) mh = vh;
   evas_object_resize(wd->o_text, mw, mh);
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_frame, *o_box;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_framelist_add(Evas *evas, const char *label, int horiz)
{
   Evas_Object *obj, *o, *win;
   E_Widget_Data *wd;

   win = e_win_evas_win_get(evas);
   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = elm_frame_add(win);
   wd->o_frame = o;
   elm_object_text_set(o, label);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   o = elm_box_add(win);
   wd->o_box = o;
   elm_box_horizontal_set(o, horiz);
   elm_box_homogeneous_set(o, 0);
   elm_object_content_set(wd->o_frame, o);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);

   return obj;
}

E_API void
e_widget_framelist_object_append_full(Evas_Object *obj, Evas_Object *sobj, int fill_w, int fill_h, int expand_w, int expand_h, double align_x, double align_y, Evas_Coord min_w, Evas_Coord min_h, Evas_Coord max_w, Evas_Coord max_h)
{
   E_Widget_Data *wd;
   Evas_Coord mw = 0, mh = 0;

   wd = e_widget_data_get(obj);

   elm_box_pack_end(wd->o_box, sobj);
   e_widget_size_min_get(sobj, &mw, &mh);
   if (fill_w) align_x = -1;
   if (fill_h) align_y = -1;
   E_WEIGHT(sobj, expand_w, expand_h);
   E_ALIGN(sobj, align_x, align_y);
   evas_object_size_hint_min_set(sobj, min_w, min_h);
   evas_object_size_hint_max_set(sobj, max_w, max_h);
   elm_box_recalculate(wd->o_box);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

E_API void
e_widget_framelist_object_append(Evas_Object *obj, Evas_Object *sobj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);

   elm_box_pack_end(wd->o_box, sobj);
   E_EXPAND(sobj);
   E_FILL(sobj);
   elm_box_recalculate(wd->o_box);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

E_API void
e_widget_framelist_content_align_set(Evas_Object *obj, double halign, double valign)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_box_align_set(wd->o_box, halign, valign);
}

E_API void
e_widget_framelist_label_set(Evas_Object *obj, const char *label)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   edje_object_part_text_set(wd->o_frame, "e.text.label", label);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_disabled_get(obj))
     edje_object_signal_emit(wd->o_frame, "e,state,disabled", "e");
   else
     edje_object_signal_emit(wd->o_frame, "e,state,enabled", "e");
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *obj;
   Evas_Object *o_frame, *o_table;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_frametable_add(Evas *evas, const char *label, int homogenous)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);
   wd->obj = obj;

   o = elm_frame_add(e_win_evas_win_get(evas));
   wd->o_frame = o;
   elm_object_text_set(o, label);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   o = elm_table_add(e_win_evas_win_get(evas));
   wd->o_table = o;
   elm_table_homogeneous_set(o, homogenous);
   elm_object_content_set(wd->o_frame, o);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);

   return obj;
}

E_API void
e_widget_frametable_object_append(Evas_Object *obj, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h)
{
   e_widget_frametable_object_append_full(obj, sobj, col, row, colspan, rowspan, fill_w, fill_h, expand_w, expand_h, 0.5, 0.5, -1, -1, -1, -1);
}

E_API void
e_widget_frametable_object_append_full(Evas_Object *obj, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h, double align_x, double align_y, Evas_Coord min_w, Evas_Coord min_h, Evas_Coord max_w, Evas_Coord max_h)
{
   E_Widget_Data *wd = e_widget_data_get(obj);

   if ((min_w > 0) || (min_h > 0))
     evas_object_size_hint_min_set(sobj, min_w, min_h);
   if ((max_w > 0) || (max_h > 0))
     evas_object_size_hint_max_set(sobj, max_w, max_h);
   if (fill_w) align_x = -1;
   if (fill_h) align_y = -1;
   E_ALIGN(sobj, align_x, align_y);
   E_WEIGHT(sobj, expand_w, expand_h);
   elm_table_pack(wd->o_table, sobj, col, row, colspan, rowspan);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

E_API void
e_widget_frametable_object_repack(Evas_Object *obj EINA_UNUSED, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h)
{
   if (fill_w || fill_h)
     E_ALIGN(sobj, fill_w ? -1 : 0.5, fill_h ? -1 : 0.5);
   E_WEIGHT(sobj, expand_w, expand_h);
   elm_table_pack_set(sobj, col, row, colspan, rowspan);
}

E_API void
e_widget_frametable_content_align_set(Evas_Object *obj, double halign, double valign)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_table_align_set(wd->o_table, halign, valign);
}

E_API void
e_widget_frametable_label_set(Evas_Object *obj, const char *label)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_text_set(wd->o_frame, label);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_disabled_set(wd->o_frame, e_widget_disabled_get(obj));
}

#include "e.h"
#include "e_widget_filepreview.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *obj;
   Evas_Object *fm_overlay_clip;
   Evas_Object *o_table;
   Evas_Object *o_table2;
   Evas_Object *o_preview_frame;
   Evas_Object *o_preview;
   Evas_Object *o_up_button;
   Evas_Object *o_favorites_frame;
   Evas_Object *o_favorites_fm;
   Evas_Object *o_favorites_add;
   Evas_Object *o_files_frame;
   Evas_Object *o_files_fm;
   Evas_Object *o_entry;
   Evas_Coord   preview_w, preview_h;
   char        *entry_text;
   const char  *path;
   void         (*sel_func)(void *data, Evas_Object *obj);
   void        *sel_data;
   void         (*chg_func)(void *data, Evas_Object *obj);
   void        *chg_data;
   int          preview;
   Eina_Bool    nochange E_BITFIELD; // block changing of entry
   Eina_Bool    fprev E_BITFIELD; // current fprev is dir
};

static void _e_wid_del_hook(Evas_Object *obj);

/* local subsystem functions */
static void
_e_wid_fsel_button_up(void *data1, void *data2 EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data1;
   if (wd->o_files_fm)
     e_fm2_parent_go(wd->o_files_fm);
   if (wd->o_files_frame)
     e_widget_scrollframe_child_pos_set(wd->o_files_frame, 0, 0);
}

static void
_e_wid_fsel_favorites_add(void *data1, void *data2 EINA_UNUSED)
{
   E_Widget_Data *wd;
   const char *current_path, *fn;
   char buf[PATH_MAX], *fname;
   struct stat st;
   FILE *f;
   size_t len;
   int ret;

   wd = data1;
   current_path = e_fm2_real_path_get(wd->o_files_fm);
   if (!ecore_file_is_dir(current_path)) return;

   len = e_user_dir_snprintf(buf, sizeof(buf), "fileman/favorites/%s",
                             ecore_file_file_get(current_path));
   if (len >= sizeof(buf)) return;
   if (stat(buf, &st) < 0)
     ret = symlink(current_path, buf);
   else
     {
        int i = 1, maxlen;

        buf[len] = '-';
        len++;
        if (len == sizeof(buf)) return;
        maxlen = sizeof(buf) - len;
        do
          {
             if (snprintf(buf + len, maxlen, "%d", i) >= maxlen)
               return;
             i++;
          }
        while (stat(buf, &st) == 0);
        ret = symlink(current_path, buf);
     }
   if (ret != 0) return;
   fn = ecore_file_file_get(buf);
   len = strlen(fn) + 1;
   fname = alloca(len);
   memcpy(fname, fn, len);
   e_user_dir_concat_static(buf, "fileman/favorites/.order");
   if (ecore_file_exists(buf))
     {
        f = fopen(buf, "a");
        if (f)
          {
             fprintf(f, "%s\n", fname);
             fclose(f);
          }
     }
   e_fm2_refresh(wd->o_favorites_fm);
}

static void
_e_wid_fsel_favorites_files_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;
   Eina_List *icons, *l;
   E_Fm2_Icon_Info *ici;
   const char *rp;
   char *p1, *p2;

   wd = data;
   if (!wd->o_favorites_fm) return;
   if (!wd->o_files_fm) return;
   icons = e_fm2_all_list_get(wd->o_favorites_fm);
   if (!icons) return;
   rp = e_fm2_real_path_get(wd->o_files_fm);
   p1 = ecore_file_realpath(rp);
   if (!p1) goto done;
   EINA_LIST_FOREACH(icons, l, ici)
     {
        if (ici->link)
          {
             p2 = ecore_file_realpath(ici->link);
             if (p2)
               {
                  if (!strcmp(p1, p2))
                    {
                       e_fm2_select_set(wd->o_favorites_fm, ici->file, 1);
                       E_FREE(p2);
                       goto done;
                    }
                  E_FREE(p2);
               }
          }
     }
done:
   E_FREE(p1);
   eina_list_free(icons);
}

static void
_e_wid_fsel_favorites_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;
   Eina_List *selected;
   E_Fm2_Icon_Info *ici;

   wd = data;
   if (!wd->o_favorites_fm) return;
   if (!wd->o_files_frame) return;
   selected = e_fm2_selected_list_get(wd->o_favorites_fm);
   if (!selected) return;
   ici = eina_list_data_get(selected);
   if ((ici->link) && (ici->mount))
     e_fm2_path_set(wd->o_files_fm, ici->link, "/");
   else if (ici->real_link)
     e_fm2_path_set(wd->o_files_fm, NULL, ici->real_link);
   eina_list_free(selected);
   e_widget_scrollframe_child_pos_set(wd->o_files_frame, 0, 0);
}

static void
_e_wid_fsel_files_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   if (!wd->o_files_fm) return;
   if (e_fm2_selected_count(wd->o_files_fm)) return;
   wd->nochange = EINA_TRUE;
   e_fm2_first_sel(wd->o_files_fm);
   wd->nochange = EINA_FALSE;
}

static void
_e_wid_fsel_files_dir_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   if (!wd->o_files_fm) return;
   if (!e_fm2_has_parent_get(wd->o_files_fm))
     {
        if (wd->o_up_button)
          e_widget_disabled_set(wd->o_up_button, 1);
     }
   else
     {
        if (wd->o_up_button)
          e_widget_disabled_set(wd->o_up_button, 0);
     }
   if (wd->o_files_frame)
     e_widget_scrollframe_child_pos_set(wd->o_files_frame, 0, 0);
   eina_stringshare_replace(&wd->path, NULL);
   if (wd->chg_func) wd->chg_func(wd->chg_data, wd->obj);
}

static void
_e_wid_fsel_typebuf_change(E_Widget_Data *wd, Evas_Object *obj EINA_UNUSED, const char *str)
{
   e_widget_entry_text_set(wd->o_entry, str);
}

static void
_e_wid_fsel_sel_chg(E_Widget_Data *wd, Evas_Object *fm)
{
   Eina_List *selected;
   E_Fm2_Icon_Info *ici;
   const char *rp;
   char buf[PATH_MAX];
   struct stat st;
   Eina_Bool preview;

   rp = e_widget_entry_text_get(wd->o_entry);
   if (wd->nochange && rp && rp[0]) return;
   preview = !!fm;
   fm = fm ? : wd->o_files_fm;
   wd->fprev = (fm != wd->o_files_fm);
   selected = e_fm2_selected_list_get(fm);
   if (!selected) return;
   ici = eina_list_data_get(selected);
   rp = e_fm2_real_path_get(fm);
   if (!strcmp(rp, "/"))
     {
        snprintf(buf, sizeof(buf), "/%s", ici->file);
     }
   else
     {
        snprintf(buf, sizeof(buf), "%s/%s", rp, ici->file);
     }
   eina_stringshare_replace(&wd->path, buf);
   if (stat(wd->path, &st) == 0)
     {
        if (wd->preview && (!preview))
          e_widget_filepreview_path_set(wd->o_preview, wd->path, ici->mime);
        if (!S_ISDIR(st.st_mode))
          e_widget_entry_text_set(wd->o_entry, ici->file);
     }
   eina_list_free(selected);
   if (wd->chg_func) wd->chg_func(wd->chg_data, wd->obj);
}

static void
_e_wid_fsel_fprev_selection_change(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   _e_wid_fsel_sel_chg(data, event_info);
}

static void
_e_wid_fsel_files_selection_change(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   _e_wid_fsel_sel_chg(data, NULL);
}

static void
_e_wid_fsel_preview_file_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   E_Widget_Data *wd;
   Eina_List *l;
   E_Fm2_Icon_Info *ici;
   const char *path, *dev, *newpath;

   wd = data;
   l = e_fm2_selected_list_get(event_info);
   if (!l) return;
   ici = eina_list_data_get(l);
   if (S_ISDIR(ici->statinfo.st_mode))
     {
        e_fm2_path_get(event_info, &dev, &path);
        newpath = eina_stringshare_printf("%s/%s", path, ici->file);
        e_fm2_path_set(wd->o_files_fm, dev, newpath);
        eina_stringshare_del(newpath);
        return;
     }
   if (wd->sel_func) wd->sel_func(wd->sel_data, wd->obj);
}

static void
_e_wid_fsel_files_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   if (wd->path)
     {
        if (wd->sel_func) wd->sel_func(wd->sel_data, wd->obj);
     }
}

static void
_e_wid_fsel_moveresize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd = data;
   int x, y, w, h;
   evas_object_geometry_get(wd->o_files_frame, &x, &y, &w, &h);
   evas_object_resize(wd->fm_overlay_clip, x + w, y + h);
   evas_object_geometry_get(wd->o_favorites_frame, &x, &y, NULL, NULL);
   evas_object_move(wd->fm_overlay_clip, x, y);
}

/* externally accessible functions */
E_API Evas_Object *
e_widget_fsel_add(Evas *evas, const char *dev, const char *path, char *selected, char *filter EINA_UNUSED,
                  void (*sel_func)(void *data, Evas_Object *obj), void *sel_data,
                  void (*chg_func)(void *data, Evas_Object *obj), void *chg_data,
                  int preview)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw = 0, mh = 0;
   E_Fm2_Config fmc;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   wd->obj = obj;
   wd->sel_func = sel_func;
   wd->sel_data = sel_data;
   wd->chg_func = chg_func;
   wd->chg_data = chg_data;
   wd->preview = preview;

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_table = o;
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   wd->o_table2 = o;

   o = e_widget_button_add(evas, _("Add to Favorites"), "bookmark-new",
                           _e_wid_fsel_favorites_add, wd, NULL);
   wd->o_favorites_add = o;
   e_widget_table_object_append(wd->o_table2, o, 0, 0, 1, 1, 0, 0, 0, 0);

   o = e_widget_button_add(evas, _("Go up a Directory"), "go-up",
                           _e_wid_fsel_button_up, wd, NULL);
   wd->o_up_button = o;
   e_widget_table_object_append(wd->o_table2, o, 1, 0, 1, 1, 0, 0, 1, 0);

   if (preview)
     {
        Evas_Coord mw2, mh2;

        wd->o_preview_frame = e_widget_framelist_add(evas, _("Preview"), 0);
        wd->o_preview = e_widget_filepreview_add(evas, 128, 128, 0);
        e_widget_filepreview_filemode_force(wd->o_preview);
        e_widget_framelist_object_append(wd->o_preview_frame, wd->o_preview);
        evas_object_smart_callback_add(wd->o_preview, "selected",
                                       _e_wid_fsel_preview_file_selected, wd);

        e_widget_size_min_get(wd->o_preview, &mw, &mh);
        e_widget_size_min_get(wd->o_preview_frame, &mw2, &mh2);
        /* need size of preview here or min size will be off */
        e_widget_size_min_set(wd->o_preview_frame, mw2, mh + 128);
        evas_object_smart_callback_add(wd->o_preview, "selection_change", _e_wid_fsel_fprev_selection_change, wd);
     }

   o = e_fm2_add(evas);
   wd->o_favorites_fm = o;
   memset(&fmc, 0, sizeof(E_Fm2_Config));
   fmc.view.mode = E_FM2_VIEW_MODE_LIST;
   fmc.view.open_dirs_in_place = 1;
   fmc.view.selector = 1;
   fmc.view.single_click = 1;
   fmc.view.no_subdir_jump = 1;
   fmc.view.no_subdir_drop = 1;
   fmc.view.link_drop = 1;
   fmc.icon.list.w = 24;
   fmc.icon.list.h = 24;
   fmc.icon.fixed.w = 1;
   fmc.icon.fixed.h = 1;
   fmc.icon.extension.show = 0;
   fmc.icon.key_hint = NULL;
   fmc.list.sort.no_case = 1;
   fmc.list.sort.dirs.first = 0;
   fmc.list.sort.dirs.last = 0;
   fmc.selection.single = 1;
   fmc.selection.windows_modifiers = 0;
   fmc.view.no_click_rename = 1;
   e_fm2_config_set(o, &fmc);
   e_fm2_icon_menu_flags_set(o, E_FM2_MENU_NO_VIEW_CHANGE | E_FM2_MENU_NO_ACTIVATE_CHANGE);
   evas_object_smart_callback_add(o, "changed",
                                  _e_wid_fsel_favorites_files_changed, wd);
   evas_object_smart_callback_add(o, "selected",
                                  _e_wid_fsel_favorites_selected, wd);
   e_fm2_path_set(o, "favorites", "/");

   o = e_widget_scrollframe_pan_add(evas, wd->o_favorites_fm,
                                    e_fm2_pan_set,
                                    e_fm2_pan_get,
                                    e_fm2_pan_max_get,
                                    e_fm2_pan_child_size_get);
   evas_object_propagate_events_set(wd->o_favorites_fm, 0);
   e_widget_scrollframe_focus_object_set(o, wd->o_favorites_fm);

   wd->o_favorites_frame = o;
   e_widget_size_min_set(o, 128, 128);
   e_widget_table_object_append(wd->o_table2, o, 0, 1, 1, 1, 1, 1, 0, 1);

   o = e_fm2_add(evas);
   wd->o_files_fm = o;
   memset(&fmc, 0, sizeof(E_Fm2_Config));
   fmc.view.mode = E_FM2_VIEW_MODE_LIST;
   fmc.view.open_dirs_in_place = 1;
   fmc.view.selector = 1;
   fmc.view.single_click = e_config->filemanager_single_click;
   fmc.view.no_subdir_jump = 0;
   fmc.icon.list.w = 24;
   fmc.icon.list.h = 24;
   fmc.icon.fixed.w = 1;
   fmc.icon.fixed.h = 1;
   fmc.icon.extension.show = 1;
   fmc.icon.key_hint = NULL;
   fmc.list.sort.no_case = 1;
   fmc.list.sort.dirs.first = 1;
   fmc.list.sort.dirs.last = 0;
   fmc.selection.single = 1;
   fmc.selection.windows_modifiers = 0;
   fmc.view.no_click_rename = 1;
   e_fm2_config_set(o, &fmc);
   e_fm2_icon_menu_flags_set(o, E_FM2_MENU_NO_VIEW_MENU);
   evas_object_smart_callback_add(o, "changed",
                                  _e_wid_fsel_files_changed, wd);
   evas_object_smart_callback_add(o, "dir_changed",
                                  _e_wid_fsel_files_dir_changed, wd);
   evas_object_smart_callback_add(o, "selection_change",
                                  _e_wid_fsel_files_selection_change, wd);
   evas_object_smart_callback_add(o, "selected",
                                  _e_wid_fsel_files_selected, wd);
   evas_object_smart_callback_add(o, "typebuf_changed",
                                  (Evas_Smart_Cb)_e_wid_fsel_typebuf_change, wd);
   e_fm2_path_set(o, dev, path);

   o = e_widget_scrollframe_pan_add(evas, wd->o_files_fm,
                                    e_fm2_pan_set,
                                    e_fm2_pan_get,
                                    e_fm2_pan_max_get,
                                    e_fm2_pan_child_size_get);
   evas_object_propagate_events_set(wd->o_files_fm, 0);
   e_widget_scrollframe_focus_object_set(o, wd->o_files_fm);

   wd->o_files_frame = o;
   e_widget_size_min_set(o, 128, 128);
   e_widget_table_object_append(wd->o_table2, o, 1, 1, 1, 1, 1, 1, 1, 1);

   o = e_widget_entry_add(e_win_evas_win_get(evas), &(wd->entry_text), NULL, NULL, NULL);
   wd->o_entry = o;
   if (selected) e_widget_entry_text_set(o, selected);

   wd->fm_overlay_clip = evas_object_rectangle_add(evas);
   evas_object_static_clip_set(wd->fm_overlay_clip, EINA_TRUE);
   e_widget_sub_object_add(obj, wd->fm_overlay_clip);
   e_fm2_overlay_clip_to(wd->o_favorites_fm, wd->fm_overlay_clip);
   e_fm2_overlay_clip_to(wd->o_files_fm, wd->fm_overlay_clip);
   evas_object_event_callback_add(wd->o_favorites_frame, EVAS_CALLBACK_RESIZE, _e_wid_fsel_moveresize, wd);
   evas_object_event_callback_add(wd->o_favorites_frame, EVAS_CALLBACK_MOVE, _e_wid_fsel_moveresize, wd);
   evas_object_event_callback_add(wd->o_files_frame, EVAS_CALLBACK_RESIZE, _e_wid_fsel_moveresize, wd);
   evas_object_event_callback_add(wd->o_files_frame, EVAS_CALLBACK_MOVE, _e_wid_fsel_moveresize, wd);
   evas_object_show(wd->fm_overlay_clip);

   if (preview)
     {
        e_widget_table_object_append(wd->o_table2,
                                     wd->o_preview_frame,
                                     2, 1, 1, 1, 1, 1, 0, 1);
     }

   e_widget_table_object_append(wd->o_table, wd->o_table2,
                                0, 0, 1, 1, 1, 1, 1, 1);
   e_widget_table_object_append(wd->o_table, wd->o_entry,
                                0, 1, 1, 1, 1, 0, 1, 0);

   e_widget_size_min_get(wd->o_table, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);

   evas_object_show(wd->o_favorites_add);
   evas_object_show(wd->o_up_button);
   evas_object_show(wd->o_favorites_frame);
   evas_object_show(wd->o_favorites_fm);
   evas_object_show(wd->o_files_frame);
   evas_object_show(wd->o_files_fm);
   evas_object_show(wd->o_entry);
   evas_object_show(wd->o_table2);
   evas_object_show(wd->o_table);
   wd->nochange = EINA_TRUE;
   if (!selected) e_fm2_first_sel(wd->o_files_fm);
   wd->nochange = EINA_FALSE;
   evas_object_focus_set(wd->o_files_fm, EINA_TRUE);
   return obj;
}

E_API void
e_widget_fsel_path_get(Evas_Object *obj, const char **dev, const char **path)
{
   E_Widget_Data *wd;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   e_fm2_path_get(wd->o_files_fm, dev, path);
}

E_API const char *
e_widget_fsel_selection_path_get(Evas_Object *obj)
{
   E_Widget_Data *wd;
   const char *s, *dir;
   char buf[PATH_MAX];

   if (!obj) return NULL;
   if (!(wd = e_widget_data_get(obj))) return NULL;
   if (wd->fprev) return wd->path;
   s = e_widget_entry_text_get(wd->o_entry);
   dir = e_fm2_real_path_get(wd->o_files_fm);
   if ((dir) && (s) && (s[0] == '/'))
     {
        eina_stringshare_replace(&wd->path, s);
     }
   else if (s)
     {
        snprintf(buf, sizeof(buf), "%s/%s", dir, s);
        eina_stringshare_replace(&wd->path, buf);
     }
   else
     {
        eina_stringshare_replace(&wd->path, dir);
     }
   return wd->path;
}

E_API void
e_widget_fsel_window_set(Evas_Object *obj, Evas_Object *win)
{
   E_Widget_Data *wd;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   e_fm2_window_set(wd->o_favorites_fm, win);
   e_fm2_window_set(wd->o_files_fm, win);
}

E_API Eina_Bool
e_widget_fsel_typebuf_visible_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!obj) return EINA_FALSE;
   wd = e_widget_data_get(obj);
   if (!wd) return EINA_FALSE;
   return e_fm2_typebuf_visible_get(wd->o_files_fm) || e_fm2_typebuf_visible_get(wd->o_favorites_fm);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   E_FREE(wd->entry_text);
   eina_stringshare_del(wd->path);

   free(wd);
}
#include "e.h"

typedef struct _E_Widget_Queue_Item E_Widget_Queue_Item;
typedef struct _E_Widget_Data       E_Widget_Data;
typedef struct _E_Widget_Callback   E_Widget_Callback;

struct _E_Widget_Data
{
   Evas_Object *o_widget, *o_scrollframe, *o_ilist;
   Eina_List   *callbacks;
   const char **value;
   struct
   {
      Eina_List   *queue;
      Ecore_Timer *timer;
      int          count;
      int          show_nth;
      int          select_nth;
   } queue;
};

struct _E_Widget_Callback
{
   void        (*func)(void *data);
   void       *data;
   const char *value;
};

struct _E_Widget_Queue_Item
{
   int          command;
   Evas_Object *icon;
   Evas_Object *end;
   const char  *label;
   int          header;
   void         (*func)(void *data);
   void        *data;
   const char  *val;
   int          relative, use_relative;
   int          item;
};

static void      _e_wid_del_hook(Evas_Object *obj);
static void      _e_wid_focus_hook(Evas_Object *obj);
static void      _e_wid_cb_scrollframe_resize(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void      _e_wid_cb_item_sel(void *data, void *data2);
static void      _e_wid_cb_item_hilight(void *data, void *data2);
static void      _e_wid_cb_selected(void *data, Evas_Object *obj, void *event_info);
static void      _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);

static Eina_Bool _queue_timer(void *data);
static void      _queue_queue(Evas_Object *obj);
static void      _queue_append(Evas_Object *obj, int command, Evas_Object *icon, Evas_Object *end, const char *label, int header, void (*func)(void *data), void *data, const char *val, int relative, int use_relative, int item);
static void      _queue_remove(Evas_Object *obj, E_Widget_Queue_Item *qi, int del);

enum
{
   CMD_ADD,
   CMD_REMOVE,
   CMD_APPEND,
   CMD_PREPEND,
   CMD_APPEND_RELATIVE,
   CMD_PREPEND_RELATIVE,
   CMD_SELECT,
   CMD_UNSELECT,
   CMD_RANGE_SELECT,
   CMD_MULTI_SELECT,
   CMD_LABEL_SET,
   CMD_ICON_SET,
   CMD_END_SET,
   CMD_SHOW
};

static Eina_Bool
_queue_timer(void *data)
{
   Evas_Object *obj;
   E_Widget_Data *wd;
   int num;
   double start = ecore_time_get();

   obj = data;
   wd = e_widget_data_get(obj);
   if (!wd) return EINA_FALSE;
   wd->queue.timer = NULL;
   e_widget_ilist_freeze(obj);
   num = 0;
   while (wd->queue.queue)
     {
        E_Widget_Queue_Item *qi;

        qi = eina_list_data_get(wd->queue.queue);
        if (qi->command == CMD_ADD)
          {
             E_Widget_Callback *wcb, *rcb;

             wcb = E_NEW(E_Widget_Callback, 1);
             if (!wcb) break;
             wcb->func = qi->func;
             wcb->data = qi->data;
             if (qi->val) wcb->value = eina_stringshare_add(qi->val);
             if (qi->use_relative == CMD_APPEND)
               {
                  wd->callbacks = eina_list_append(wd->callbacks, wcb);
                  e_ilist_append(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                 _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
               }
             else if (qi->use_relative == CMD_PREPEND)
               {
                  wd->callbacks = eina_list_prepend(wd->callbacks, wcb);
                  e_ilist_prepend(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                  _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
               }
             else if (qi->use_relative == CMD_APPEND_RELATIVE)
               {
                  rcb = eina_list_nth(wd->callbacks, qi->relative);
                  if (rcb)
                    {
                       wd->callbacks = eina_list_append_relative(wd->callbacks, wcb, rcb);
                       e_ilist_append_relative(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                               _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb, qi->relative);
                    }
                  else
                    {
                       wd->callbacks = eina_list_append(wd->callbacks, wcb);
                       e_ilist_append(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                      _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
                    }
               }
             else if (qi->use_relative == CMD_PREPEND_RELATIVE)
               {
                  rcb = eina_list_nth(wd->callbacks, qi->relative);
                  if (rcb)
                    {
                       wd->callbacks = eina_list_prepend_relative(wd->callbacks, wcb, rcb);
                       e_ilist_prepend_relative(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                                _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb, qi->relative);
                    }
                  else
                    {
                       wd->callbacks = eina_list_prepend(wd->callbacks, wcb);
                       e_ilist_prepend(wd->o_ilist, qi->icon, qi->end, qi->label, qi->header,
                                       _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
                    }
               }
             else
               {
                  E_FREE(wcb);
               }
             if (qi->icon) evas_object_show(qi->icon);
             if (qi->end) evas_object_show(qi->end);
          }
        else if (qi->command == CMD_LABEL_SET)
          e_ilist_nth_label_set(wd->o_ilist, qi->item, qi->label);
        else if (qi->command == CMD_ICON_SET)
          e_ilist_nth_icon_set(wd->o_ilist, qi->item, qi->icon);
        else if (qi->command == CMD_SHOW)
          {
             Evas_Coord x, y, w, h;
             if (num > 0) break;

             e_ilist_nth_geometry_get(wd->o_ilist, qi->item, &x, &y, &w, &h);
             if (qi->use_relative)
               e_scrollframe_child_pos_set(wd->o_scrollframe, x, y);
             else
               e_scrollframe_child_region_show(wd->o_scrollframe, x, y + h, w, h);
          }
        else if (qi->command == CMD_SELECT)
          e_ilist_selected_set(wd->o_ilist, qi->item);
        else if (qi->command == CMD_UNSELECT)
          {
             if ((wd->value) && *(wd->value))
               {
                  eina_stringshare_del(*(wd->value));
                  *(wd->value) = NULL;
               }
             e_ilist_unselect(wd->o_ilist);
          }
#if 0
        else if (qi->command == CMD_REMOVE)
          {
             E_Widget_Callback *wcb;
             Eina_List *item;

             e_ilist_remove_num(wd->o_ilist, qi->item);
             item = eina_list_nth_list(wd->callbacks, qi->item);
             if (item)
               {
                  wcb = eina_list_data_get(item);
                  if (wcb && wcb->value) eina_stringshare_del(wcb->value);
                  free(wcb);
                  wd->callbacks = eina_list_remove_list(wd->callbacks, item);
               }
          }
#endif
        else if (qi->command == CMD_MULTI_SELECT)
          e_ilist_multi_select(wd->o_ilist, qi->item);
        else if (qi->command == CMD_RANGE_SELECT)
          e_ilist_range_select(wd->o_ilist, qi->item);
        else if (qi->command == CMD_END_SET)
          e_ilist_nth_end_set(wd->o_ilist, qi->item, qi->end);
        _queue_remove(obj, qi, 0);

        if ((num++ >= 10) && (ecore_time_get() - start > 0.01))
          break;
     }
   e_widget_ilist_thaw(obj);
   e_widget_ilist_go(obj);
   _queue_queue(obj);
   return ECORE_CALLBACK_CANCEL;
}

static void
_queue_queue(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   if (!wd->queue.queue) return;
   if (wd->queue.timer) return;
   wd->queue.timer = ecore_timer_loop_add(0.00001, _queue_timer, obj);
}

static void
_queue_append(Evas_Object *obj, int command, Evas_Object *icon, Evas_Object *end,
              const char *label, int header, void (*func)(void *data), void *data,
              const char *val, int relative, int use_relative, int item)
{
   E_Widget_Data *wd;
   E_Widget_Queue_Item *qi;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   qi = E_NEW(E_Widget_Queue_Item, 1);
   if (!qi) return;
   qi->command = command;
   qi->icon = icon;
   qi->end = end;
   qi->label = eina_stringshare_add(label);
   qi->header = header;
   qi->func = func;
   qi->data = data;
   qi->val = eina_stringshare_add(val);
   qi->relative = relative;
   qi->use_relative = use_relative;
   qi->item = item;
   wd->queue.queue = eina_list_append(wd->queue.queue, qi);
   _queue_queue(obj);
}

static void
_queue_remove(Evas_Object *obj, E_Widget_Queue_Item *qi, int del)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   wd->queue.queue = eina_list_remove(wd->queue.queue, qi);
   if (del)
     {
        if (qi->icon) evas_object_del(qi->icon);
        if (qi->end) evas_object_del(qi->end);
     }
   eina_stringshare_del(qi->label);
   eina_stringshare_del(qi->val);
   free(qi);
}

static void
_queue_clear(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   while (wd->queue.queue)
     _queue_remove(obj, eina_list_data_get(wd->queue.queue), 1);
   if (wd->queue.timer) ecore_timer_del(wd->queue.timer);
   wd->queue.timer = NULL;
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   Eina_Bool disabled;
   E_Widget_Data *wd;

   disabled = e_widget_disabled_get(obj);
   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_disabled_set(wd->o_ilist, disabled);
   evas_object_freeze_events_set(wd->o_scrollframe, disabled);
}

/* externally accessible functions */
E_API Evas_Object *
e_widget_ilist_add(Evas *evas, int icon_w, int icon_h, const char **value)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   wd = E_NEW(E_Widget_Data, 1);
   if (!wd) return NULL;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   e_widget_data_set(obj, wd);

   wd->value = value;

   o = e_scrollframe_add(evas);
   wd->o_scrollframe = o;
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);

   o = e_ilist_add(evas);
   wd->o_ilist = o;
   e_ilist_icon_size_set(o, icon_w, icon_h);
   evas_object_event_callback_add(wd->o_scrollframe, EVAS_CALLBACK_RESIZE,
                                  _e_wid_cb_scrollframe_resize, o);
   e_scrollframe_child_set(wd->o_scrollframe, o);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);
   evas_object_smart_callback_add(o, "selected", _e_wid_cb_selected, obj);

   evas_object_resize(obj, 32, 32);
   e_widget_size_min_set(obj, 32, 32);
   return obj;
}

E_API void
e_widget_ilist_freeze(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_freeze(wd->o_ilist);
}

E_API void
e_widget_ilist_thaw(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_thaw(wd->o_ilist);
}

E_API void
e_widget_ilist_append(Evas_Object *obj, Evas_Object *icon, const char *label, void (*func)(void *data), void *data, const char *val)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 0, func, data, val, 0, CMD_APPEND, 0);
/*
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wcb = E_NEW(E_Widget_Callback, 1);
   if (!wcb) return;

   wd = e_widget_data_get(obj);
   wcb->func = func;
   wcb->data = data;
   if (val) wcb->value = strdup(val);
   wd->callbacks = eina_list_append(wd->callbacks, wcb);
   e_ilist_append(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
   if (icon) evas_object_show(icon);
 */
}

E_API void
e_widget_ilist_append_full(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, void (*func)(void *data), void *data, const char *val)
{
   _queue_append(obj, CMD_ADD, icon, end, label, 0, func, data, val, 0, CMD_APPEND, 0);
}

E_API void
e_widget_ilist_header_append_relative(Evas_Object *obj, Evas_Object *icon, const char *label, int relative)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 1, NULL, NULL, NULL, relative, CMD_APPEND_RELATIVE, 0);
}

E_API void
e_widget_ilist_header_prepend_relative(Evas_Object *obj, Evas_Object *icon, const char *label, int relative)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 1, NULL, NULL, NULL, relative, CMD_PREPEND_RELATIVE, 0);
}

E_API void
e_widget_ilist_append_relative(Evas_Object *obj, Evas_Object *icon, const char *label, void (*func)(void *data), void *data, const char *val, int relative)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 0, func, data, val, relative, CMD_APPEND_RELATIVE, 0);
/*
   E_Widget_Data *wd;
   E_Widget_Callback *wcb, *rcb;

   wcb = E_NEW(E_Widget_Callback, 1);
   if (!wcb) return;

   wd = e_widget_data_get(obj);
   wcb->func = func;
   wcb->data = data;
   if (val) wcb->value = strdup(val);

   rcb = eina_list_nth(wd->callbacks, relative);
   if (rcb)
     {
        wd->callbacks = eina_list_append_relative(wd->callbacks, wcb, rcb);
        e_ilist_append_relative(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb, relative);
     }
   else
     {
        wd->callbacks = eina_list_append(wd->callbacks, wcb);
        e_ilist_append(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
     }

   if (icon) evas_object_show(icon);
 */
}

E_API void
e_widget_ilist_append_relative_full(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, void (*func)(void *data), void *data, const char *val, int relative)
{
   _queue_append(obj, CMD_ADD, icon, end, label, 0, func, data, val, relative, CMD_APPEND_RELATIVE, 0);
}

E_API void
e_widget_ilist_prepend(Evas_Object *obj, Evas_Object *icon, const char *label, void (*func)(void *data), void *data, const char *val)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 0, func, data, val, 0, CMD_PREPEND, 0);
/*
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wcb = E_NEW(E_Widget_Callback, 1);
   if (!wcb) return;

   wd = e_widget_data_get(obj);
   wcb->func = func;
   wcb->data = data;
   if (val) wcb->value = strdup(val);
   wd->callbacks = eina_list_prepend(wd->callbacks, wcb);
   e_ilist_prepend(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
   if (icon) evas_object_show(icon);
 */
}

E_API void
e_widget_ilist_header_prepend(Evas_Object *obj, Evas_Object *icon, const char *label)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 1, NULL, NULL, NULL, 0, CMD_PREPEND, 0);
}

E_API void
e_widget_ilist_prepend_full(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, void (*func)(void *data), void *data, const char *val)
{
   _queue_append(obj, CMD_ADD, icon, end, label, 0, func, data, val, 0, CMD_PREPEND, 0);
}

E_API void
e_widget_ilist_prepend_relative(Evas_Object *obj, Evas_Object *icon, const char *label, void (*func)(void *data), void *data, const char *val, int relative)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 0, func, data, val, relative, CMD_PREPEND_RELATIVE, 0);
/*
   E_Widget_Data *wd;
   E_Widget_Callback *wcb, *rcb;

   wcb = E_NEW(E_Widget_Callback, 1);
   if (!wcb) return;

   wd = e_widget_data_get(obj);
   wcb->func = func;
   wcb->data = data;
   if (val) wcb->value = strdup(val);

   rcb = eina_list_nth(wd->callbacks, relative);
   if (rcb)
     {
        wd->callbacks = eina_list_prepend_relative(wd->callbacks, wcb, rcb);
        e_ilist_prepend_relative(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb, relative);
     }
   else
     {
        wd->callbacks = eina_list_prepend(wd->callbacks, wcb);
        e_ilist_prepend(wd->o_ilist, icon, label, 0, _e_wid_cb_item_sel, _e_wid_cb_item_hilight, wd, wcb);
     }

   if (icon) evas_object_show(icon);
 */
}

E_API void
e_widget_ilist_prepend_relative_full(Evas_Object *obj, Evas_Object *icon, Evas_Object *end, const char *label, void (*func)(void *data), void *data, const char *val, int relative)
{
   _queue_append(obj, CMD_ADD, icon, end, label, 0, func, data, val, relative, CMD_PREPEND_RELATIVE, 0);
}

E_API void
e_widget_ilist_header_append(Evas_Object *obj, Evas_Object *icon, const char *label)
{
   _queue_append(obj, CMD_ADD, icon, NULL, label, 1, NULL, NULL, NULL, 0, CMD_APPEND, 0);
/*
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wcb = E_NEW(E_Widget_Callback, 1);
   if (!wcb) return;

   wd = e_widget_data_get(obj);
   wd->callbacks = eina_list_append(wd->callbacks, wcb);
   e_ilist_append(wd->o_ilist, icon, label, 1, NULL, NULL, NULL, NULL);
   if (icon) evas_object_show(icon);
 */
}

E_API void
e_widget_ilist_selector_set(Evas_Object *obj, int selector)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_selector_set(wd->o_ilist, selector);
}

E_API void
e_widget_ilist_go(Evas_Object *obj)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh, vw, vh, w, h;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   wd->o_widget = obj;
   e_ilist_size_min_get(wd->o_ilist, &mw, &mh);
   evas_object_resize(wd->o_ilist, mw, mh);
   e_scrollframe_child_viewport_size_get(wd->o_scrollframe, &vw, &vh);
   evas_object_geometry_get(wd->o_scrollframe, NULL, NULL, &w, &h);
   if (mw > vw)
     {
        Evas_Coord wmw, wmh;

        e_widget_size_min_get(obj, &wmw, &wmh);
        e_widget_size_min_set(obj, mw + (w - vw), wmh);
     }
   else if (mw < vw)
     evas_object_resize(wd->o_ilist, vw, mh);
}

E_API void
e_widget_ilist_clear(Evas_Object *obj)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   _queue_clear(obj);
   e_ilist_clear(wd->o_ilist);
   e_scrollframe_child_pos_set(wd->o_scrollframe, 0, 0);
   EINA_LIST_FREE(wd->callbacks, wcb)
     {
        eina_stringshare_del(wcb->value);
        free(wcb);
     }
}

E_API int
e_widget_ilist_count(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return 0;

   if (wd->queue.queue)
     {
        E_Widget_Queue_Item *qi;
        Eina_List *l;
        int cnt = 0;

        EINA_LIST_FOREACH(wd->queue.queue, l, qi)
          if (qi->command == CMD_ADD) cnt++;

        return cnt + e_ilist_count(wd->o_ilist);
     }
   else
     return e_ilist_count(wd->o_ilist);
}

E_API const Eina_List *
e_widget_ilist_items_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_items_get(wd->o_ilist);
}

E_API Eina_Bool
e_widget_ilist_nth_is_header(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return EINA_FALSE;
   return e_ilist_nth_is_header(wd->o_ilist, n);
}

E_API void
e_widget_ilist_nth_label_set(Evas_Object *obj, int n, const char *label)
{
   _queue_append(obj, CMD_LABEL_SET, NULL, NULL, label, 0, NULL, NULL, NULL, 0, 0, n);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_ilist_nth_label_set(wd->o_ilist, n, label);
 */
}

E_API const char *
e_widget_ilist_nth_label_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_nth_label_get(wd->o_ilist, n);
}

E_API void
e_widget_ilist_nth_icon_set(Evas_Object *obj, int n, Evas_Object *icon)
{
   _queue_append(obj, CMD_ICON_SET, icon, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, n);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_ilist_nth_icon_set(wd->o_ilist, n, icon);
 */
}

E_API Evas_Object *
e_widget_ilist_nth_icon_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_nth_icon_get(wd->o_ilist, n);
}

E_API void
e_widget_ilist_nth_end_set(Evas_Object *obj, int n, Evas_Object *end)
{
   _queue_append(obj, CMD_END_SET, NULL, end, NULL, 0, NULL, NULL, NULL, 0, 0, n);
}

E_API Evas_Object *
e_widget_ilist_nth_end_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_nth_end_get(wd->o_ilist, n);
}

E_API void *
e_widget_ilist_nth_data_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   wcb = eina_list_nth(wd->callbacks, n);

   if (!wcb)
     return NULL;
   else
     return wcb->data;
}

E_API const char *
e_widget_ilist_nth_value_get(Evas_Object *obj, int n)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   wcb = eina_list_nth(wd->callbacks, n);

   if (!wcb)
     return NULL;
   else
     return wcb->value;
}

/**
 * Return if the given item returned by e_widget_ilist_items_get()
 * is a header.
 *
 * This avoids expensive lookups to the nth element, however it's not
 * able to check any validity on the given pointer and may crash. Be
 * sure to use only with valid return of e_widget_ilist_items_get().
 */
E_API Eina_Bool
e_widget_ilist_item_is_header(const E_Ilist_Item *it)
{
   return it->header;
}

/**
 * Return the label of given item returned by e_widget_ilist_items_get().
 *
 * This avoids expensive lookups to the nth element, however it's not
 * able to check any validity on the given pointer and may crash. Be
 * sure to use only with valid return of e_widget_ilist_items_get().
 */
E_API const char *
e_widget_ilist_item_label_get(const E_Ilist_Item *it)
{
   return it->label;
}

/**
 * Return the icon of given item returned by e_widget_ilist_items_get().
 *
 * This avoids expensive lookups to the nth element, however it's not
 * able to check any validity on the given pointer and may crash. Be
 * sure to use only with valid return of e_widget_ilist_items_get().
 *
 * Do not delete this object!
 */
E_API Evas_Object *
e_widget_ilist_item_icon_get(const E_Ilist_Item *it)
{
   return it->o_icon;
}

/**
 * Return the end of given item returned by e_widget_ilist_items_get().
 *
 * This avoids expensive lookups to the nth element, however it's not
 * able to check any validity on the given pointer and may crash. Be
 * sure to use only with valid return of e_widget_ilist_items_get().
 *
 * Do not delete this object!
 */
E_API Evas_Object *
e_widget_ilist_item_end_get(const E_Ilist_Item *it)
{
   return it->o_end;
}

/**
 * Return the data of given item returned by e_widget_ilist_items_get().
 *
 * This avoids expensive lookups to the nth element, however it's not
 * able to check any validity on the given pointer and may crash. Be
 * sure to use only with valid return of e_widget_ilist_items_get().
 *
 * Do not delete this object!
 */
E_API void *
e_widget_ilist_item_data_get(const E_Ilist_Item *it)
{
   E_Widget_Callback *wcb = it->data2;

   if (!wcb)
     return NULL;
   else
     return wcb->data;
}

E_API const char *
e_widget_ilist_item_value_get(const E_Ilist_Item *it)
{
   E_Widget_Callback *wcb = it->data2;

   if (!wcb)
     return NULL;
   else
     return wcb->value;
}

/**
 * Show the nth element of an ilist
 * @param obj the ilist
 * @param n the number of the element to show
 * @param top if true, place this item at the top, otherwise scroll just
 * enough to show the element (from the current position).
 */
E_API void
e_widget_ilist_nth_show(Evas_Object *obj, int n, int top)
{
   _queue_append(obj, CMD_SHOW, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, top, n);
/*
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h;

   wd = e_widget_data_get(obj);
   e_ilist_nth_geometry_get(wd->o_ilist, n, &x, &y, &w, &h);
   if (top)
     e_scrollframe_child_pos_set(wd->o_scrollframe, x, y);
   else
     e_scrollframe_child_region_show(wd->o_scrollframe, x, y, w, h);
 */
}

E_API void
e_widget_ilist_selected_set(Evas_Object *obj, int n)
{
   _queue_append(obj, CMD_SELECT, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, n);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_ilist_selected_set(wd->o_ilist, n);
 */
}

E_API const Eina_List *
e_widget_ilist_selected_items_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_selected_items_get(wd->o_ilist);
}

E_API int
e_widget_ilist_selected_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return 0;
   return e_ilist_selected_get(wd->o_ilist);
}

E_API const char *
e_widget_ilist_selected_label_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_selected_label_get(wd->o_ilist);
}

E_API Evas_Object *
e_widget_ilist_selected_icon_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_selected_icon_get(wd->o_ilist);
}

E_API void *
e_widget_ilist_selected_data_get(Evas_Object *obj)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   wcb = eina_list_nth(wd->callbacks, e_ilist_selected_get(wd->o_ilist));

   return wcb ? wcb->data : NULL;
}

E_API Evas_Object *
e_widget_ilist_selected_end_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   return e_ilist_selected_end_get(wd->o_ilist);
}

E_API int
e_widget_ilist_selected_count_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return 0;
   return e_ilist_selected_count_get(wd->o_ilist);
}

E_API const char *
e_widget_ilist_selected_value_get(Evas_Object *obj)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return NULL;
   wcb = eina_list_nth(wd->callbacks, e_ilist_selected_get(wd->o_ilist));

   if (!wcb)
     return NULL;
   else
     return wcb->value;
}

E_API void
e_widget_ilist_unselect(Evas_Object *obj)
{
   _queue_append(obj, CMD_UNSELECT, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, 0);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if ((wd->value) && *(wd->value))
       {
          free(*(wd->value));
 *(wd->value) = NULL;
       }
   e_ilist_unselect(wd->o_ilist);
 */
}

E_API void
e_widget_ilist_remove_num(Evas_Object *obj, int n)
{
/*    _queue_append(obj, CMD_REMOVE, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, n); */
   E_Widget_Callback *wcb;
   E_Widget_Data *wd;
   Eina_List *item;

   if (n < 0) return;
   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_remove_num(wd->o_ilist, n);
   item = eina_list_nth_list(wd->callbacks, n);
   if (item)
     {
        wcb = eina_list_data_get(item);
        if (wcb) eina_stringshare_del(wcb->value);
        free(wcb);
        wd->callbacks = eina_list_remove_list(wd->callbacks, item);
     }
}

E_API void
e_widget_ilist_multi_select_set(Evas_Object *obj, Eina_Bool multi)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   e_ilist_multi_select_set(wd->o_ilist, multi);
}

E_API Eina_Bool
e_widget_ilist_multi_select_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return EINA_FALSE;
   return e_ilist_multi_select_get(wd->o_ilist);
}

E_API void
e_widget_ilist_multi_select(Evas_Object *obj, int n)
{
   _queue_append(obj, CMD_MULTI_SELECT, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, n);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_ilist_multi_select(wd->o_ilist, n);
 */
}

E_API void
e_widget_ilist_range_select(Evas_Object *obj, int n)
{
   _queue_append(obj, CMD_RANGE_SELECT, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, n);
/*
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_ilist_range_select(wd->o_ilist, n);
 */
}

E_API Eina_Bool
e_widget_ilist_custom_edje_file_set(Evas_Object *obj, const char *file, const char *group)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return EINA_FALSE;
   if (group)
     {
        char buf[1024];

        snprintf(buf, sizeof(buf), "%s/scrollframe", group);
        e_scrollframe_custom_edje_file_set(wd->o_scrollframe, file, buf);
     }
   return e_ilist_custom_edje_file_set(wd->o_ilist, file, group);
}

E_API void
e_widget_ilist_preferred_size_get(Evas_Object *obj, Evas_Coord *w, Evas_Coord *h)
{
   Evas_Coord ww, hh, mw, mh, vw, vh;
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   evas_object_geometry_get(wd->o_scrollframe, NULL, NULL, &ww, &hh);
   evas_object_resize(wd->o_scrollframe, 200, 200);
   e_scrollframe_child_viewport_size_get(wd->o_scrollframe, &vw, &vh);
   e_ilist_size_min_get(wd->o_ilist, &mw, &mh);
   evas_object_resize(wd->o_scrollframe, ww, hh);
   if (w) *w = 200 - vw + mw;
   if (h) *h = 200 - vh + mh;
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;
   E_Widget_Callback *wcb;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   _queue_clear(obj);
   EINA_LIST_FREE(wd->callbacks, wcb)
     {
        if (wcb) eina_stringshare_del(wcb->value);
        free(wcb);
     }
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (!wd) return;
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scrollframe), "e,state,focused", "e");
        evas_object_focus_set(wd->o_ilist, 1);
     }
   else
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scrollframe), "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_ilist, 0);
     }
}

static void
_e_wid_cb_scrollframe_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Evas_Coord mw, mh, vw, vh, w, h;

   e_scrollframe_child_viewport_size_get(obj, &vw, &vh);
   e_ilist_size_min_get(data, &mw, &mh);
   evas_object_geometry_get(data, NULL, NULL, &w, &h);
   if (vw >= mw)
     {
        if (w != vw) evas_object_resize(data, vw, h);
        evas_object_smart_callback_call(data, "changed", obj);
     }
}

static void
_e_wid_cb_item_sel(void *data, void *data2)
{
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h;
   E_Widget_Callback *wcb;

   wd = data;
   wcb = data2;
   e_ilist_selected_geometry_get(wd->o_ilist, &x, &y, &w, &h);
   e_scrollframe_child_region_show(wd->o_scrollframe, x, y, w, h);
   if (wd->o_widget)
     {
        if (wd->value)
          {
             if (*(wd->value)) eina_stringshare_del(*(wd->value));
             if (wcb->value)
               *(wd->value) = eina_stringshare_ref(wcb->value);
             else
               *(wd->value) = NULL;
          }
        if (wcb->func) wcb->func(wcb->data);
        e_widget_change(wd->o_widget);
     }
}

static void
_e_wid_cb_item_hilight(void *data, void *data2 EINA_UNUSED)
{
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h;

   wd = data;
   e_ilist_selected_geometry_get(wd->o_ilist, &x, &y, &w, &h);
   e_scrollframe_child_region_show(wd->o_scrollframe, x, y, w, h);
}

static void
_e_wid_cb_selected(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   evas_object_smart_callback_call(data, "selected", event_info);
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *img;
   int          minw, minh;
};

static void _e_wid_del_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_image_add_from_object(Evas *evas, Evas_Object *object, int minw, int minh)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   int mw = 0, mh = 0;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = calloc(1, sizeof(E_Widget_Data));

   evas_object_show(object);
   o = e_icon_add(evas);
   e_icon_fill_inside_set(o, 1);
   e_icon_edje_object_set(o, object);
   wd->img = o;
   e_widget_data_set(obj, wd);
   e_widget_can_focus_set(obj, 0);
   wd->minw = minw <= 0 ? minw : 0;
   wd->minh = minh <= 0 ? minh : 0;
   if (isedje(object))
     edje_object_size_min_get(object, &mw, &mh);
   else
     evas_object_size_hint_min_get(object, &mw, &mh);
   if (minw || minh)
     e_widget_size_min_set(obj, minw, minh);
   else
     e_widget_size_min_set(obj, mw, mh);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   return obj;
}

E_API Evas_Object *
e_widget_image_add_from_file(Evas *evas, const char *file, int minw, int minh)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   if (!ecore_file_exists(file)) return NULL;
   obj = e_widget_add(evas);
   wd = calloc(1, sizeof(E_Widget_Data));
   if (!wd) return NULL;

   o = e_icon_add(evas);
   e_icon_fill_inside_set(o, 1);
   e_icon_file_set(o, file);

   wd->img = o;
   evas_object_show(o);
   e_widget_data_set(obj, wd);
   e_widget_can_focus_set(obj, 0);
   e_widget_size_min_set(obj, minw, minh);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   return obj;
}

E_API void
e_widget_image_edje_set(Evas_Object *obj, const char *file, const char *part)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_icon_file_edje_set(wd->img, file, part);
   evas_object_show(wd->img);
}

E_API void
e_widget_image_file_set(Evas_Object *obj, const char *file)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_icon_file_set(wd->img, file);
   evas_object_show(wd->img);
}

E_API void
e_widget_image_file_key_set(Evas_Object *obj, const char *file, const char *key)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_icon_file_key_set(wd->img, file, key);
   evas_object_show(wd->img);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *text;
};

/* local subsystem functions */
static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);

/* externally accessible functions */
/**
 * Creates a new label widget
 *
 * @param evas pointer
 * @param text to assign to the widget
 * @return the label widget
 */
E_API Evas_Object *
e_widget_label_add(Evas *evas, const char *label)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = elm_label_add(e_win_evas_win_get(evas));
   wd->text = o;
   evas_object_show(o);
   e_widget_can_focus_set(obj, 0);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   elm_object_text_set(o, label);

   return obj;
}

/**
 * Changes the text for the label
 *
 * @param the label widget
 * @param text to assign to the widget
 */
E_API void
e_widget_label_text_set(Evas_Object *obj, const char *text)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   if (!wd) return;
   elm_object_text_set(wd->text, text);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   free(wd);
   if (!wd) return;
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   if (!wd) return;
   elm_object_disabled_set(wd->text, e_widget_disabled_get(obj));
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_box;
};

static void _e_wid_del_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
/**
 * Creates a new list widget
 *
 * @param evas the evas pointer
 * @param homogenous should widgets append to the list be evenly spaced out
 * @param horiz the direction the list should be displayed
 * @return the new list wdiget
 */
E_API Evas_Object *
e_widget_list_add(Evas *evas, int homogenous, int horiz)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = elm_box_add(e_win_evas_win_get(evas));
   wd->o_box = o;
   elm_box_horizontal_set(o, horiz);
   elm_box_homogeneous_set(o, homogenous);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   return obj;
}

/**
 * Prepend a widget to the list
 *
 * @param obj the list widget to prepend the sub widget too
 * @param sobj the sub widget
 * @param fill DOCUMENT ME!
 * @param expand DOCUMENT ME!
 * @param align who the sub widget to be aligned, to wards the center or sides
 * @return the new list wdiget
 */
E_API void
e_widget_list_object_prepend(Evas_Object *obj, Evas_Object *sobj, int fill, int expand, double align)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   wd = e_widget_data_get(obj);

   mw = mh = 0;
   if (fill) align = -1;
   if (elm_box_horizontal_get(wd->o_box) == 1)
     {
        E_ALIGN(sobj, -1, align);
        E_WEIGHT(sobj, expand, 1);
     }
   else
     {
        E_ALIGN(sobj, align, -1);
        E_WEIGHT(sobj, 1, expand);
     }
   elm_box_pack_start(wd->o_box, sobj);
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

/**
 * Append a widget to the list
 *
 * @param obj the list widget to append the sub widget too
 * @param sobj the sub widget
 * @param fill DOCUMENT ME!
 * @param expand DOCUMENT ME!
 * @param align who the sub widget to be aligned, to wards the center or sides
 * @return the new list wdiget
 */
E_API void
e_widget_list_object_append(Evas_Object *obj, Evas_Object *sobj, int fill, int expand, double align)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   wd = e_widget_data_get(obj);

   mw = mh = 0;
   if (fill) align = -1;
   if (elm_box_horizontal_get(wd->o_box) == 1)
     {
        E_ALIGN(sobj, -1, align);
        E_WEIGHT(sobj, expand, 1);
     }
   else
     {
        E_ALIGN(sobj, align, -1);
        E_WEIGHT(sobj, 1, expand);
     }
   elm_box_pack_end(wd->o_box, sobj);
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

E_API void
e_widget_list_object_repack(Evas_Object *obj, Evas_Object *sobj, int fill, int expand, double align)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   wd = e_widget_data_get(obj);

   mw = mh = 0;
   if (fill) align = -1;
   if (elm_box_horizontal_get(wd->o_box) == 1)
     {
        E_ALIGN(sobj, -1, align);
        E_WEIGHT(sobj, expand, 1);
     }
   else
     {
        E_ALIGN(sobj, align, -1);
        E_WEIGHT(sobj, 1, expand);
     }
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
}

E_API void
e_widget_list_homogeneous_set(Evas_Object *obj, int homogenous)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   elm_box_homogeneous_set(wd->o_box, homogenous);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *obj, *o_frame, *img, *o_thumb, *o_extern;
};

/* local subsystem functions */
static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_preview_thumb_gen(void *data, Evas_Object *obj, void *event_info);

/* externally accessible functions */
E_API Evas_Object *
e_widget_preview_add(Evas *evas, int minw, int minh)
{
   Evas_Object *obj;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);

   wd = E_NEW(E_Widget_Data, 1);
   if (!wd) return NULL;

   wd->obj = obj;

   wd->o_frame = edje_object_add(evas);
   e_theme_edje_object_set(wd->o_frame, "base/theme/widgets",
                           "e/widgets/preview");
   evas_object_show(wd->o_frame);
   e_widget_sub_object_add(obj, wd->o_frame);
   e_widget_resize_object_set(obj, wd->o_frame);

   wd->img = e_livethumb_add(evas);
   e_livethumb_vsize_set(wd->img, minw * 2, minh * 2);
   evas_object_size_hint_min_set(wd->img, minw, minh);
   /* Why do we set max size? This prevents from being nicely resized. */
   /* evas_object_size_hint_max_set(o, minw, minh); */
   evas_object_show(wd->img);
   e_widget_sub_object_add(obj, wd->img);
   edje_object_part_swallow(wd->o_frame, "e.swallow.content", wd->img);

   e_widget_data_set(obj, wd);
   e_widget_can_focus_set(obj, 0);
   edje_object_size_min_calc(wd->o_frame, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);

   return obj;
}

E_API void
e_widget_preview_size_set(Evas_Object *obj, int minw, int minh)
{
   E_Widget_Data *wd;
   int mw, mh;

   wd = e_widget_data_get(obj);
   e_livethumb_vsize_set(wd->img, minw * 2, minh * 2);
   evas_object_size_hint_min_set(wd->img, minw, minh);
   edje_object_size_min_calc(wd->o_frame, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
}

E_API Evas *
e_widget_preview_evas_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   return e_livethumb_evas_get(wd->img);
}

E_API void
e_widget_preview_extern_object_set(Evas_Object *obj, Evas_Object *eobj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   wd->o_extern = eobj;
   e_livethumb_thumb_set(wd->img, wd->o_extern);

   e_widget_change(obj);
}

E_API int
e_widget_preview_file_set(Evas_Object *obj, const char *file, const char *key)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->o_thumb) evas_object_del(wd->o_thumb);
   wd->o_thumb = e_icon_add(e_livethumb_evas_get(wd->img));
   e_icon_fill_inside_set(wd->o_thumb, 0);
   e_icon_file_key_set(wd->o_thumb, file, key);
   evas_object_show(wd->o_thumb);
   e_livethumb_thumb_set(wd->img, wd->o_thumb);

   e_widget_change(obj);

   return 1;
}

E_API int
e_widget_preview_thumb_set(Evas_Object *obj, const char *file, const char *key, int w, int h)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->o_thumb) evas_object_del(wd->o_thumb);
   wd->o_thumb = e_thumb_icon_add(e_livethumb_evas_get(wd->img));
   if (eina_str_has_extension(file, "edj"))
     {
        if (key)
          e_thumb_icon_file_set(wd->o_thumb, file, key);
        else
        /* FIXME: There is probably a quicker way of doing this. */
        if (edje_file_group_exists(file, "icon"))
          e_thumb_icon_file_set(wd->o_thumb, file, "icon");
        else if (edje_file_group_exists(file, "e/desktop/background"))
          e_thumb_icon_file_set(wd->o_thumb, file, "e/desktop/background");
        else if (edje_file_group_exists(file, "e/init/splash"))
          e_thumb_icon_file_set(wd->o_thumb, file, "e/init/splash");
     }
   else
     e_thumb_icon_file_set(wd->o_thumb, file, NULL);
   evas_object_smart_callback_add(wd->o_thumb, "e_thumb_gen",
                                  _e_wid_preview_thumb_gen, wd);
   e_thumb_icon_size_set(wd->o_thumb, w, h);
   e_thumb_icon_begin(wd->o_thumb);
   e_livethumb_thumb_set(wd->img, wd->o_thumb);

   e_widget_change(obj);

   return 1;
}

E_API void
e_widget_preview_vsize_set(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_livethumb_vsize_set(wd->img, w, h);
   e_widget_size_min_set(wd->obj, w, h);
   evas_object_smart_callback_call(wd->obj, "preview_update", NULL);
   e_widget_change(wd->obj);
}

static void
_e_wid_preview_thumb_gen(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   evas_object_show(wd->o_thumb);
   evas_object_smart_callback_call(wd->obj, "preview_update", NULL);
   e_widget_change(wd->obj);
}

E_API void
e_widget_preview_file_get(Evas_Object *obj, const char **file, const char **group)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   edje_object_file_get(wd->o_thumb, file, group);
}

E_API int
e_widget_preview_edje_set(Evas_Object *obj, const char *file, const char *group)
{
   E_Widget_Data *wd;
   int ret;

   wd = e_widget_data_get(obj);
   if (wd->o_thumb) evas_object_del(wd->o_thumb);
   wd->o_thumb = edje_object_add(e_livethumb_evas_get(wd->img));
   ret = edje_object_file_set(wd->o_thumb, file, group);
   e_livethumb_thumb_set(wd->img, wd->o_thumb);
   e_widget_change(obj);
   return ret;
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   E_FREE(wd);
}

#include "e.h"

struct _E_Radio_Group
{
   int       *valptr;
   Eina_List *radios;
};

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   E_Radio_Group *group;
   Evas_Object   *o_radio;
   Evas_Object   *o_icon;
   int            valnum;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_do(Evas_Object *obj);
static void _e_wid_activate_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _e_wid_signal_cb1(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
/* local subsystem functions */

/* externally accessible functions */
E_API E_Radio_Group *
e_widget_radio_group_new(int *val)
{
   E_Radio_Group *group;

   group = calloc(1, sizeof(E_Radio_Group));
   group->valptr = val;
   return group;
}

E_API Evas_Object *
e_widget_radio_add(Evas *evas, const char *label, int valnum, E_Radio_Group *group)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_activate_hook_set(obj, _e_wid_activate_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);

   wd = calloc(1, sizeof(E_Widget_Data));
   wd->group = group;
   wd->valnum = valnum;
   e_widget_data_set(obj, wd);

   o = edje_object_add(evas);
   wd->o_radio = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/radio");
   edje_object_signal_callback_add(o, "e,action,toggle", "*",
                                   _e_wid_signal_cb1, obj);
   edje_object_part_text_set(o, "e.text.label", label);
   evas_object_show(o);
   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   if ((wd->group) && (wd->group->valptr))
     {
        if (*(wd->group->valptr) == valnum)
          edje_object_signal_emit(o, "e,state,on", "e");
     }
   if (wd->group)
     {
        wd->group->radios = eina_list_append(wd->group->radios, obj);
     }

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);

   return obj;
}

E_API Evas_Object *
e_widget_radio_icon_add(Evas *evas, const char *label, const char *icon, int icon_w, int icon_h, int valnum, E_Radio_Group *group)
{
   Evas_Object *obj, *o, *o2;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_activate_hook_set(obj, _e_wid_activate_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);

   wd = calloc(1, sizeof(E_Widget_Data));
   wd->group = group;
   wd->valnum = valnum;
   e_widget_data_set(obj, wd);

   o = edje_object_add(evas);
   wd->o_radio = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/radio_icon");
   edje_object_signal_callback_add(o, "e,action,toggle", "*",
                                   _e_wid_signal_cb1, obj);
   edje_object_part_text_set(o, "e.text.label", label);
   evas_object_show(o);

   if (label)
     {
        edje_object_signal_emit(o, "e,state,labeled", "e");
        edje_object_message_signal_process(o);
     }

   if (icon)
     {
        o2 = e_icon_add(evas);
        wd->o_icon = o2;
        e_util_icon_theme_set(o2, icon);
        evas_object_size_hint_min_set(o2, icon_w, icon_h);
        edje_object_part_swallow(wd->o_radio, "e.swallow.icon", o2);
        evas_object_show(o2);
        e_widget_sub_object_add(obj, o2);
     }

   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);
   if ((wd->group) && (wd->group->valptr))
     {
        if (*(wd->group->valptr) == valnum)
          edje_object_signal_emit(o, "e,state,on", "e");
     }
   if (wd->group)
     wd->group->radios = eina_list_append(wd->group->radios, obj);

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);

   return obj;
}

E_API void
e_widget_radio_toggle_set(Evas_Object *obj, int toggle)
{
   E_Widget_Data *wd;

   if (!(wd = e_widget_data_get(obj))) return;

   if (toggle)
     {
        Eina_List *l;
        Evas_Object *sobj;

        EINA_LIST_FOREACH(wd->group->radios, l, sobj)
          {
             if (sobj != obj)
               {
                  wd = e_widget_data_get(sobj);
                  if (wd->valnum == *(wd->group->valptr))
                    {
                       edje_object_signal_emit(wd->o_radio, "e,state,off", "e");
                       break;
                    }
               }
          }
        wd = e_widget_data_get(obj);
        *(wd->group->valptr) = wd->valnum;
        edje_object_signal_emit(wd->o_radio, "e,state,on", "e");
     }
   else
     edje_object_signal_emit(wd->o_radio, "e,state,off", "e");
   evas_object_smart_callback_call(obj, "changed", NULL);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->group)
     {
        wd->group->radios = eina_list_remove(wd->group->radios, obj);
        if (!wd->group->radios) free(wd->group);
     }
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(wd->o_radio, "e,state,focused", "e");
        evas_object_focus_set(wd->o_radio, 1);
     }
   else
     {
        edje_object_signal_emit(wd->o_radio, "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_radio, 0);
     }
}

static void
_e_wid_do(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (e_widget_disabled_get(obj)) return;

   wd = e_widget_data_get(obj);
   if ((wd->group) && (wd->group->valptr))
     {
        Eina_List *l;
        Evas_Object *sobj;
        int toggled = 0;

        EINA_LIST_FOREACH(wd->group->radios, l, sobj)
          {
             if (sobj != obj)
               {
                  wd = e_widget_data_get(sobj);
                  if (wd->valnum == *(wd->group->valptr))
                    {
                       edje_object_signal_emit(wd->o_radio, "e,state,off", "e");
                       toggled = 1;
                       break;
                    }
               }
          }
        wd = e_widget_data_get(obj);
        if (!toggled)
          {
             if (wd->valnum == *(wd->group->valptr)) return;
          }
        *(wd->group->valptr) = wd->valnum;
        edje_object_signal_emit(wd->o_radio, "e,state,on", "e");
     }
   evas_object_smart_callback_call(obj, "changed", NULL);
}

static void
_e_wid_activate_hook(Evas_Object *obj)
{
   _e_wid_do(obj);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_disabled_get(obj))
     edje_object_signal_emit(wd->o_radio, "e,state,disabled", "e");
   else
     edje_object_signal_emit(wd->o_radio, "e,state,enabled", "e");
}

static void
_e_wid_signal_cb1(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _e_wid_do(data);
   e_widget_change(data);
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_scrollframe, *o_child, *o_fobj;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_cb_scrollframe_resize(void *data, Evas *e, Evas_Object *obj,
                                         void *event_info);

/* externally accessible functions */
E_API Evas_Object *
e_widget_scrollframe_simple_add(Evas *evas, Evas_Object *child)
{
   E_Widget_Data *wd;
   Evas_Object *obj, *o;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = e_scrollframe_add(evas);
   e_scrollframe_policy_set(o, E_SCROLLFRAME_POLICY_AUTO,
                            E_SCROLLFRAME_POLICY_AUTO);
   wd->o_scrollframe = o;
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);

   e_scrollframe_child_set(wd->o_scrollframe, child);
   evas_object_show(child);
   wd->o_child = child;
   e_widget_sub_object_add(obj, child);
   evas_object_event_callback_add(wd->o_scrollframe, EVAS_CALLBACK_RESIZE,
                                  _e_wid_cb_scrollframe_resize, wd->o_child);

   return obj;
}

E_API Evas_Object *
e_widget_scrollframe_pan_add(Evas *evas, Evas_Object *pan, void (*pan_set)(Evas_Object *obj, Evas_Coord x, Evas_Coord y), void (*pan_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y), void (*pan_max_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y), void (*pan_child_size_get)(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y))
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = e_scrollframe_add(evas);
   wd->o_scrollframe = o;
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);

   e_scrollframe_extern_pan_set(o, pan, pan_set, pan_get, pan_max_get,
                                pan_child_size_get);
   evas_object_show(pan);
   e_widget_sub_object_add(obj, pan);

   return obj;
}

E_API void
e_widget_scrollframe_child_pos_set(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_scrollframe_child_pos_set(wd->o_scrollframe, x, y);
}

E_API void
e_widget_scrollframe_child_pos_get(Evas_Object *obj, Evas_Coord *x, Evas_Coord *y)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_scrollframe_child_pos_get(wd->o_scrollframe, x, y);
}

E_API void
e_widget_scrollframe_child_region_show(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_scrollframe_child_region_show(wd->o_scrollframe, x, y, w, h);
}

E_API void
e_widget_scrollframe_focus_object_set(Evas_Object *obj, Evas_Object *fobj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   wd->o_fobj = fobj;
}

E_API Evas_Object *
e_widget_scrollframe_object_get(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   return wd->o_scrollframe;
}

/* Private functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scrollframe), "e,state,focused", "e");
        if (wd->o_fobj) evas_object_focus_set(wd->o_fobj, 1);
        else evas_object_focus_set(wd->o_scrollframe, 1);
     }
   else
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_scrollframe), "e,state,unfocused", "e");
        if (wd->o_fobj) evas_object_focus_set(wd->o_fobj, 0);
        evas_object_focus_set(wd->o_scrollframe, 0);
     }
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_e_wid_cb_scrollframe_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Evas_Coord mw = 0, mh = 0;
   Evas_Coord vw = 0, vh = 0;
   Evas_Coord w = 0, h = 0;

   e_scrollframe_child_viewport_size_get(obj, &vw, &vh);
   e_widget_size_min_get(data, &mw, &mh);
   evas_object_geometry_get(data, NULL, NULL, &w, &h);
   if (vw >= mw)
     {
        if (w != vw) evas_object_resize(data, vw, h);
     }
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_widget, *o_slider;
   double      *dval;
   int         *ival;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_cb_changed(void *data, Evas_Object *obj, void *event_info);
static void _e_wid_disable_hook(Evas_Object *obj);

/* externally accessible functions */
E_API Evas_Object *
e_widget_slider_add(Evas *evas, int horiz, int rev, const char *fmt, double min, double max, double step, int count, double *dval, int *ival, Evas_Coord size)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);
   wd->o_widget = obj;

   o = e_slider_add(evas);
   wd->o_slider = o;
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);

   e_slider_orientation_set(o, horiz);
   e_slider_direction_set(o, rev);
   e_slider_value_range_set(o, min, max);
   e_slider_value_format_display_set(o, fmt);
   e_slider_value_step_count_set(o, count);
   e_slider_value_step_size_set(o, step);
   if (dval) e_slider_value_set(o, *dval);
   else if (ival)
     e_slider_value_set(o, *ival);

   e_slider_size_min_get(o, &mw, &mh);
   if (horiz)
     e_widget_size_min_set(obj, mw + size, mh);
   else
     e_widget_size_min_set(obj, mw, mh + size);

   wd->dval = dval;
   wd->ival = ival;
   evas_object_smart_callback_add(o, "changed", _e_wid_cb_changed, wd);

   return obj;
}

/**
 * Set the double value for the slider. This will also move the slider to
 * the correct position and update the text indicator. Will not do anything
 * if the slider was not created with a double value.
 *
 * @param slider pointer to the slider to be modified
 * @param dval the double value to set the slider to
 * @return 1 if value set, return 0 if value not set
 */
E_API int
e_widget_slider_value_double_set(Evas_Object *slider, double dval)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   if (!wd->dval) return 0;
   *(wd->dval) = dval;
   e_slider_value_set(wd->o_slider, dval);
   return 1;
}

/**
 * Set the integer value for the slider. This will also move the slider to
 * the correct position and update the text indicator. Will not do anything
 * if the slider was not created with an integer value.
 *
 * @param slider pointer to the slider to be modified
 * @param ival the integer value to set the slider to
 * @return 1 if value set, return 0 if value not set
 */
E_API int
e_widget_slider_value_int_set(Evas_Object *slider, int ival)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   if (!wd->ival) return 0;
   *(wd->ival) = ival;
   e_slider_value_set(wd->o_slider, ival);
   return 1;
}

/**
 * Get the double value for the slider. The value of dval is undefined
 * if the slider was not created with a double value.
 *
 * @param slider pointer to the slider to be queried
 * @param dval the pointer to the double value to be set with the value of the slider
 * @return 1 if value returned, return 0 if value not returned
 */
E_API int
e_widget_slider_value_double_get(Evas_Object *slider, double *dval)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   if (!wd->dval) return 0;
   if (!dval) return 0;

   *dval = *(wd->dval);
   return 1;
}

/**
 * Get the integer value for the slider. The value of ival is undefined
 * if the slider was not created with an integer value.
 *
 * @param slider pointer to the slider to be queried
 * @param ival the pointer to the integer value to be set with the value of the slider
 * @return 1 if value returned, return 0 if value not returned
 */
E_API int
e_widget_slider_value_int_get(Evas_Object *slider, int *ival)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   if (!wd->ival) return 0;
   if (!ival) return 0;

   *ival = *(wd->ival);
   return 1;
}

E_API void
e_widget_slider_value_format_display_set(Evas_Object *slider, const char *format)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   e_slider_value_format_display_set(wd->o_slider, format);
}

E_API void
e_widget_slider_value_range_set(Evas_Object *slider, double min, double max)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   e_slider_value_range_set(wd->o_slider, min, max);
}

E_API void
e_widget_slider_value_step_size_set(Evas_Object *slider, double step_size)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(slider);
   e_slider_value_step_size_set(wd->o_slider, step_size);
}

E_API void
e_widget_slider_special_value_add(Evas_Object *obj, double value, double error, const char *label)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   e_slider_special_value_add(wd->o_slider, value, error, label);
}

/* Private functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(e_slider_edje_object_get(wd->o_slider), "e,state,focused", "e");
        evas_object_focus_set(wd->o_slider, 1);
     }
   else
     {
        edje_object_signal_emit(e_slider_edje_object_get(wd->o_slider), "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_slider, 0);
     }
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_e_wid_cb_changed(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;

   wd = data;
   if (wd->dval) *(wd->dval) = e_slider_value_get(wd->o_slider);
   else if (wd->ival)
     *(wd->ival) = e_slider_value_get(wd->o_slider);
   e_widget_change(wd->o_widget);
   evas_object_smart_callback_call(wd->o_widget, "changed", NULL);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;
   Eina_Bool disable;

   wd = e_widget_data_get(obj);
   disable = e_widget_disabled_get(obj);
   e_slider_disabled_set(wd->o_slider, disable);
   if (disable)
     edje_object_signal_emit(e_slider_edje_object_get(wd->o_slider),
                             "e,state,disabled", "e");
   else
     edje_object_signal_emit(e_slider_edje_object_get(wd->o_slider),
                             "e,state,enabled", "e");
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;

struct _E_Widget_Data
{
   Evas_Object      *o_edje;
   Evas_Object      *o_spectrum;
   Evas_Object      *o_event;

   E_Color          *cv;
   E_Color_Component mode;

   int               dragging;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _e_wid_resize(void *data, Evas_Object *o, Evas_Coord w, Evas_Coord h);
static void _e_wid_move(void *data, Evas_Object *o, Evas_Coord x, Evas_Coord y);

static void _e_wid_mouse_handle(Evas_Object *obj, int mx, int my);
static void _e_wid_cb_down(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_cb_move(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_cb_up(void *data, Evas *e, Evas_Object *obj, void *event_info);

Evas_Object *
e_widget_spectrum_add(Evas *evas, E_Color_Component mode, E_Color *cv)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw, mh;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);

   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   wd->cv = cv;
   wd->mode = mode;

   o = edje_object_add(evas);
   wd->o_edje = o;
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/spectrum");

   evas_object_show(o);
   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);

   e_widget_sub_object_add(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);
   e_widget_resize_object_set(obj, o);
   evas_object_intercept_resize_callback_add(o, _e_wid_resize, wd);
   evas_object_intercept_move_callback_add(o, _e_wid_move, wd);

   o = e_spectrum_add(evas);
   e_spectrum_color_value_set(o, cv);
   e_spectrum_mode_set(o, mode);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);
   wd->o_spectrum = o;
   edje_object_part_swallow(wd->o_edje, "e.swallow.content", o);

   o = evas_object_rectangle_add(evas);
   evas_object_repeat_events_set(o, EINA_TRUE);
   evas_object_color_set(o, 0, 0, 0, 0);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_cb_down, obj);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, _e_wid_cb_move, obj);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, _e_wid_cb_up, obj);
   wd->o_event = o;

   e_widget_spectrum_update(obj, 0);

   return obj;
}

void
e_widget_spectrum_update(Evas_Object *obj, int redraw)
{
   E_Widget_Data *wd;
   double vx = 0, vy = 0;

   wd = e_widget_data_get(obj);
   /* redraw spectrum */
   if (redraw)
     e_spectrum_update(wd->o_spectrum);

   switch (wd->mode)
     {
      case E_COLOR_COMPONENT_R:
        vy = wd->cv->g / 255.0;
        vx = wd->cv->b / 255.0;
        break;

      case E_COLOR_COMPONENT_G:
        vy = wd->cv->b / 255.0;
        vx = wd->cv->r / 255.0;
        break;

      case E_COLOR_COMPONENT_B:
        vy = wd->cv->r / 255.0;
        vx = wd->cv->g / 255.0;
        break;

      case E_COLOR_COMPONENT_H:
        vy = wd->cv->s;
        vx = wd->cv->v;
        break;

      case E_COLOR_COMPONENT_S:
        vy = wd->cv->v;
        vx = wd->cv->h / 360.0;
        break;

      case E_COLOR_COMPONENT_V:
        vy = wd->cv->h / 360.0;
        vx = wd->cv->s;
        break;

      case E_COLOR_COMPONENT_MAX:
        break;
     }
   edje_object_part_drag_value_set(wd->o_edje, "cursor", vx, vy);
}

void
e_widget_spectrum_mode_set(Evas_Object *obj, E_Color_Component mode)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (wd->mode == mode) return;
   wd->mode = mode;
   e_spectrum_mode_set(wd->o_spectrum, mode);
   e_widget_spectrum_update(obj, 0);
}

static void
_e_wid_move(void *data, Evas_Object *o EINA_UNUSED, Evas_Coord x, Evas_Coord y)
{
   E_Widget_Data *wd;

   wd = data;
   evas_object_move(wd->o_edje, x, y);
   evas_object_move(wd->o_event, x, y);
}

static void
_e_wid_resize(void *data, Evas_Object *o EINA_UNUSED, Evas_Coord w, Evas_Coord h)
{
   E_Widget_Data *wd;

   wd = data;
   evas_object_resize(wd->o_edje, w, h);
   evas_object_resize(wd->o_event, w, h);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(wd->o_edje, "e,state,focused", "e");
        evas_object_focus_set(wd->o_edje, 1);
     }
   else
     {
        edje_object_signal_emit(wd->o_edje, "e,state,unfocused", "e");
        evas_object_focus_set(wd->o_edje, 0);
     }
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   if (e_widget_disabled_get(obj))
     edje_object_signal_emit(wd->o_spectrum, "e,state,disabled", "e");
   else
     edje_object_signal_emit(wd->o_spectrum, "e,state,enabled", "e");
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_e_wid_mouse_handle(Evas_Object *obj, int mx, int my)
{
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h;
   double vx, vy;

   wd = e_widget_data_get(obj);

   evas_object_geometry_get(wd->o_spectrum, &x, &y, &w, &h);
   vx = (mx - x) / (double)w;
   vy = (my - y) / (double)h;
   if (vx > 1) vx = 1;
   if (vx < 0) vx = 0;
   if (vy > 1) vy = 1;
   if (vy < 0) vy = 0;

   edje_object_part_drag_value_set(wd->o_edje, "e.dragable.cursor", vx, vy);

   switch (wd->mode)
     {
      case E_COLOR_COMPONENT_R:
        wd->cv->g = vy * 255;
        wd->cv->b = vx * 255;
        e_color_update_rgb(wd->cv);
        break;

      case E_COLOR_COMPONENT_G:
        wd->cv->b = vy * 255;
        wd->cv->r = vx * 255;
        e_color_update_rgb(wd->cv);
        break;

      case E_COLOR_COMPONENT_B:
        wd->cv->r = vy * 255;
        wd->cv->g = vx * 255;
        e_color_update_rgb(wd->cv);
        break;

      case E_COLOR_COMPONENT_H:
        wd->cv->s = vy;
        wd->cv->v = vx;
        e_color_update_hsv(wd->cv);
        break;

      case E_COLOR_COMPONENT_S:
        wd->cv->v = vy;
        wd->cv->h = vx * 360;
        e_color_update_hsv(wd->cv);
        break;

      case E_COLOR_COMPONENT_V:
        wd->cv->h = vy * 360;
        wd->cv->s = vx;
        e_color_update_hsv(wd->cv);
        break;

      case E_COLOR_COMPONENT_MAX:
        break;
     }
   e_widget_change(obj);
}

static void
_e_wid_cb_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev;
   Evas_Object *o_wid;
   E_Widget_Data *wd;

   o_wid = data;
   wd = e_widget_data_get(o_wid);
   ev = event_info;

   wd->dragging = 1;
   _e_wid_mouse_handle(o_wid, ev->canvas.x, ev->canvas.y);
}

static void
_e_wid_cb_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Evas_Object *o_wid;
   E_Widget_Data *wd;

   o_wid = data;
   wd = e_widget_data_get(o_wid);
   wd->dragging = 0;
}

static void
_e_wid_cb_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev;
   Evas_Object *o_wid;
   E_Widget_Data *wd;

   o_wid = data;
   wd = e_widget_data_get(o_wid);
   ev = event_info;

   if (wd->dragging == 1)
     {
        _e_wid_mouse_handle(o_wid, ev->cur.canvas.x, ev->cur.canvas.y);
     }
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_table;
};

static void _e_wid_del_hook(Evas_Object *obj);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_table_add(Evas_Object *parent, int homogenous)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas_object_evas_get(parent));

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   o = elm_table_add(parent);
   wd->o_table = o;
   elm_table_homogeneous_set(o, homogenous);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   return obj;
}

E_API void
e_widget_table_object_append(Evas_Object *obj, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h)
{
   e_widget_table_object_align_append(obj, sobj,
                                      col, row, colspan, rowspan,
                                      fill_w, fill_h, expand_w, expand_h,
                                      0.5, 0.5);
}

E_API void
e_widget_table_object_align_append(Evas_Object *obj, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h, double ax, double ay)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   if (!wd) return;

   if (fill_w) ax = -1;
   if (fill_h) ay = -1;
   E_ALIGN(sobj, ax, ay);
   E_WEIGHT(sobj, expand_w, expand_h);
   elm_table_pack(wd->o_table, sobj, col, row, colspan, rowspan);
   e_widget_sub_object_add(obj, sobj);
   evas_object_show(sobj);
}

E_API void
e_widget_table_object_repack(Evas_Object *obj EINA_UNUSED, Evas_Object *sobj, int col, int row, int colspan, int rowspan, int fill_w, int fill_h, int expand_w, int expand_h)
{
   if (fill_w || fill_h)
     E_ALIGN(sobj, fill_w ? -1 : 0.5, fill_h ? -1 : 0.5);
   E_WEIGHT(sobj, expand_w, expand_h);
   elm_table_pack_set(sobj, col, row, colspan, rowspan);
}

E_API void
e_widget_table_unpack(Evas_Object *obj, Evas_Object *sobj)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   if (!wd) return;
   e_widget_sub_object_del(obj, sobj);
   elm_table_unpack(wd->o_table, sobj);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd = e_widget_data_get(obj);
   if (!wd) return;
   free(wd);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_widget, *o_entry;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);

/* externally accessible functions */
E_API Evas_Object *
e_widget_textblock_add(Evas *evas)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   wd = calloc(1, sizeof(E_Widget_Data));
   e_widget_data_set(obj, wd);

   wd->o_entry = o = elm_entry_add(e_win_evas_win_get(evas));
   elm_entry_scrollable_set(o, 1);
   elm_entry_editable_set(o, 0);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_wid_focus_steal, obj);

   evas_object_resize(obj, 32, 32);
   e_widget_size_min_set(obj, 32, 32);
   return obj;
}

E_API void
e_widget_textblock_markup_set(Evas_Object *obj, const char *text)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_entry_entry_set(wd->o_entry, text);
}

E_API void
e_widget_textblock_plain_set(Evas_Object *obj, const char *text)
{
   char *markup;
   char *d;
   const char *p;
   int mlen;

   if (!text)
     {
        e_widget_textblock_markup_set(obj, NULL);
        return;
     }
   mlen = strlen(text);
   /* need to look for these and replace with a new string (on the right)
    * '\n' -> "<ps/>"
    * '\t' -> "        "
    * '<'  -> "&lt;"
    * '>'  -> "&gt;"
    * '&'  -> "&amp;"
    */
   for (p = text; *p != 0; p++)
     {
        if (*p == '\n') mlen += 4 - 1;
        else if (*p == '\t')
          mlen += 8 - 1;
        else if (*p == '<')
          mlen += 4 - 1;
        else if (*p == '>')
          mlen += 4 - 1;
        else if (*p == '&')
          mlen += 5 - 1;
     }
   markup = alloca(mlen + 1);
   if (!markup) return;
   markup[0] = 0;
   for (d = markup, p = text; *p != 0; p++, d++)
     {
        if (*p == '\n')
          {
             strcpy(d, "<ps/>");
             d += 4 - 1;
          }
        else if (*p == '\t')
          {
             strcpy(d, "        ");
             d += 8 - 1;
          }
        else if (*p == '<')
          {
             strcpy(d, "&lt;");
             d += 4 - 1;
          }
        else if (*p == '>')
          {
             strcpy(d, "&gt;");
             d += 4 - 1;
          }
        else if (*p == '&')
          {
             strcpy(d, "&amp;");
             d += 5 - 1;
          }
        else *d = *p;
     }
   *d = 0;
   elm_entry_entry_set(obj, markup);
}

static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   free(wd);
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   elm_object_focus_set(wd->o_entry, e_widget_focus_get(obj));
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
typedef struct _Item          Item;
struct _E_Widget_Data
{
   Evas_Object *o_base, *o_box;
   int          icon_w, icon_h;
   Eina_List   *items;
   Eina_Bool    scrollable E_BITFIELD;
   Eina_Bool    focus_steal E_BITFIELD;
};

struct _Item
{
   Evas_Object *o_toolbar, *o_base, *o_icon;
   void         (*func)(void *data1, void *data2);
   const void  *data1, *data2;
   Eina_Bool    selected E_BITFIELD;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _e_wid_focus_hook(Evas_Object *obj);
static void _e_wid_focus_steal(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_disable_hook(Evas_Object *obj);
static void _e_wid_signal_cb1(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _e_wid_cb_scrollframe_resize(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_wid_cb_key_down(void *data, Evas *evas, Evas_Object *obj, void *event_info);
static void _e_wid_signal_prev(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _e_wid_signal_next(void *data, Evas_Object *obj, const char *emission, const char *source);
static void _item_show(Item *it);
static void _item_select(Item *it);
static void _item_unselect(Item *it);

/* local subsystem functions */

/* externally accessible functions */
E_API Evas_Object *
e_widget_toolbar_add(Evas *evas, int icon_w, int icon_h)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;
   Evas_Coord mw = 0, mh = 0;

   obj = e_widget_add(evas);

   e_widget_del_hook_set(obj, _e_wid_del_hook);
   e_widget_focus_hook_set(obj, _e_wid_focus_hook);
   e_widget_disable_hook_set(obj, _e_wid_disable_hook);
   wd = E_NEW(E_Widget_Data, 1);
   e_widget_data_set(obj, wd);
   wd->icon_w = icon_w;
   wd->icon_h = icon_h;
   wd->focus_steal = EINA_TRUE;

   o = e_scrollframe_add(evas);
   wd->o_base = o;
   o = elm_box_add(e_win_evas_win_get(evas));
   wd->o_box = o;
   o = wd->o_base;
   e_scrollframe_custom_theme_set(o, "base/theme/widgets", "e/widgets/toolbar");
   e_scrollframe_single_dir_set(o, 1);
   e_scrollframe_policy_set(o, E_SCROLLFRAME_POLICY_AUTO,
                            E_SCROLLFRAME_POLICY_OFF);
   e_scrollframe_thumbscroll_force(o, 1);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE,
                                  _e_wid_cb_scrollframe_resize, obj);
   evas_object_event_callback_add(e_scrollframe_edje_object_get(wd->o_base),
                                  EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_wid_focus_steal, obj);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN,
                                  _e_wid_cb_key_down, obj);

   edje_object_signal_callback_add(e_scrollframe_edje_object_get(o),
                                   "e,action,prev", "e",
                                   _e_wid_signal_prev, obj);
   edje_object_signal_callback_add(e_scrollframe_edje_object_get(o),
                                   "e,action,next", "e",
                                   _e_wid_signal_next, obj);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   e_widget_resize_object_set(obj, o);

   o = wd->o_box;
   elm_box_horizontal_set(o, 1);
   elm_box_homogeneous_set(o, 1);
   e_scrollframe_child_set(wd->o_base, o);
   e_widget_sub_object_add(obj, o);
   evas_object_show(o);

   edje_object_size_min_calc(e_scrollframe_edje_object_get(wd->o_base),
                             &mw, &mh);
   e_widget_size_min_set(obj, mw, mh);

   return obj;
}

E_API const Eina_List *
e_widget_toolbar_items_get(Evas_Object *obj)
{
   E_Widget_Data *wd;
   if (!obj) return NULL;
   if (!(wd = e_widget_data_get(obj))) return NULL;
   return wd->items;
}

E_API unsigned int
e_widget_toolbar_items_count(Evas_Object *obj)
{
   E_Widget_Data *wd;
   if (!obj) return 0;
   if (!(wd = e_widget_data_get(obj))) return 0;
   return eina_list_count(wd->items);
}

E_API const char *
e_widget_toolbar_item_label_get(void *item)
{
   Item *it = item;
   if (!it) return NULL;
   return edje_object_part_text_get(it->o_base, "e.text.label");
}

E_API void
e_widget_toolbar_item_append(Evas_Object *obj, Evas_Object *icon, const char *label, void (*func)(void *data1, void *data2), const void *data1, const void *data2)
{
   E_Widget_Data *wd;
   Evas_Object *o;
   Item *it;
   Evas_Coord mw = 0, mh = 0, vw = 0, vh = 0;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   o = edje_object_add(evas_object_evas_get(obj));
   e_theme_edje_object_set(o, "base/theme/widgets",
                           "e/widgets/toolbar/item");
   it = E_NEW(Item, 1);
   it->o_toolbar = obj;
   it->o_base = o;
   it->o_icon = icon;
   it->func = func;
   it->data1 = data1;
   it->data2 = data2;
   wd->items = eina_list_append(wd->items, it);

   edje_object_signal_callback_add(o, "e,action,click", "e",
                                   _e_wid_signal_cb1, it);
   if (icon)
     {
        evas_object_size_hint_min_set(icon, wd->icon_w, wd->icon_h);
        edje_object_part_swallow(o, "e.swallow.icon", icon);
        evas_object_show(icon);
        edje_object_signal_emit(o, "e,icon,enabled", "e");
     }
   else
     edje_object_signal_emit(o, "e,icon,disabled", "e");
   edje_object_message_signal_process(o);
   edje_object_part_text_set(o, "e.text.label", label);
   edje_object_size_min_calc(o, &mw, &mh);
   e_widget_sub_object_add(obj, o);
   evas_object_size_hint_min_set(o, mw, mh);
   E_FILL(o);
   elm_box_pack_end(wd->o_box, o);
   evas_object_show(o);
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   evas_object_resize(wd->o_box, mw, mh);
   evas_object_resize(wd->o_base, 500, 500);
   e_scrollframe_child_viewport_size_get(wd->o_base, &vw, &vh);
   if (wd->scrollable)
     e_widget_size_min_set(obj, 500 - vw, mh + (500 - vh));
   else
     e_widget_size_min_set(obj, mw + (500 - vw), mh + (500 - vh));
   evas_object_resize(wd->o_box, mw, mh);
}

E_API void
e_widget_toolbar_item_remove(Evas_Object *obj, int num)
{
   E_Widget_Data *wd;
   Item *it;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   it = eina_list_nth(wd->items, num);
   if (it)
     {
        evas_object_del(it->o_base);
        if (it->o_icon) evas_object_del(it->o_icon);
        wd->items = eina_list_remove(wd->items, it);
        E_FREE(it);
     }
}

E_API void
e_widget_toolbar_item_select(Evas_Object *obj, int num)
{
   E_Widget_Data *wd = NULL;
   Eina_List *l = NULL;
   Item *it = NULL;
   int i = 0;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   EINA_LIST_FOREACH(wd->items, l, it)
     {
        if (i == num)
          {
             if (!it->selected) _item_select(it);
          }
        else
          {
             if (it->selected) _item_unselect(it);
          }
        i++;
     }
}

E_API void
e_widget_toolbar_item_label_set(Evas_Object *obj, int num, const char *label)
{
   E_Widget_Data *wd = NULL;
   Item *it = NULL;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   it = eina_list_nth(wd->items, num);
   if (it)
     {
        int mw, mh;

        edje_object_part_text_set(it->o_base, "e.text.label", label);
        edje_object_size_min_calc(it->o_base, &mw, &mh);
        evas_object_size_hint_min_set(it->o_base, mw, mh);
     }
}

E_API void
e_widget_toolbar_scrollable_set(Evas_Object *obj, Eina_Bool scrollable)
{
   E_Widget_Data *wd;
   Evas_Coord mw = 0, mh = 0, vw = 0, vh = 0;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   wd->scrollable = scrollable;
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   evas_object_resize(wd->o_box, mw, mh);
   evas_object_resize(wd->o_base, 500, 500);
   e_scrollframe_child_viewport_size_get(wd->o_base, &vw, &vh);
   if (wd->scrollable)
     e_widget_size_min_set(obj, 500 - vw, mh + (500 - vh));
   else
     e_widget_size_min_set(obj, mw + (500 - vw), mh + (500 - vh));
   evas_object_resize(wd->o_box, mw, mh);
}

E_API void
e_widget_toolbar_focus_steal_set(Evas_Object *obj, Eina_Bool steal)
{
   E_Widget_Data *wd;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   if (wd->focus_steal == steal) return;
   if (steal)
     {
        evas_object_event_callback_add(e_scrollframe_edje_object_get(wd->o_base),
                                       EVAS_CALLBACK_MOUSE_DOWN,
                                       _e_wid_focus_steal, obj);
        wd->focus_steal = EINA_TRUE;
     }
   else
     {
        evas_object_event_callback_del(e_scrollframe_edje_object_get(wd->o_base),
                                       EVAS_CALLBACK_MOUSE_DOWN,
                                       _e_wid_focus_steal);
        wd->focus_steal = EINA_FALSE;
     }
}

E_API void
e_widget_toolbar_clear(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;
   Item *it = NULL;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   EINA_LIST_FREE(wd->items, it)
     {
        evas_object_del(it->o_base);
        if (it->o_icon) evas_object_del(it->o_icon);
        E_FREE(it);
     }
}

E_API int
e_widget_toolbar_item_selected_get(Evas_Object *obj)
{
   E_Widget_Data *wd = NULL;
   Eina_List *l = NULL;
   Item *it = NULL;
   int i = 0;

   if (!obj) return 0;
   if (!(wd = e_widget_data_get(obj))) return 0;
   EINA_LIST_FOREACH(wd->items, l, it)
     {
        if (it->selected) return i;
        i++;
     }
   return 0;
}

/* local functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;
   Item *it;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   EINA_LIST_FREE(wd->items, it)
     {
        evas_object_del(it->o_base);
        if (it->o_icon) evas_object_del(it->o_icon);
        E_FREE(it);
     }
   E_FREE(wd);
}

static void
_e_wid_disable_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   if (e_widget_disabled_get(obj))
     edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_base),
                             "e,state,disabled", "e");
   else
     edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_base),
                             "e,state,enabled", "e");
}

static void
_e_wid_signal_cb1(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   Item *it, *it2;
   E_Widget_Data *wd;
   Eina_List *l;

   if (!(it = data)) return;
   if (it->selected) return;
   wd = e_widget_data_get(it->o_toolbar);
   EINA_LIST_FOREACH(wd->items, l, it2)
     {
        if (it2->selected)
          {
             _item_unselect(it2);
             break;
          }
     }
   _item_select(it);
}

static void
_e_wid_signal_prev(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Widget_Data *wd;
   Eina_List *l, *l2;
   Item *it = NULL, *it2 = NULL;

   if (!(wd = e_widget_data_get(data))) return;
   if ((!wd->o_base) || (!wd->o_box)) return;
   EINA_LIST_FOREACH(wd->items, l, it)
     {
        if (it->selected)
          {
             l2 = eina_list_prev(l);
             if (l2) it2 = eina_list_data_get(l2);
             break;
          }
     }
   if ((it) && (it2) && (it != it2))
     {
        _item_unselect(it);
        _item_select(it2);
     }
}

static void
_e_wid_signal_next(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   E_Widget_Data *wd;
   Eina_List *l, *l2;
   Item *it = NULL, *it2 = NULL;

   if (!(wd = e_widget_data_get(data))) return;
   if ((!wd->o_base) || (!wd->o_box)) return;
   EINA_LIST_FOREACH(wd->items, l, it)
     {
        if (it->selected)
          {
             l2 = eina_list_next(l);
             if (l2) it2 = eina_list_data_get(l2);
             break;
          }
     }
   if ((it) && (it2) && (it != it2))
     {
        _item_unselect(it);
        _item_select(it2);
     }
}

static void
_e_wid_cb_scrollframe_resize(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Widget_Data *wd;
   Evas_Coord mw, mh, vw, vh, w, h;
   Eina_List *l;
   Item *it;

   if (!(wd = e_widget_data_get(data))) return;
   if ((!wd->o_base) || (!wd->o_box)) return;

   e_scrollframe_child_viewport_size_get(wd->o_base, &vw, &vh);
   elm_box_recalculate(wd->o_box);
   evas_object_size_hint_min_get(wd->o_box, &mw, &mh);
   evas_object_geometry_get(wd->o_box, NULL, NULL, &w, &h);
   if (vw >= mw)
     {
        if (w != vw) evas_object_resize(wd->o_box, vw, h);
     }
   EINA_LIST_FOREACH(wd->items, l, it)
     {
        if (it->selected)
          {
             _item_show(it);
             break;
          }
     }
}

static void
_e_wid_cb_key_down(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Key_Down *ev;
   E_Widget_Data *wd;
   Eina_List *l, *l2;
   Item *it = NULL, *it2 = NULL;

   ev = event_info;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   wd = e_widget_data_get(data);
   if ((!strcmp(ev->key, "Up")) || (!strcmp(ev->key, "KP_Up")) ||
       (!strcmp(ev->key, "Left")) || (!strcmp(ev->key, "KP_Left")))
     {
        EINA_LIST_FOREACH(wd->items, l, it)
          {
             if (it->selected)
               {
                  l2 = eina_list_prev(l);
                  if (l2) it2 = eina_list_data_get(l2);
                  break;
               }
          }
     }
   else if ((!strcmp(ev->key, "Down")) ||
            (!strcmp(ev->key, "KP_Down")) ||
            (!strcmp(ev->key, "Right")) ||
            (!strcmp(ev->key, "KP_Right")))
     {
        EINA_LIST_FOREACH(wd->items, l, it)
          {
             if (it->selected)
               {
                  l2 = eina_list_next(l);
                  if (l2) it2 = eina_list_data_get(l2);
                  break;
               }
          }
     }
   else if ((!strcmp(ev->key, "Home")) ||
            (!strcmp(ev->key, "KP_Home")))
     {
        EINA_LIST_FOREACH(wd->items, l, it)
          {
             if (it->selected)
               {
                  it2 = eina_list_data_get(wd->items);
                  break;
               }
          }
     }
   else if ((!strcmp(ev->key, "End")) || (!strcmp(ev->key, "KP_End")))
     {
        EINA_LIST_FOREACH(wd->items, l, it)
          {
             if (it->selected)
               {
                  it2 = eina_list_last_data_get(wd->items);
                  break;
               }
          }
     }
   if ((it) && (it2) && (it != it2))
     {
        _item_unselect(it);
        _item_select(it2);
     }
}

static void
_e_wid_focus_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   if (!obj) return;
   if (!(wd = e_widget_data_get(obj))) return;
   if (e_widget_focus_get(obj))
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_base), "e,state,focused", "e");
        evas_object_focus_set(obj, EINA_TRUE);
     }
   else
     {
        edje_object_signal_emit(e_scrollframe_edje_object_get(wd->o_base), "e,state,unfocused", "e");
        evas_object_focus_set(obj, EINA_FALSE);
     }
}

static void
_e_wid_focus_steal(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   e_widget_focus_steal(data);
}

static void
_item_show(Item *it)
{
   E_Widget_Data *wd;
   Evas_Coord x, y, w, h, bx, by;

   wd = e_widget_data_get(it->o_toolbar);
   evas_object_geometry_get(wd->o_box, &bx, &by, NULL, NULL);
   evas_object_geometry_get(it->o_base, &x, &y, &w, &h);
   e_scrollframe_child_region_show(wd->o_base, x - bx, y - by, w, h);
}

static void
_item_select(Item *it)
{
   it->selected = EINA_TRUE;
   edje_object_signal_emit(it->o_base, "e,state,selected", "e");
   if (it->o_icon)
     {
        if (strcmp(evas_object_type_get(it->o_icon), "e_icon"))
          edje_object_signal_emit(it->o_icon, "e,state,selected", "e");
        else
          e_icon_selected_set(it->o_icon, EINA_TRUE);
     }
   _item_show(it);
   if (it->func) it->func((void *)it->data1, (void *)it->data2);
}

static void
_item_unselect(Item *it)
{
   it->selected = EINA_FALSE;
   edje_object_signal_emit(it->o_base, "e,state,unselected", "e");
   if (it->o_icon)
     {
        if (strcmp(evas_object_type_get(it->o_icon), "e_icon"))
          edje_object_signal_emit(it->o_icon, "e,state,unselected", "e");
        else
          e_icon_selected_set(it->o_icon, EINA_FALSE);
     }
}

#include "e.h"

typedef struct _E_Widget_Data E_Widget_Data;
struct _E_Widget_Data
{
   Evas_Object *o_widget, *o_tb, *o_bar;
   Eina_List   *content;
};

static void _e_wid_del_hook(Evas_Object *obj);
static void _item_sel(void *data1, void *data2);

/* externally accessible functions */
E_API Evas_Object *
e_widget_toolbook_add(Evas *evas, int icon_w, int icon_h)
{
   Evas_Object *obj, *o;
   E_Widget_Data *wd;

   obj = e_widget_add(evas);
   e_widget_del_hook_set(obj, _e_wid_del_hook);
   wd = E_NEW(E_Widget_Data, 1);
   e_widget_data_set(obj, wd);
   wd->o_widget = obj;

   o = e_widget_table_add(e_win_evas_win_get(evas), 0);
   e_widget_resize_object_set(obj, o);
   evas_object_show(o);
   e_widget_sub_object_add(obj, o);
   wd->o_tb = o;

   o = e_widget_toolbar_add(evas, icon_w, icon_h);
   e_widget_toolbar_scrollable_set(o, EINA_TRUE);
   e_widget_table_object_append(wd->o_tb, o, 0, 0, 1, 1, 1, 1, 1, 0);
   wd->o_bar = o;
   evas_object_show(o);

   return obj;
}

E_API void
e_widget_toolbook_page_append(Evas_Object *toolbook, Evas_Object *icon, const char *label, Evas_Object *content, int expand_w, int expand_h, int fill_w, int fill_h, double ax, double ay)
{
   E_Widget_Data *wd;
   Evas_Coord minw, minh;

   wd = e_widget_data_get(toolbook);
   e_widget_toolbar_item_append(wd->o_bar, icon, label, _item_sel,
                                toolbook, content);
   e_widget_table_object_repack(wd->o_tb, wd->o_bar, 0, 0, 1, 1, 1, 1, 1, 0);
   e_widget_table_object_align_append(wd->o_tb, content, 0, 1, 1, 1,
                                      fill_w, fill_h, expand_w, expand_h,
                                      ax, ay);
   evas_object_hide(content);
   wd->content = eina_list_append(wd->content, content);
   e_widget_size_min_get(wd->o_tb, &minw, &minh);
   e_widget_size_min_set(toolbook, minw, minh);
}

E_API void
e_widget_toolbook_page_show(Evas_Object *toolbook, int n)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(toolbook);
   e_widget_toolbar_item_select(wd->o_bar, n);
}

/* Private functions */
static void
_e_wid_del_hook(Evas_Object *obj)
{
   E_Widget_Data *wd;

   wd = e_widget_data_get(obj);
   eina_list_free(wd->content);
   E_FREE(wd);
}

static void
_item_sel(void *data1, void *data2)
{
   E_Widget_Data *wd;
   Evas_Object *o, *obj, *sobj;
   Eina_List *l;

   obj = data1;
   sobj = data2;
   wd = e_widget_data_get(obj);

   EINA_LIST_FOREACH(wd->content, l, o)
     {
        if (o == sobj) evas_object_show(o);
        else evas_object_hide(o);
     }
}

#include "e.h"

/* intercept elm_win operations so we talk directly to e_client */
#undef elm_win_add
#undef elm_win_util_dialog_add
#undef elm_win_util_standard_add

typedef struct _Elm_Win_Trap_Ctx
{
   E_Client      *client;
   E_Pointer     *pointer;
   int x, y, w, h;
   Eina_Bool      centered E_BITFIELD;
   Eina_Bool      placed E_BITFIELD;
   Eina_Bool      sized E_BITFIELD;
   Eina_Bool      internal_no_remember E_BITFIELD;
   Eina_Bool      internal_no_reopen E_BITFIELD;
   Eina_Bool      visible E_BITFIELD;
   Eina_Bool      override E_BITFIELD;
} Elm_Win_Trap_Ctx;


static Elm_Win_Trap_Ctx *current_win = NULL;

static void *
_e_elm_win_trap_add(Evas_Object *o)
{
   if (elm_win_type_get(o) == ELM_WIN_INLINED_IMAGE) return NULL;
   Elm_Win_Trap_Ctx *ctx = calloc(1, sizeof(Elm_Win_Trap_Ctx));
   EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
   evas_object_name_set(o, "E_Win");
   return ctx;
}

static void
_e_elm_win_trap_del(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return;
   if (ctx->client)
     {
        ctx->client->internal_elm_win = NULL;
        e_object_del(E_OBJECT(ctx->client));
        evas_object_data_set(o, "E_Client", NULL);
     }
   free(ctx);
}

static Eina_Bool
_e_elm_win_trap_hide(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (!ctx->visible) return EINA_FALSE;
   if (strncmp(ecore_evas_engine_name_get(e_win_ee_get(o)), "wayland", 7))
     E_FREE_FUNC(ctx->pointer, e_object_del);
#ifdef HAVE_WAYLAND
   else if (ctx->client)
     e_pixmap_alias(NULL, E_PIXMAP_TYPE_WL, ecore_wl2_window_surface_id_get(elm_win_wl_window_get(o)));
#endif

   if (!ctx->client) return EINA_TRUE;
   ctx->visible = 0;
   evas_object_hide(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_show(void *data, Evas_Object *o)
{
   Elm_Win_Trap_Ctx *ctx = data;
   Evas *e = evas_object_evas_get(o);
   Ecore_Evas *ee = ecore_evas_ecore_evas_get(e);
   Eina_Bool borderless;

   if (!ctx) return EINA_TRUE;
   borderless = elm_win_borderless_get(o);
   if (!ctx->client)
     {
        E_Client *ec;
        Ecore_Window win;
#ifdef HAVE_WAYLAND
        int64_t wl_win_id = -1;
#endif
        E_Pixmap_Type type = E_PIXMAP_TYPE_X;

        win = elm_win_window_id_get(o);
#ifdef HAVE_WAYLAND
        if (!strncmp(ecore_evas_engine_name_get(ee), "wayland", 7))
          {
             Ecore_Wl2_Window *ewin = elm_win_wl_window_get(o);

             type = E_PIXMAP_TYPE_WL;
             ecore_evas_object_cursor_set(ee, NULL, 0, 0, 0);
             ctx->pointer = e_comp->pointer;
             elm_win_borderless_set(o, 1);
             wl_win_id = ecore_wl2_window_surface_id_get(ewin);
          }
        else
#endif
          {
             type = E_PIXMAP_TYPE_X;
             ctx->pointer = e_pointer_window_new(win, EINA_TRUE);
          }

        if (type == E_PIXMAP_TYPE_WL)
          ec = e_pixmap_find_client(type, (int64_t)win);
        else
          ec = e_pixmap_find_client(type, win);

        if (ec)
          ctx->client = ec;
        else
          {
             E_Pixmap *cp;
             const char *title, *name, *clas;

             ecore_evas_name_class_get(ee, &name, &clas);
             if (!name) name = "E";
             if (!clas) clas = "_e_internal_window";
             ecore_evas_name_class_set(ee, name, clas);
             title = elm_win_title_get(o);
             if ((!title) || (!title[0]))
               title = "E";
             ecore_evas_title_set(ee, title);

             if (type == E_PIXMAP_TYPE_WL)
               cp = e_pixmap_new(type, (int64_t)win);
             else
               cp = e_pixmap_new(type, win);

             EINA_SAFETY_ON_NULL_RETURN_VAL(cp, EINA_TRUE);
#ifdef HAVE_WAYLAND
             if (wl_win_id >= 0)
               e_pixmap_alias(cp, type, wl_win_id);
#endif

             current_win = ctx;
             ctx->client = e_client_new(cp, 0, 1);
             current_win = NULL;
             EINA_SAFETY_ON_NULL_RETURN_VAL(ctx->client, EINA_TRUE);
             eina_stringshare_replace(&ctx->client->icccm.name, name);
             eina_stringshare_replace(&ctx->client->icccm.class, clas);
             eina_stringshare_replace(&ctx->client->icccm.title, title);
          }
        ctx->client->placed = ctx->placed | ctx->centered;
        ctx->client->internal_no_remember = ctx->internal_no_remember;
        ctx->client->internal_no_reopen = ctx->internal_no_reopen;
        ctx->client->internal_elm_win = o;
        elm_win_autodel_set(o, 1);
        evas_object_data_set(o, "E_Client", ctx->client);
        ctx->client->dialog = elm_win_type_get(o) == ELM_WIN_DIALOG_BASIC;
        ctx->client->tooltip = elm_win_type_get(o) == ELM_WIN_TOOLTIP;

        evas_object_size_hint_min_get(o, &ctx->client->icccm.min_w, &ctx->client->icccm.min_h);
        ctx->client->icccm.min_w = MAX(ctx->client->icccm.min_w, 0);
        ctx->client->icccm.min_h = MAX(ctx->client->icccm.min_h, 0);
        evas_object_size_hint_max_get(o, &ctx->client->icccm.max_w, &ctx->client->icccm.max_h);
        ctx->client->icccm.max_w = MAX(ctx->client->icccm.max_w, 0);
        ctx->client->icccm.max_h = MAX(ctx->client->icccm.max_h, 0);
     }
//#endif
   evas_object_geometry_get(o, &ctx->client->client.x, &ctx->client->client.y, &ctx->client->client.w, &ctx->client->client.h);
   ecore_evas_show(ee);
   eina_stringshare_replace(&ctx->client->internal_icon, elm_win_icon_name_get(o));
   if (!ctx->visible)
     {
        ctx->visible = 1;
        evas_object_show(ctx->client->frame);
     }
   ctx->client->borderless |= borderless;
   e_comp_object_frame_xy_adjust(ctx->client->frame, ctx->client->client.x, ctx->client->client.y, &ctx->client->x, &ctx->client->y);
   e_comp_object_frame_wh_adjust(ctx->client->frame, ctx->client->client.w, ctx->client->client.h, &ctx->client->w, &ctx->client->h);
   if (ctx->sized) evas_object_resize(ctx->client->frame, ctx->w, ctx->h);
   if (ctx->placed) evas_object_move(ctx->client->frame, ctx->x, ctx->y);
   if (ctx->centered)
     {
        e_comp_object_util_center(o);
        ctx->centered = 0;
     }
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_move(void *data, Evas_Object *o, int x, int y)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (e_comp->comp_type == E_PIXMAP_TYPE_WL)
     {
        int ex, ey;

        /* if the ee coords match the requested coords, this is coming from
         * a configure event which the compositor sent. failing to reject this
         * move will result in bad window positioning
         */
        ecore_evas_geometry_get(e_win_ee_get(o), &ex, &ey, NULL, NULL);
        if ((x == ex) && (y == ey)) return EINA_FALSE;
     }
   ctx->placed = 1;
   ctx->x = x, ctx->y = y;
   if (!ctx->client) return EINA_TRUE;
   if ((ctx->client->client.x != x) || (ctx->client->client.y != y))
     e_client_util_move_without_frame(ctx->client, x, y);
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_resize(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   ctx->sized = 1;
   ctx->w = w, ctx->h = h;
   if (!ctx->client) return EINA_TRUE;
   e_comp_object_frame_wh_adjust(ctx->client->frame, w, h, &w, &h);
   e_client_resize_limit(ctx->client, &w, &h);
   evas_object_resize(ctx->client->frame, w, h);
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_center(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool h, Eina_Bool v)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   ctx->centered = h | v;
   ctx->placed = 1;
   if (!ctx->client) return EINA_FALSE;
   if (ctx->centered)
     {
        e_comp_object_util_center(ctx->client->frame);
        ctx->centered = 0;
     }
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_lower(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   evas_object_lower(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_raise(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   evas_object_raise(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_activate(void *data, Evas_Object *o EINA_UNUSED)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   evas_object_focus_set(ctx->client->frame, 1);
   if (!ctx->client->lock_user_stacking)
     evas_object_raise(ctx->client->frame);
   return EINA_FALSE;
}

static Eina_Bool
_e_elm_win_trap_size_min_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   ctx->client->icccm.min_w = w;
   ctx->client->icccm.min_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w < w ? w : ctx->client->w;
   mh = ctx->client->h < h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_size_max_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   w = MAX(w, 0);
   h = MAX(h, 0);
   ctx->client->icccm.max_w = w;
   ctx->client->icccm.max_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w > w ? w : ctx->client->w;
   mh = ctx->client->h > h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_size_base_set(void *data, Evas_Object *o EINA_UNUSED, int w, int h)
{
   Elm_Win_Trap_Ctx *ctx = data;
   int mw = 0, mh = 0;

   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;
   ctx->client->icccm.base_w = w;
   ctx->client->icccm.base_h = h;
   e_client_resize_limit(ctx->client, &mw, &mh);
   mw = ctx->client->w < w ? w : ctx->client->w;
   mh = ctx->client->h < h ? h : ctx->client->h;
   if ((ctx->client->w != mw) || (ctx->client->h != mh))
     evas_object_resize(ctx->client->frame, mw, mh);

   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_borderless_set(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool borderless)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;
   if (!ctx->client) return EINA_TRUE;

   borderless = !!borderless;

   ctx->client->borderless = borderless;
   EC_CHANGED(ctx->client);
   ctx->client->changes.border = 1;
   return EINA_TRUE;
}

static Eina_Bool
_e_elm_win_trap_override_set(void *data, Evas_Object *o EINA_UNUSED, Eina_Bool override)
{
   Elm_Win_Trap_Ctx *ctx = data;
   if (!ctx) return EINA_TRUE;

   if (ctx->client)
     CRI("Override being set too late on internal client!");
   ctx->override = !!override;
   return EINA_TRUE;
}

static const Elm_Win_Trap _e_elm_win_trap = {
   ELM_WIN_TRAP_VERSION,
   _e_elm_win_trap_add,
   _e_elm_win_trap_del,
   _e_elm_win_trap_hide,
   _e_elm_win_trap_show,
   _e_elm_win_trap_move,
   _e_elm_win_trap_resize,
   _e_elm_win_trap_center,
   _e_elm_win_trap_lower,
   _e_elm_win_trap_raise,
   _e_elm_win_trap_activate,
   /* alpha_set */ NULL,
   /* aspect_set */ NULL,
   /* avoid_damage_set */ NULL,
   _e_elm_win_trap_borderless_set,
   /* demand_attention_set */ NULL,
   /* focus_skip_set */ NULL,
   /* fullscreen_set */ NULL,
   /* iconified_set */ NULL,
   /* layer_set */ NULL,
   /* manual_render_set */ NULL,
   /* maximized_set */ NULL,
   /* modal_set */ NULL,
   /* name_class_set */ NULL,
   /* object_cursor_set */ NULL,
   _e_elm_win_trap_override_set,
   /* rotation_set */ NULL,
   /* rotation_with_resize_set */ NULL,
   /* shaped_set */ NULL,
   _e_elm_win_trap_size_base_set,
   /* size_step_set */ NULL,
   _e_elm_win_trap_size_min_set,
   _e_elm_win_trap_size_max_set,
   /* sticky_set */ NULL,
   /* title_set */ NULL,
   /* urgent_set */ NULL,
   /* withdrawn_set */ NULL
};

static void
_e_win_client_hook_new(void *d EINA_UNUSED, E_Client *ec)
{
   if (!ec->internal) return;
   if (current_win)
     ec->override = current_win->override;
}

/* externally accessible functions */
EINTERN int
e_win_init(void)
{
   if (!elm_win_trap_set(&_e_elm_win_trap)) return 0;
   e_client_hook_add(E_CLIENT_HOOK_NEW_CLIENT, _e_win_client_hook_new, NULL);
   return 1;
}

EINTERN int
e_win_shutdown(void)
{
   return 1;
}

E_API E_Client *
e_win_client_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx;

   if (!e_obj_is_win(obj)) return NULL;
   ctx = elm_win_trap_data_get(obj);

   return ctx ? ctx->client : NULL;
}

E_API Ecore_Evas *
e_win_ee_get(Evas_Object *obj)
{
   return ecore_evas_ecore_evas_get(evas_object_evas_get(obj));
}

E_API E_Pointer *
e_win_pointer_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return NULL;
   return ctx->pointer;
}

E_API Eina_Bool
e_win_centered_get(Evas_Object *obj)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return EINA_FALSE;
   return ctx->centered;
}

E_API void
e_win_client_icon_set(Evas_Object *obj, const char *icon)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return;
   if (ctx->client)
     eina_stringshare_replace(&ctx->client->internal_icon, icon);
}

E_API void
e_win_client_icon_key_set(Evas_Object *obj, const char *key)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return;
   if (ctx->client)
     eina_stringshare_replace(&ctx->client->internal_icon_key, key);
}

E_API void
e_win_placed_set(Evas_Object *obj, Eina_Bool placed)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return;
   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->placed = !!placed;
   if (ctx->client)
     {
        ctx->client->placed = !!placed;
        EC_CHANGED(ctx->client);
     }
}

E_API void
e_win_no_remember_set(Evas_Object *obj, Eina_Bool no_rem)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return;
   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->internal_no_remember = !!no_rem;
   if (ctx->client)
     ctx->client->internal_no_remember = !!no_rem;
}

E_API void
e_win_no_reopen_set(Evas_Object *obj, Eina_Bool no_reopen)
{
   Elm_Win_Trap_Ctx *ctx = elm_win_trap_data_get(obj);

   if (!ctx) return;
   EINA_SAFETY_ON_NULL_RETURN(obj);
   ctx->internal_no_reopen = !!no_reopen;
   if (ctx->client)
     ctx->client->internal_no_reopen = !!no_reopen;
}

E_API Evas_Object *
e_elm_win_add(Evas_Object *parent, const char *name, Elm_Win_Type type)
{
   char *eng;
   Evas_Object *o;

   if (type == ELM_WIN_INLINED_IMAGE) return elm_win_add(parent, name, type);
   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_add(parent, name, type);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}

E_API Evas_Object *
e_elm_win_util_standard_add(const char *name, const char *title)
{
   char *eng;
   Evas_Object *o;

   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_util_standard_add(name, title);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}

E_API Evas_Object *
e_elm_win_util_dialog_add(Evas_Object *parent, const char *name, const char *title)
{
   char *eng;
   Evas_Object *o;

   eng = eina_strdup(getenv("ELM_ACCEL"));
   e_util_env_set("ELM_ACCEL", "none");
   o = elm_win_util_dialog_add(parent, name, title);
   e_util_env_set("ELM_ACCEL", eng);
   elm_win_alpha_set(o, 1);
   free(eng);
   return o;
}
#include "e.h"

static void _e_xinerama_clean(void);
static void _e_xinerama_update(void);
static int  _e_xinerama_cb_screen_sort(const void *data1, const void *data2);

static Eina_List *all_screens = NULL;
static Eina_List *chosen_screens = NULL;
static Eina_List *fake_screens = NULL;

static void
_screen_free(E_Screen *scr)
{
   free(scr->id);
   free(scr);
}

EINTERN int
e_xinerama_init(void)
{
   return 1;
}

EINTERN int
e_xinerama_shutdown(void)
{
   _e_xinerama_clean();
   return 1;
}

E_API void
e_xinerama_update(void)
{
   _e_xinerama_clean();
   _e_xinerama_update();
}

E_API const Eina_List *
e_xinerama_screens_get(void)
{
   if (fake_screens) return fake_screens;
   return chosen_screens;
}

E_API const Eina_List *
e_xinerama_screens_all_get(void)
{
   if (fake_screens) return fake_screens;
   return all_screens;
}

E_API void
e_xinerama_screens_set(Eina_List *screens)
{
   E_FREE_LIST(all_screens, _screen_free);
   chosen_screens = eina_list_free(chosen_screens);
   all_screens = screens;
   _e_xinerama_update();
}

EINTERN void
e_xinerama_fake_screen_del(int num)
{
   Eina_List *l;
   E_Screen *scr, *lscr;
   int n = 0, x = 0;

   /* this assumes X number of identical horizontal screens */
   EINA_SAFETY_ON_NULL_RETURN(fake_screens);
   l = eina_list_nth_list(fake_screens, num);
   EINA_SAFETY_ON_NULL_RETURN(l);
   scr = eina_list_data_get(l);
   free(scr);
   fake_screens = eina_list_remove_list(fake_screens, l);
   EINA_LIST_FOREACH(fake_screens, l, lscr)
     {
        lscr->screen = n++;
        lscr->x = x;
        lscr->w = e_comp->w / eina_list_count(fake_screens);
        x = lscr->x + lscr->w;
     }
   e_comp_canvas_update();
   ecore_event_add(E_EVENT_RANDR_CHANGE, NULL, NULL, NULL);
}

EINTERN void
e_xinerama_fake_screen_append(void)
{
   Eina_List *l;
   E_Screen *lscr;
   int n, x = 0;

   /* this assumes X number of identical horizontal screens */
   n = eina_list_count(fake_screens) + 1;
   EINA_LIST_FOREACH(fake_screens, l, lscr)
     {
        lscr->x = x;
        lscr->w = e_comp->w / n;
        x = lscr->x + lscr->w;
     }
   e_xinerama_fake_screen_add(e_comp->w - (e_comp->w / n), 0, e_comp->w / n, e_comp->h);
   e_comp_canvas_update();
   ecore_event_add(E_EVENT_RANDR_CHANGE, NULL, NULL, NULL);
}

E_API void
e_xinerama_fake_screen_add(int x, int y, int w, int h)
{
   E_Screen *scr;

   scr = E_NEW(E_Screen, 1);
   scr->screen = eina_list_count(fake_screens);
   scr->escreen = scr->screen;
   scr->x = x;
   scr->y = y;
   scr->w = w;
   scr->h = h;
   fake_screens = eina_list_append(fake_screens, scr);
}

E_API Eina_Bool
e_xinerama_fake_screens_exist(void)
{
   return !!fake_screens;
}

/* local subsystem functions */
static void
_e_xinerama_clean(void)
{
   E_FREE_LIST(all_screens, _screen_free);
   chosen_screens = eina_list_free(chosen_screens);
   E_FREE_LIST(fake_screens, _screen_free);
}

static void
_e_xinerama_update(void)
{
   Eina_List *l;
   E_Screen *scr;
   unsigned int n = 0;

   /* now go through all_screens... and build a list of chosen screens */
   EINA_LIST_FOREACH(all_screens, l, scr)
     {
        Eina_List *ll;
        E_Screen *scr2;
        int add = 1;
        Eina_List *removes;

        removes = NULL;
        /* does this screen intersect with any we have chosen? */
        EINA_LIST_FOREACH(chosen_screens, ll, scr2)
          {
             /* if they intersect */
             if (E_INTERSECTS(scr->x, scr->y, scr->w, scr->h,
                              scr2->x, scr2->y, scr2->w, scr2->h))
               {
                  int sz, sz2;

                  /* calculate pixel area */
                  sz = scr->w * scr->h;
                  sz2 = scr2->w * scr2->h;
                  /* if the one we already have is bigger, DON'T add the new */
                  if (sz > sz2)
                    removes = eina_list_append(removes, scr2);
                  /* add the old to a list to remove */
                  else
                    add = 0;
               }
          }
        /* if there are screens to remove - remove them */
        EINA_LIST_FREE(removes, scr2)
          {
             chosen_screens = eina_list_remove(chosen_screens, scr2);
          }
        /* if this screen is to be added, add it */
        if (add)
          chosen_screens = eina_list_append(chosen_screens, scr);
     }
   chosen_screens = eina_list_sort(chosen_screens,
                                   eina_list_count(chosen_screens),
                                   _e_xinerama_cb_screen_sort);
   INF("======================= screens:");
   EINA_LIST_FOREACH(chosen_screens, l, scr)
     {
        INF("E INIT: XINERAMA CHOSEN: [%i][%i], %ix%i+%i+%i",
            scr->screen, scr->escreen, scr->w, scr->h, scr->x, scr->y);
        n++;
     }
}

static int
_e_xinerama_cb_screen_sort(const void *data1, const void *data2)
{
   const E_Screen *scr, *scr2;
   int dif;

   scr = data1;
   scr2 = data2;
   if (scr->x != scr2->x)
     return scr->x - scr2->x;
   else if (scr->y != scr2->y)
     return scr->y - scr2->y;
   else
     {
        dif = (scr2->w * scr2->h) - (scr->w * scr->h);
        if (dif == 0) return scr->screen - scr2->screen;
     }
   return dif;
}

#include "e.h"

static void _e_xkb_update_event(int);

static void _e_xkb_type_reconfig(E_Pixmap_Type comp_type);
static void _e_xkb_type_update(E_Pixmap_Type comp_type, int cur_group);

static int _e_xkb_cur_group = -1;

static Ecore_Exe *cur_exe;

#ifndef HAVE_WAYLAND_ONLY
static Ecore_Event_Handler *xkb_state_handler = NULL;
static Ecore_Event_Handler *xkb_new_keyboard_handler = NULL;
static Ecore_Event_Handler *xkb_keymap_handler = NULL;
static int skip_new_keyboard = 0;
static Ecore_Timer *save_group = NULL;
#endif

E_API int E_EVENT_XKB_CHANGED = 0;

static void
_eval_cur_group(void)
{
   Eina_List *l;
   E_Config_XKB_Layout *cl2, *cl = NULL;
   int cur_group = -1;

   cl = e_config->xkb.current_layout;
   if (!cl) cl = e_config->xkb.sel_layout;

   EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl2)
     {
        cur_group++;
        if (!cl2->name) continue;
        if (!cl || e_config_xkb_layout_eq(cl, cl2))
          {
             INF("Setting keyboard layout: %s|%s|%s", cl2->name, cl2->model, cl2->variant);
             e_config->xkb.cur_group = cur_group;
             return;
          }
     }
   e_config->xkb.cur_group = 0;
}

#ifndef HAVE_WAYLAND_ONLY
static Eina_Bool
_e_xkb_init_timer(void *data EINA_UNUSED)
{
   if (!e_comp->root) return EINA_FALSE;

   _eval_cur_group();

   e_xkb_update(e_config->xkb.cur_group);
   ecore_x_xkb_track_state();
   return EINA_FALSE;
}
#endif

#ifndef HAVE_WAYLAND_ONLY

static Eina_Bool
_e_xkb_save_group(void *data)
{
   int group = (intptr_t)data;

   if (e_config->xkb.cur_group != group)
     {
        e_config->xkb.cur_group = group;
        e_config_save_queue();
        e_xkb_update(e_config->xkb.cur_group);
     }
   save_group = NULL;
   return EINA_FALSE;
}

static Eina_Bool
_xkb_new_keyboard(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   Ecore_X_Event_Xkb *ev = event;

   if (skip_new_keyboard > 0)
     {
        skip_new_keyboard --;
        return ECORE_CALLBACK_PASS_ON;
     }

   // skip just "xmodmap" changes
   if ((ev) && (ev->map_notify)) return ECORE_CALLBACK_PASS_ON;
   printf("XKB: EV: _xkb_new_keyboard\n");
   //we have to restore our settings here
   e_xkb_reconfig();
   e_xkb_update(e_config->xkb.cur_group);
   e_deskenv_xmodmap_run();

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_xkb_new_state(void* data EINA_UNUSED, int type EINA_UNUSED, void *event)
{
   Ecore_X_Event_Xkb *ev = event;

   if (save_group) ecore_timer_del(save_group);
   save_group = ecore_timer_loop_add(0.5, _e_xkb_save_group, (void *)(intptr_t)ev->group);

   return ECORE_CALLBACK_PASS_ON;
}

static Eina_Bool
_xkb_keymap(void* data EINA_UNUSED, int type EINA_UNUSED, void *event EINA_UNUSED)
{
   printf("XKB: EV: _xkb_keymap\n");
   return ECORE_CALLBACK_PASS_ON;
}
#endif

static Eina_Bool
kb_exe_del(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Exe_Event_Del *ev)
{
   if (ev->exe == cur_exe)
     cur_exe = NULL;
   return ECORE_CALLBACK_RENEW;
}

/* externally accessible functions */
E_API int
e_xkb_init(E_Pixmap_Type comp_type)
{
   if (!E_EVENT_XKB_CHANGED)
     {
        E_EVENT_XKB_CHANGED = ecore_event_type_new();
        ecore_event_handler_add(ECORE_EXE_EVENT_DEL, (Ecore_Event_Handler_Cb)kb_exe_del, NULL);
     }
#ifndef HAVE_WAYLAND_ONLY
   if (comp_type == E_PIXMAP_TYPE_X)
     {
        xkb_state_handler = ecore_event_handler_add(ECORE_X_EVENT_XKB_STATE_NOTIFY, _xkb_new_state, NULL);
        xkb_new_keyboard_handler = ecore_event_handler_add(ECORE_X_EVENT_XKB_NEWKBD_NOTIFY, _xkb_new_keyboard, NULL);
        xkb_keymap_handler = ecore_event_handler_add(ECORE_X_EVENT_WINDOW_KEYMAP, _xkb_keymap, NULL);
     }
#endif
   if (e_config->xkb.dont_touch_my_damn_keyboard) return 1;
   printf("XKB: e_xkb_init\n");

   _e_xkb_type_reconfig(comp_type);

#ifndef HAVE_WAYLAND_ONLY
   if (comp_type == E_PIXMAP_TYPE_X)
     ecore_timer_loop_add(1.5, _e_xkb_init_timer, NULL);
   else
#endif
   if (comp_type == E_PIXMAP_TYPE_WL)
     {
        _eval_cur_group();
        _e_xkb_type_update(comp_type, e_config->xkb.cur_group);
     }

   return 1;
}

E_API int
e_xkb_shutdown(void)
{
#ifndef HAVE_WAYLAND_ONLY
   E_FREE_FUNC(xkb_keymap_handler, ecore_event_handler_del);
   E_FREE_FUNC(xkb_state_handler, ecore_event_handler_del);
   E_FREE_FUNC(xkb_new_keyboard_handler, ecore_event_handler_del);
   ecore_timer_del(save_group);
   save_group = NULL;
#endif
   return 1;
}

static void
_e_x_xkb_reconfig(void)
{
   E_Config_XKB_Layout *cl;
   E_Config_XKB_Option *op;
   Eina_List *l;
   Eina_Strbuf *buf;

   if (e_config->xkb.dont_touch_my_damn_keyboard) return;
   printf("XKB: _e_x_xkb_reconfig\n");
   if ((!e_config->xkb.used_layouts) && (!e_config->xkb.used_options) && (!e_config->xkb.default_model)) return;
   if (!getenv("DISPLAY")) return;

   buf = eina_strbuf_new();
   eina_strbuf_append(buf, "setxkbmap ");

   if (e_config->xkb.used_layouts)
     {
        eina_strbuf_append(buf, "-layout '");
        EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
          {
             if (cl->name)
               {
                  eina_strbuf_append(buf, cl->name);
                  eina_strbuf_append(buf, ",");
               }
          }

        eina_strbuf_append(buf, "' -variant '");
        EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
          {
             if ((cl->variant) && (strcmp(cl->variant, "basic")))
               {
                  eina_strbuf_append(buf, cl->variant);
                  eina_strbuf_append(buf, ",");
               }
             else
               eina_strbuf_append(buf, ",");
          }
        eina_strbuf_append(buf, "'");

        /* use first entry in used layouts */
        cl = e_config->xkb.used_layouts->data;

        if (cl->model)
          {
             eina_strbuf_append(buf, " -model '");
             if (strcmp(cl->model, "default"))
               eina_strbuf_append(buf, cl->model);
             else if ((e_config->xkb.default_model) &&
                      (strcmp(e_config->xkb.default_model, "default")))
               eina_strbuf_append(buf, e_config->xkb.default_model);
             else
               eina_strbuf_append(buf, "default");
             eina_strbuf_append(buf, "'");
          }
     }
   else if (e_config->xkb.default_model)
     {
        eina_strbuf_append(buf, " -model '");
        eina_strbuf_append(buf, e_config->xkb.default_model);
        eina_strbuf_append(buf, "'");
     }

   if (e_config->xkb.used_options)
     {
        /* clear options */
        eina_strbuf_append(buf, " -option ");

        /* add in selected options */
        EINA_LIST_FOREACH(e_config->xkb.used_options, l, op)
          {
             if (op->name)
               {
                  eina_strbuf_append(buf, " -option '");
                  eina_strbuf_append(buf, op->name);
                  eina_strbuf_append(buf, "'");
               }
          }
     }
#ifndef HAVE_WAYLAND_ONLY
   skip_new_keyboard ++;
#endif
   INF("SET XKB RUN: %s", eina_strbuf_string_get(buf));
   E_FREE_FUNC(cur_exe, ecore_exe_kill);
   cur_exe = ecore_exe_run(eina_strbuf_string_get(buf), NULL);
   eina_strbuf_free(buf);
}

static void
_e_x_xkb_update(int cur_group)
{
   if (e_config->xkb.dont_touch_my_damn_keyboard) return;
   printf("XKB: _e_x_xkb_update\n");
   if ((!e_config->xkb.used_layouts) && (!e_config->xkb.used_options) && (!e_config->xkb.default_model)) return;
   if (!getenv("DISPLAY")) return;
   if (cur_group != _e_xkb_cur_group)
     {
        _e_xkb_cur_group = cur_group;
#ifndef HAVE_WAYLAND_ONLY
        if (e_comp->root)
          {
             e_comp_canvas_keys_ungrab();
             ecore_x_xkb_select_group(cur_group);
             e_comp_canvas_keys_grab();
          }
#endif
        e_deskenv_xmodmap_run();
        _e_xkb_update_event(cur_group);
        return;
     }
}


static void
_e_wl_xkb_update(int cur_group)
{
#ifdef HAVE_WAYLAND
   e_comp_wl_input_keymap_index_set(cur_group);
   _e_xkb_update_event(cur_group);
#else
   (void)cur_group;
#endif
}


static void
_e_wl_xkb_reconfig(void)
{
#ifdef HAVE_WAYLAND
   E_Config_XKB_Option *op;
   E_Config_XKB_Layout *cl;
   Eina_Strbuf *options, *layouts, *variants;
   Eina_List *l;


   options = eina_strbuf_new();

    /* create options */
   EINA_LIST_FOREACH(e_config->xkb.used_options, l, op)
     {
        if (op->name)
          {
             eina_strbuf_append(options, op->name);
             eina_strbuf_append_char(options, ',');
          }
     }

   layouts = eina_strbuf_new();
   variants = eina_strbuf_new();

   //search for the correct layout

   /* create layouts */
   EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl)
     {
        if (cl->name)
          {
             eina_strbuf_append(layouts, cl->name);
             eina_strbuf_append_char(layouts, ',');
          }
        else
          eina_strbuf_append_char(layouts, ',');

        if ((cl->variant) && (strcmp(cl->variant, "basic")))
          {
             eina_strbuf_append(variants, cl->variant);
             eina_strbuf_append_char(variants, ',');
          }
        else
          eina_strbuf_append_char(variants, ',');
     }

   /* collect model to use */

   /* set keymap to the compositor */
   e_comp_wl_input_keymap_set(NULL,
                              e_config->xkb.default_model,
                              eina_strbuf_string_get(layouts), //pool of layouts to use
                              eina_strbuf_string_get(variants),  //pool of variants to use
                              eina_strbuf_string_get(options) //list of options
                             );

   eina_strbuf_free(variants);
   eina_strbuf_free(layouts);
   eina_strbuf_free(options);
#endif
}

static void
_e_xkb_type_reconfig(E_Pixmap_Type comp_type)
{
   printf("XKB: _e_xkb_type_reconfig\n");
   if (comp_type == E_PIXMAP_TYPE_X)
     _e_x_xkb_reconfig();
   else if (comp_type == E_PIXMAP_TYPE_WL)
     _e_wl_xkb_reconfig();
}

E_API void
e_xkb_reconfig(void)
{
   _e_xkb_type_reconfig(e_comp->comp_type);
}

static void
_e_xkb_type_update(E_Pixmap_Type comp_type, int cur_group)
{
   e_config->xkb.cur_group = cur_group;
   e_config_save_queue();

   if (comp_type == E_PIXMAP_TYPE_WL)
     _e_wl_xkb_update(cur_group);
   else
     _e_x_xkb_update(cur_group);
}

E_API void
e_xkb_update(int cur_group)
{
   _e_xkb_type_update(e_comp->comp_type, cur_group);
}

E_API void
e_xkb_layout_next(void)
{
   Eina_List *l;
   E_Config_XKB_Layout *cl;

   if (e_config->xkb.dont_touch_my_damn_keyboard) return;
   printf("XKB: e_xkb_layout_next\n");
   if (!e_config->xkb.used_layouts) return;
   l = eina_list_nth_list(e_config->xkb.used_layouts, e_config->xkb.cur_group);
   l = eina_list_next(l);
   if (!l) l = e_config->xkb.used_layouts;

   e_config->xkb.cur_group = (e_config->xkb.cur_group + 1) % eina_list_count(e_config->xkb.used_layouts);
   cl = eina_list_data_get(l);
   eina_stringshare_replace(&e_config->xkb.cur_layout, cl->name);
   eina_stringshare_replace(&e_config->xkb.selected_layout, cl->name);
   INF("Setting keyboard layout: %s|%s|%s", cl->name, cl->model, cl->variant);
   e_xkb_update(e_config->xkb.cur_group);
   _e_xkb_update_event(e_config->xkb.cur_group);
   e_config_save_queue();
}

E_API void
e_xkb_layout_prev(void)
{
   Eina_List *l;
   E_Config_XKB_Layout *cl;

   if (e_config->xkb.dont_touch_my_damn_keyboard) return;
   printf("XKB: e_xkb_layout_prev\n");
   if (!e_config->xkb.used_layouts) return;
   l = eina_list_nth_list(e_config->xkb.used_layouts, e_config->xkb.cur_group);
   l = eina_list_prev(l);
   if (!l) l = eina_list_last(e_config->xkb.used_layouts);

   e_config->xkb.cur_group = (e_config->xkb.cur_group == 0) ?
     ((int)eina_list_count(e_config->xkb.used_layouts) - 1) : (e_config->xkb.cur_group - 1);
   cl = eina_list_data_get(l);
   eina_stringshare_replace(&e_config->xkb.cur_layout, cl->name);
   eina_stringshare_replace(&e_config->xkb.selected_layout, cl->name);
   INF("Setting keyboard layout: %s|%s|%s", cl->name, cl->model, cl->variant);
   e_xkb_update(e_config->xkb.cur_group);
   _e_xkb_update_event(e_config->xkb.cur_group);
   e_config_save_queue();
}

/* always use this function to get the current layout's name
 * to ensure the most accurate results!!!
 */
E_API E_Config_XKB_Layout *
e_xkb_layout_get(void)
{
   unsigned int n = 0;

   if (e_config->xkb.dont_touch_my_damn_keyboard) return NULL;
   printf("XKB: e_xkb_layout_get\n");
   if (e_config->xkb.current_layout) return e_config->xkb.current_layout;
   if (_e_xkb_cur_group >= 0)
     n = _e_xkb_cur_group;
   return eina_list_nth(e_config->xkb.used_layouts, n);
}

E_API void
e_xkb_layout_set(const E_Config_XKB_Layout *cl)
{
   Eina_List *l;
   E_Config_XKB_Layout *cl2;
   int cur_group = -1;

   EINA_SAFETY_ON_NULL_RETURN(cl);
   if (e_config->xkb.dont_touch_my_damn_keyboard) return;
   printf("XKB: e_xkb_layout_set\n");
   if (e_config_xkb_layout_eq(e_config->xkb.current_layout, cl)) return;
   cl2 = e_config_xkb_layout_dup(e_config->xkb.current_layout);
   e_config_xkb_layout_free(e_config->xkb.current_layout);
   e_config->xkb.current_layout = cl2;
   EINA_LIST_FOREACH(e_config->xkb.used_layouts, l, cl2)
     {
        cur_group++;
        if (!cl2->name) continue;
        if (e_config_xkb_layout_eq(cl, cl2))
          {
             INF("Setting keyboard layout: %s|%s|%s", cl2->name, cl2->model, cl2->variant);
             eina_stringshare_replace(&e_config->xkb.cur_layout, cl->name);
             eina_stringshare_replace(&e_config->xkb.selected_layout, cl->name);
             e_xkb_update(cur_group);
             break;
          }
     }
   _e_xkb_update_event(e_config->xkb.cur_group);
   e_config_save_queue();
}

E_API const char *
e_xkb_layout_name_reduce(const char *name)
{
   const char *s;

   if (!name) return NULL;
   s = strchr(name, '/');
   if (s) s++;
   else s = name;
   return s;
}

E_API void
e_xkb_e_icon_flag_setup(Evas_Object *eicon, const char *name)
{
   int w, h;
   char buf[PATH_MAX];

   e_xkb_flag_file_get(buf, sizeof(buf), name);
   e_icon_preload_set(eicon, EINA_FALSE);
   e_icon_file_set(eicon, buf);
   e_icon_size_get(eicon, &w, &h);
   evas_object_size_hint_aspect_set(eicon, EVAS_ASPECT_CONTROL_BOTH, w, h);
}

E_API void
e_xkb_flag_file_get(char *buf, size_t bufsize, const char *name)
{
   name = e_xkb_layout_name_reduce(name);
   snprintf(buf, bufsize, "%s/data/flags/%s_flag.png",
            e_prefix_data_get(), name ? name : "unknown");
   if (!ecore_file_exists(buf))
     snprintf(buf, bufsize, "%s/data/flags/unknown_flag.png",
              e_prefix_data_get());
}

E_API Eina_Bool
e_config_xkb_layout_eq(const E_Config_XKB_Layout *a, const E_Config_XKB_Layout *b)
{
   if (a == b) return EINA_TRUE;
   if ((!a) || (!b)) return EINA_FALSE;
   return ((a->name == b->name) && (a->model == b->model) && (a->variant == b->variant));
}

E_API void
e_config_xkb_layout_free(E_Config_XKB_Layout *cl)
{
   if (!cl) return;

   eina_stringshare_del(cl->name);
   eina_stringshare_del(cl->model);
   eina_stringshare_del(cl->variant);
   free(cl);
}

E_API E_Config_XKB_Layout *
e_config_xkb_layout_dup(const E_Config_XKB_Layout *cl)
{
   E_Config_XKB_Layout *cl2;

   EINA_SAFETY_ON_NULL_RETURN_VAL(cl, NULL);
   cl2 = E_NEW(E_Config_XKB_Layout, 1);
   cl2->name = eina_stringshare_ref(cl->name);
   cl2->model = eina_stringshare_ref(cl->model);
   cl2->variant = eina_stringshare_ref(cl->variant);
   return cl2;
}

static void
_e_xkb_update_event(int cur_group)
{
   ecore_event_add(E_EVENT_XKB_CHANGED, NULL, NULL, (intptr_t *)(long)cur_group);
}

/* TODO
   check http://www.pvv.org/~mariusbu/proposal.html
   for advances in cross toolkit settings */

#include <e.h>
//#include <X11/Xlib.h>
//#include <X11/Xmd.h>            /* For CARD16 */

// define here to avoid needing x includes directly.
#define C16                 unsigned short
#define C32                 unsigned int

#define RETRY_TIMEOUT       2.0

#define SETTING_TYPE_INT    0
#define SETTING_TYPE_STRING 1
#define SETTING_TYPE_COLOR  2

#define OFFSET_ADD(n) ((n + 4 - 1) & (~(4 - 1)))

typedef struct _Settings_Manager Settings_Manager;
typedef struct _Setting         Setting;

struct _Settings_Manager
{
   Ecore_X_Window  selection;
   Ecore_Timer    *timer_retry;
   unsigned long   serial;
   Ecore_X_Atom    _atom_xsettings_screen;
   Eina_Bool       enabled E_BITFIELD;
};

struct _Setting
{
   unsigned short type;
   const char    *name;
   struct {
      const char *value;
   } s;
   struct {
      int value;
   } i;
   struct {
      unsigned short red, green, blue, alpha;
   } c;
   unsigned long length;
   unsigned long last_change;
};

static void _e_xsettings_apply(Settings_Manager *sm);

static Ecore_X_Atom _atom_manager = 0;
static Ecore_X_Atom _atom_xsettings = 0;
static Ecore_X_Atom _atom_gtk_iconthemes = 0;
static Ecore_X_Atom _atom_gtk_rcfiles = 0;
static Settings_Manager *manager = NULL;
static Eina_List *settings = NULL;
static Eina_Bool running = EINA_FALSE;
static Eio_File *eio_op = NULL;
static Eina_Bool setting = EINA_FALSE;
static Eina_Bool reset = EINA_FALSE;
static const char _setting_icon_theme_name[] = "Net/IconThemeName";
static const char _setting_theme_name[] = "Net/ThemeName";
static const char _setting_font_name[] = "Gtk/FontName";
static const char _setting_xft_dpi[] = "Xft/DPI";
static const char _setting_xft_antialias[] = "Xft/Antialias";
static const char _setting_xft_autohint[] = "Xft/Autohint";
static const char _setting_xft_hinting[] = "Xft/Hinting";
static const char _setting_xft_hintstyle[] = "Xft/HintStyle";
static const char _setting_xft_rgba[] = "Xft/RGBA";
static const char *_setting_theme = NULL;

static void _e_xsettings_done_cb(void *data, Eio_File *handler, const Eina_Stat *stat);

static Ecore_X_Atom
_e_xsettings_atom_screen_get(int screen_num)
{
   char buf[32];

   snprintf(buf, sizeof(buf), "_XSETTINGS_S%d", screen_num);
   return ecore_x_atom_get(buf);
}

static Eina_Bool
_e_xsettings_selection_owner_set(void)
{
   Ecore_X_Atom atom;
   Ecore_X_Window cur_selection;
   Eina_Bool ret;

   atom = _e_xsettings_atom_screen_get(0);
   ecore_x_selection_owner_set(e_comp->cm_selection, atom,
                               ecore_x_current_time_get());
   ecore_x_sync();
   cur_selection = ecore_x_selection_owner_get(atom);

   ret = (cur_selection == e_comp->cm_selection);
   if (!ret)
     ERR("XSETTINGS: tried to set selection to %#x, but got %#x",
         (unsigned int)e_comp->cm_selection, cur_selection);

   return ret;
}

static void
_e_xsettings_deactivate(Settings_Manager *sm)
{
   Ecore_X_Atom atom;

   atom = _e_xsettings_atom_screen_get(0);
   ecore_x_selection_owner_set(0, atom, ecore_x_current_time_get());
   ecore_x_sync();
   sm->enabled = 0;
}

static Eina_Bool
_e_xsettings_activate(Settings_Manager *sm)
{
   Ecore_X_Atom atom;
   Ecore_X_Window old_win;

   if (sm->enabled) return 1;

   atom = _e_xsettings_atom_screen_get(0);
   old_win = ecore_x_selection_owner_get(atom);
   if (old_win != 0) return 0;

   if (!_e_xsettings_selection_owner_set())
     return 0;

   ecore_x_client_message32_send(e_comp->root, _atom_manager,
                                 ECORE_X_EVENT_MASK_WINDOW_CONFIGURE,
                                 ecore_x_current_time_get(), atom,
                                 e_comp->cm_selection, 0, 0);

   if (settings) _e_xsettings_apply(sm);
   sm->enabled = 1;

   return 1;
}

static Eina_Bool
_e_xsettings_activate_retry(void *data)
{
   Settings_Manager *sm = data;
   Eina_Bool ret;

   INF("XSETTINGS: reactivate...");
   ret = _e_xsettings_activate(sm);
   if (ret)
     INF("XSETTINGS: activate success!");
   else
     ERR("XSETTINGS: activate failure! retrying in %0.1f seconds", RETRY_TIMEOUT);

   if (!ret)
     return ECORE_CALLBACK_RENEW;

   sm->timer_retry = NULL;
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_xsettings_retry(Settings_Manager *sm)
{
   if (sm->timer_retry) return;
   sm->timer_retry = ecore_timer_loop_add
     (RETRY_TIMEOUT, _e_xsettings_activate_retry, sm);
}

static void
_e_xsettings_string_set(const char *name, const char *value)
{
   Setting *s;
   Eina_List *l;

   if (!name) return;
   if (name == _setting_theme_name)
     e_config->xsettings.net_theme_name_detected = value;
   name = eina_stringshare_add(name);

   EINA_LIST_FOREACH(settings, l, s)
     {
        if (s->type != SETTING_TYPE_STRING) continue;
        if (s->name == name) break;
     }
   if (!value)
     {
        if (!s) return;
        DBG("remove %s\n", name);
        eina_stringshare_del(name);
        eina_stringshare_del(s->name);
        eina_stringshare_del(s->s.value);
        settings = eina_list_remove(settings, s);
        E_FREE(s);
        return;
     }
   if (s)
     {
        DBG("update %s %s\n", name, value);
        eina_stringshare_del(name);
        eina_stringshare_replace(&s->s.value, value);
     }
   else
     {
        DBG("add %s %s\n", name, value);
        s = E_NEW(Setting, 1);
        s->type = SETTING_TYPE_STRING;
        s->name = name;
        s->s.value = eina_stringshare_add(value);
        settings = eina_list_append(settings, s);
     }

   /* type + pad + name-len + last-change-serial + str_len */
   s->length = 12;
   s->length += OFFSET_ADD(strlen(name));
   s->length += OFFSET_ADD(strlen(value));
   s->last_change = ecore_x_current_time_get();
}

static void
_e_xsettings_int_set(const char *name, int value, Eina_Bool set)
{
   Setting *s;
   Eina_List *l;

   if (!name) return;
   name = eina_stringshare_add(name);

   EINA_LIST_FOREACH(settings, l, s)
     {
        if (s->type != SETTING_TYPE_INT) continue;
        if (s->name == name) break;
     }
   if (!set)
     {
        if (!s) return;
        DBG("remove %s\n", name);
        eina_stringshare_del(name);
        eina_stringshare_del(s->name);
        settings = eina_list_remove(settings, s);
        E_FREE(s);
        return;
     }
   if (s)
     {
        DBG("update %s %d\n", name, value);
        eina_stringshare_del(name);
        s->i.value = value;
     }
   else
     {
        DBG("add %s %d\n", name, value);
        s = E_NEW(Setting, 1);
        s->type = SETTING_TYPE_INT;
        s->name = name;
        s->i.value = value;
        settings = eina_list_append(settings, s);
     }

   // type + pad + name-len + last-change-serial + value
   s->length = 12;
   s->length += OFFSET_ADD(strlen(name));
}

static unsigned char *
_e_xsettings_copy(unsigned char *buffer, Setting *s)
{
   size_t str_len;
   size_t len;
   C16 tmp16;
   C32 tmp32;

   buffer[0] = s->type;
   buffer[1] = 0;
   buffer += 2;

   str_len = strlen(s->name);
   tmp16 = str_len;
   memcpy(buffer, &tmp16, sizeof(C16));
   buffer += 2;

   memcpy(buffer, s->name, str_len);
   buffer += str_len;

   len = OFFSET_ADD(str_len) - str_len;
   memset(buffer, 0, len);
   buffer += len;

   tmp32 = s->last_change;
   memcpy(buffer, &tmp32, sizeof(C32));
   buffer += 4;

   switch (s->type)
     {
      case SETTING_TYPE_INT:
        tmp32 = s->i.value;
        memcpy(buffer, &tmp32, sizeof(C32));
        buffer += 4;
        break;

      case SETTING_TYPE_STRING:
        str_len = strlen(s->s.value);
        tmp32 = str_len;
        memcpy(buffer, &tmp32, sizeof(C32));
        buffer += 4;

        memcpy(buffer, s->s.value, str_len);
        buffer += str_len;

        len = OFFSET_ADD(str_len) - str_len;
        memset(buffer, 0, len);
        buffer += len;
        break;

      case SETTING_TYPE_COLOR:
        tmp16 = s->c.red;
        memcpy(buffer, &tmp16, sizeof(C16));
        buffer += 2;
        tmp16 = s->c.green;
        memcpy(buffer, &tmp16, sizeof(C16));
        buffer += 2;
        tmp16 = s->c.blue;
        memcpy(buffer, &tmp16, sizeof(C16));
        buffer += 2;
        tmp16 = s->c.alpha;
        memcpy(buffer, &tmp16, sizeof(C16));
        buffer += 2;
        break;
     }

   return buffer;
}

static void
_e_xsettings_apply(Settings_Manager *sm)
{
   unsigned char *data;
   unsigned char *pos;
   size_t len = 12;
   Setting *s;
   Eina_List *l;
   C32 tmp32;

   EINA_LIST_FOREACH(settings, l, s)
     len += s->length;

   pos = data = calloc(1, len);
   if (!data) return;

#if (defined __BYTE_ORDER && __BYTE_ORDER == __LITTLE_ENDIAN) || (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
   *pos = 0; //LSBFirst
#else
   *pos = 1; //MSBFirst
#endif

   pos += 4;
   tmp32 = sm->serial++;
   memcpy(pos, &tmp32, sizeof(C32));
   pos += 4;
   tmp32 = eina_list_count(settings);
   memcpy(pos, &tmp32, sizeof(C32));
   pos += 4;

   EINA_LIST_FOREACH(settings, l, s)
     pos = _e_xsettings_copy(pos, s);

   ecore_x_window_prop_property_set(e_comp->cm_selection,
                                    _atom_xsettings,
                                    _atom_xsettings,
                                    8, data, len);
   free(data);
}

static void
_e_xsettings_update(void)
{
   if (e_comp->cm_selection) _e_xsettings_apply(manager);
}

static void
_e_xsettings_gtk_icon_update(void)
{
   const Eina_List *l;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     if (ec->icccm.state)
       ecore_x_client_message8_send(e_client_util_win_get(ec),
                                    _atom_gtk_iconthemes, NULL, 0);
}

static void
_e_xsettings_gtk_rcfiles_update(void)
{
   const Eina_List *l;
   E_Client *ec;

   EINA_LIST_FOREACH(e_comp->clients, l, ec)
     if (ec->icccm.state)
       ecore_x_client_message8_send(e_client_util_win_get(ec),
                                    _atom_gtk_rcfiles, NULL, 0);
}

static void
_e_xsettings_icon_theme_set(void)
{
   if (e_config->xsettings.match_e17_icon_theme)
     {
        _e_xsettings_string_set(_setting_icon_theme_name,
                                e_config->icon_theme);
        return;
     }

   if (e_config->xsettings.net_icon_theme_name)
     {
        _e_xsettings_string_set(_setting_icon_theme_name,
                                e_config->xsettings.net_icon_theme_name);
        return;
     }

   _e_xsettings_string_set(_setting_icon_theme_name, NULL);
}

static void
_e_xsettings_error_cb(void *data, Eio_File *handler EINA_UNUSED, int error EINA_UNUSED)
{
   Eina_List *l = data;
   if (reset || setting)
     {
        char buf[PATH_MAX];
        if (reset || (!l)) l = efreet_data_dirs_get();
        else if (l)
          l = l->next;
        reset = EINA_FALSE;
        if (l)
          {
             snprintf(buf, sizeof(buf), "%s/themes/%s",
                      (char *)eina_list_data_get(l), _setting_theme);
             eio_op = eio_file_direct_stat(buf, _e_xsettings_done_cb,
                                           _e_xsettings_error_cb, l);
             return;
          }
     }
   eio_op = NULL;
   setting = EINA_FALSE;
   _setting_theme = NULL;

   if (e_config->xsettings.net_theme_name)
     _e_xsettings_string_set(_setting_theme_name,
                             e_config->xsettings.net_theme_name);
   else
     _e_xsettings_string_set(_setting_theme_name, NULL);
   _e_xsettings_update();
}

static void
_e_xsettings_done_cb(void *data EINA_UNUSED, Eio_File *handler EINA_UNUSED, const Eina_Stat *estat EINA_UNUSED)
{
   if (reset)
     {
        /* should not happen */
        _e_xsettings_error_cb(NULL, NULL, 0);
        return;
     }
   _e_xsettings_string_set(_setting_theme_name, _setting_theme);
   _setting_theme = NULL;
   eio_op = NULL;
   setting = EINA_FALSE;
   _e_xsettings_update();
}

static void
_e_xsettings_theme_set(void)
{
   if (e_config->xsettings.match_e17_theme)
     {
        const char *file;

        file = e_theme_edje_file_get(NULL, "e/desktop/background");
        if (file)
          {
             if ((_setting_theme = edje_file_data_get(file, "gtk-theme")))
               {
                  char buf[PATH_MAX];

                  e_user_homedir_snprintf(buf, sizeof(buf),
                                          ".themes/%s", _setting_theme);
                  eio_op = eio_file_direct_stat(buf, _e_xsettings_done_cb,
                                                _e_xsettings_error_cb, NULL);
                  setting = EINA_TRUE;
                  return;
               }
          }
     }

   if (e_config->xsettings.net_theme_name)
     {
        _e_xsettings_string_set(_setting_theme_name,
                                e_config->xsettings.net_theme_name);
        return;
     }

   _e_xsettings_string_set(_setting_theme_name, NULL);
}

static int
_dbl_to_str(char *buf, size_t buf_size, double val, int decimals)
{
   int ival;
   size_t l = 0;

   if (val < 0)
     {
        if (buf_size < 2) return -1;
        val = -val;
        buf[0] = '-';
        buf[1] = 0;
        l++;
     }
   ival = val;
   snprintf(buf + l, buf_size - l, "%i", ival);
   if (decimals <= 0) return 0;
   l = strlen(buf);
   if (l >= (buf_size - 2)) return -1;
   buf[l] = '.';
   buf[l + 1] = 0;
   l++;

   while (decimals > 0)
     {
        if (l >= (buf_size - 1)) return -1;
        val *= 10.0;
        ival = val;
        val = val - ival;
        buf[l] = '0' + (ival % 10);
        buf[l + 1] = 0;
        l++;
        if (l >= (buf_size - 1)) return -1;
        decimals--;
     }
   return 0;
}

static void
_e_xsettings_font_set(void)
{
   E_Font_Default *efd;
   E_Font_Properties *efp;

   efd = e_font_default_get("application");

   if (efd && efd->font)
     {
        efp = e_font_fontconfig_name_parse(efd->font);
        if (efp->name)
          {
             Eina_Strbuf *buf;
             Eina_List *l;
             int size = efd->size;
             char size_buf[32];
             const char *p;

             /* TODO better way to convert evas font sizes? */
             if      (!size)      size  =   10; // default if not set
             else if (size < 0)   size /=  -10; // in 1/10th units

             // limit sizes
             if      (size < 5)   size  =    5; // don't allow too small
             else if (size > 100) size  =  100; // don't allow silly sizes

             _dbl_to_str(size_buf, sizeof(size_buf), (double)size, 0);

             buf = eina_strbuf_new();
             eina_strbuf_append(buf, efp->name);
             eina_strbuf_append_char(buf, ' ');
             EINA_LIST_FOREACH(efp->styles, l, p)
               {
                  eina_strbuf_append(buf, p);
                  eina_strbuf_append_char(buf, ' ');
               }
             eina_strbuf_append(buf, size_buf);
             _e_xsettings_string_set(_setting_font_name,
                                     eina_strbuf_string_get(buf));
             eina_strbuf_free(buf);
             e_font_properties_free(efp);
             return;
          }

        e_font_properties_free(efp);
     }
   _e_xsettings_string_set(_setting_font_name, NULL);
}

static void
_e_xsettings_dpi_set(void)
{
   if (e_config->scale.set_xapp_dpi)
     _e_xsettings_int_set(_setting_xft_dpi,
                          (double)e_config->scale.xapp_base_dpi * e_scale * 1024.0,
                          EINA_TRUE);
   _e_xsettings_int_set(_setting_xft_antialias,
                        1,
                        EINA_TRUE);
   _e_xsettings_int_set(_setting_xft_hinting,
                        1,
                        EINA_TRUE);
   _e_xsettings_int_set(_setting_xft_autohint,
                        0,
                        EINA_TRUE);
   _e_xsettings_string_set(_setting_xft_hintstyle,
                           "hintfull");
   _e_xsettings_string_set(_setting_xft_rgba,
                           "none");
}

static void
_e_xsettings_cursor_path_set(void)
{
   struct stat st;
   char buf[PATH_MAX], env[PATH_MAX + PATH_MAX + 100], *path;

   e_user_homedir_concat_static(buf, ".icons");

   if (stat(buf, &st)) return;
   path = getenv("XCURSOR_PATH");
   if (path)
     {
        if (strstr(path, buf)) return;
        snprintf(env, sizeof(env), "%s:%s", buf, path);
        path = env;
     }
   else
     {
        snprintf(env, sizeof(env), "%s:%s", buf, "/usr/share/icons");
        path = env;
     }
   e_env_set("XCURSOR_PATH", path);
}

static void
_e_xsettings_start(void)
{
   if (running) return;

   _e_xsettings_dpi_set();
   _e_xsettings_theme_set();
   _e_xsettings_icon_theme_set();
   _e_xsettings_font_set();
   _e_xsettings_cursor_path_set();

   manager = E_NEW(Settings_Manager, 1);

   if (!_e_xsettings_activate(manager))
     _e_xsettings_retry(manager);

   running = EINA_TRUE;
}

static void
_e_xsettings_stop(void)
{
   Setting *s;

   if (!running) return;

   if (manager->timer_retry)
     ecore_timer_del(manager->timer_retry);

   if ((!stopping) && (!x_fatal))
     _e_xsettings_deactivate(manager);

   E_FREE(manager);

   EINA_LIST_FREE(settings, s)
     {
        if (s->name) eina_stringshare_del(s->name);
        if (s->s.value) eina_stringshare_del(s->s.value);
        E_FREE(s);
     }

   running = EINA_FALSE;
}

EINTERN int
e_xsettings_init(void)
{
   _atom_manager = ecore_x_atom_get("MANAGER");
   _atom_xsettings = ecore_x_atom_get("_XSETTINGS_SETTINGS");
   _atom_gtk_iconthemes = ecore_x_atom_get("_GTK_LOAD_ICONTHEMES");
   _atom_gtk_rcfiles = ecore_x_atom_get("_GTK_READ_RCFILES");

   if (e_config->xsettings.enabled)
     {
        _e_xsettings_start();
        if (!getenv("E_RESTART"))
          _e_xsettings_gtk_rcfiles_update();
     }

   return 1;
}

EINTERN int
e_xsettings_shutdown(void)
{
   _e_xsettings_stop();
   if (eio_op) eio_file_cancel(eio_op);
   eio_op = NULL;
   setting = EINA_FALSE;

   return 1;
}

static void
_update_sequence(void)
{
   _e_xsettings_dpi_set();
   _e_xsettings_theme_set();
   _e_xsettings_icon_theme_set();
   _e_xsettings_font_set();
   _e_xsettings_update();
   _e_xsettings_gtk_icon_update();
}

E_API void
e_xsettings_config_update(void)
{
   if (!_atom_manager) return;
   setting = EINA_FALSE;
   if (eio_op) eio_file_cancel(eio_op);
   if (!e_config->xsettings.enabled)
     {
        _e_xsettings_stop();
        return;
     }

   if (!running)
     {
        _e_xsettings_start();
     }
   else
     {
        _update_sequence();
        reset = EINA_TRUE;
     }
}

#include "e.h"

/* E_Zone is a child object of E_Comp. There is one zone per screen
 * in a xinerama setup. Each zone has one or more desktops.
 */

static void        _e_zone_free(E_Zone *zone);
static void        _e_zone_cb_bg_mouse_down(void *data,
                                            Evas *evas,
                                            Evas_Object *obj,
                                            void *event_info);
static void        _e_zone_cb_bg_mouse_up(void *data,
                                          Evas *evas,
                                          Evas_Object *obj,
                                          void *event_info);
static Eina_Bool   _e_zone_cb_edge_timer(void *data);
static void        _e_zone_event_generic_free(void *data, void *ev);
static void        _e_zone_object_del_attach(void *o);
static E_Zone_Edge _e_zone_detect_edge(E_Zone *zone, Evas_Object *obj);
static void        _e_zone_edge_move_resize(E_Zone *zone);
static void _e_zone_obstacle_free(E_Zone_Obstacle *obs);

E_API int E_EVENT_ZONE_DESK_COUNT_SET = 0;
E_API int E_EVENT_POINTER_WARP = 0;
E_API int E_EVENT_ZONE_USEFUL_GEOMETRY_CHANGED = 0;
E_API int E_EVENT_ZONE_MOVE_RESIZE = 0;
E_API int E_EVENT_ZONE_ADD = 0;
E_API int E_EVENT_ZONE_DEL = 0;
E_API int E_EVENT_ZONE_EDGE_IN = 0;
E_API int E_EVENT_ZONE_EDGE_OUT = 0;
E_API int E_EVENT_ZONE_EDGE_MOVE = 0;
E_API int E_EVENT_ZONE_STOW = 0;
E_API int E_EVENT_ZONE_UNSTOW = 0;

#define E_ZONE_FLIP_LEFT(zone)  (((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1)) || ((zone)->desk_x_current > 0)) && (zone)->edge.left)
#define E_ZONE_FLIP_RIGHT(zone) (((e_config->desk_flip_wrap && ((zone)->desk_x_count > 1)) || (((zone)->desk_x_current + 1) < (zone)->desk_x_count)) && (zone)->edge.right)
#define E_ZONE_FLIP_UP(zone)    (((e_config->desk_flip_wrap && ((zone)->desk_y_count > 1)) || ((zone)->desk_y_current > 0)) && (zone)->edge.top)
#define E_ZONE_FLIP_DOWN(zone)  (((e_config->desk_flip_wrap && ((zone)->desk_y_count > 1)) || (((zone)->desk_y_current + 1) < (zone)->desk_y_count)) && (zone)->edge.bottom)

#define E_ZONE_CORNER_RATIO 0.025;

EINTERN int
e_zone_init(void)
{
   E_EVENT_ZONE_DESK_COUNT_SET = ecore_event_type_new();
   E_EVENT_POINTER_WARP = ecore_event_type_new();
   E_EVENT_ZONE_MOVE_RESIZE = ecore_event_type_new();
   E_EVENT_ZONE_USEFUL_GEOMETRY_CHANGED = ecore_event_type_new();
   E_EVENT_ZONE_ADD = ecore_event_type_new();
   E_EVENT_ZONE_DEL = ecore_event_type_new();
   E_EVENT_ZONE_EDGE_IN = ecore_event_type_new();
   E_EVENT_ZONE_EDGE_OUT = ecore_event_type_new();
   E_EVENT_ZONE_EDGE_MOVE = ecore_event_type_new();
   E_EVENT_ZONE_STOW = ecore_event_type_new();
   E_EVENT_ZONE_UNSTOW = ecore_event_type_new();
   return 1;
}

EINTERN int
e_zone_shutdown(void)
{
   return 1;
}

E_API void
e_zone_all_edge_flip_eval(void)
{
   const Eina_List *l;
   E_Zone *zone;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     e_zone_edge_flip_eval(zone);
}

static void
_e_zone_cb_mouse_in(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_In *ev = event_info;
   E_Event_Zone_Edge *zev;
   E_Zone_Edge edge;
   E_Zone *zone = data;

   if (!ev->timestamp) return;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   edge = _e_zone_detect_edge(zone, obj);
   if (edge == E_ZONE_EDGE_NONE) return;

   zev = E_NEW(E_Event_Zone_Edge, 1);
   zev->zone = zone;
   zev->edge = edge;
   zev->x = ev->output.x;
   zev->y = ev->output.y;
   zev->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);
   zev->drag = !!evas_pointer_button_down_mask_get(e_comp->evas);

   ecore_event_add(E_EVENT_ZONE_EDGE_IN, zev, NULL, NULL);
   if (e_bindings_edge_in_event_handle(E_BINDING_CONTEXT_ZONE, E_OBJECT(zone), zev))
     ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
_e_zone_cb_mouse_out(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_Out *ev = event_info;
   E_Event_Zone_Edge *zev;
   E_Zone_Edge edge;
   E_Zone *zone = data;

   if (!ev->timestamp) return;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   edge = _e_zone_detect_edge(zone, obj);
   if (edge == E_ZONE_EDGE_NONE) return;

   zev = E_NEW(E_Event_Zone_Edge, 1);
   zev->zone = zone;
   zev->edge = edge;
   zev->x = ev->output.x;
   zev->y = ev->output.y;
   zev->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);
   zev->drag = !!evas_pointer_button_down_mask_get(e_comp->evas);

   ecore_event_add(E_EVENT_ZONE_EDGE_OUT, zev, NULL, NULL);
   if (e_bindings_edge_out_event_handle(E_BINDING_CONTEXT_ZONE, E_OBJECT(zone), zev))
     ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
_e_zone_cb_mouse_down(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   E_Event_Zone_Edge *zev;
   E_Zone_Edge edge;
   E_Zone *zone = data;

   if (!ev->timestamp) return;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   edge = _e_zone_detect_edge(zone, obj);
   if (edge == E_ZONE_EDGE_NONE) return;

   zev = E_NEW(E_Event_Zone_Edge, 1);
   zev->zone = zone;
   zev->edge = edge;
   zev->x = ev->output.x;
   zev->y = ev->output.y;
   zev->button = ev->button;
   zev->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);
   ecore_event_add(E_EVENT_ZONE_EDGE_OUT, zev, NULL, NULL);
   if (e_bindings_edge_down_event_handle(E_BINDING_CONTEXT_ZONE, E_OBJECT(zone), zev))
     ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
_e_zone_cb_mouse_up(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_Up *ev = event_info;
   E_Event_Zone_Edge *zev;
   E_Zone_Edge edge;
   E_Zone *zone = data;

   if (!ev->timestamp) return;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   edge = _e_zone_detect_edge(zone, obj);
   if (edge == E_ZONE_EDGE_NONE) return;

   zev = E_NEW(E_Event_Zone_Edge, 1);
   zev->zone = zone;
   zev->edge = edge;
   zev->x = ev->output.x;
   zev->y = ev->output.y;
   zev->button = ev->button;
   zev->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);
   ecore_event_add(E_EVENT_ZONE_EDGE_OUT, zev, NULL, NULL);
   if (e_bindings_edge_up_event_handle(E_BINDING_CONTEXT_ZONE, E_OBJECT(zone), zev))
     ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
_e_zone_cb_mouse_move(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info)
{
   Evas_Event_Mouse_Move *ev = event_info;
   E_Event_Zone_Edge *zev;
   E_Zone_Edge edge;
   E_Zone *zone = data;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   edge = _e_zone_detect_edge(zone, obj);
   if (edge == E_ZONE_EDGE_NONE) return;

   zev = E_NEW(E_Event_Zone_Edge, 1);
   zev->zone = zone;
   zev->edge = edge;
   zev->x = ev->cur.output.x;
   zev->y = ev->cur.output.y;
   zev->modifiers = e_bindings_evas_modifiers_convert(ev->modifiers);
   ecore_event_add(E_EVENT_ZONE_EDGE_MOVE, zev, NULL, NULL);
}

E_API E_Zone *
e_zone_new(int num, int id, int x, int y, int w, int h)
{
   E_Zone *zone;
   Evas_Object *o;
   E_Event_Zone_Add *ev;
   char name[40];

   zone = E_OBJECT_ALLOC(E_Zone, E_ZONE_TYPE, _e_zone_free);
   if (!zone) return NULL;

   zone->x = x;
   zone->y = y;
   zone->w = w;
   zone->h = h;
   zone->num = num;
   zone->id = id;

   zone->useful_geometry_dirty = 1;
   zone->useful_geometry[0].x = zone->useful_geometry[0].y =
     zone->useful_geometry[0].w = zone->useful_geometry[0].h = -1;
   zone->useful_geometry[1].x = zone->useful_geometry[1].y =
     zone->useful_geometry[1].w = zone->useful_geometry[1].h = -1;

   //printf("@@@@@@@@@@ e_zone_new: %i %i | %i %i %ix%i = %p\n", num, id, x, y, w, h, zone);

   snprintf(name, sizeof(name), "Zone %d", zone->num);
   zone->name = eina_stringshare_add(name);

   e_comp->zones = eina_list_append(e_comp->zones, zone);

   o = evas_object_rectangle_add(e_comp->evas);
   zone->bg_clip_object = o;
   evas_object_repeat_events_set(o, 1);
   evas_object_layer_set(o, E_LAYER_BG);
   evas_object_name_set(o, "zone->bg_clip_object");
   evas_object_move(o, x, y);
   evas_object_resize(o, w, h);
   evas_object_color_set(o, 255, 255, 255, 255);
   evas_object_show(o);

   o = evas_object_rectangle_add(e_comp->evas);
   zone->bg_event_object = o;
   evas_object_name_set(o, "zone->bg_event_object");
   evas_object_clip_set(o, zone->bg_clip_object);
   evas_object_layer_set(o, E_LAYER_BG);
   evas_object_repeat_events_set(o, 1);
   evas_object_move(o, x, y);
   evas_object_resize(o, w, h);
   evas_object_color_set(o, 0, 0, 0, 0);
   evas_object_repeat_events_set(o, 1);
   evas_object_show(o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_DOWN, _e_zone_cb_bg_mouse_down, zone);
   evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_UP, _e_zone_cb_bg_mouse_up, zone);

   /* TODO: config the ecore_evas type. */

   zone->desk_x_count = 0;
   zone->desk_y_count = 0;
   zone->desk_x_current = 0;
   zone->desk_y_current = 0;
   e_zone_desk_count_set(zone, e_config->zone_desks_x_count,
                         e_config->zone_desks_y_count);

   e_object_del_attach_func_set(E_OBJECT(zone), _e_zone_object_del_attach);

   e_zone_all_edge_flip_eval();

   if (starting) return zone;

   ev = E_NEW(E_Event_Zone_Add, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_ADD, ev, _e_zone_event_generic_free, NULL);

   return zone;
}

E_API void
e_zone_name_set(E_Zone *zone,
                const char *name)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if (zone->name) eina_stringshare_del(zone->name);
   zone->name = eina_stringshare_add(name);
}

static void
e_zone_reconfigure_clients(E_Zone *zone, int dx, int dy)
{
   E_Client *ec;

   E_CLIENT_FOREACH(ec)
     {
        if (ec->zone != zone) continue;

        if ((dx != 0) || (dy != 0))
          evas_object_move(ec->frame, ec->x + dx, ec->y + dy);
     }
}

E_API void
e_zone_move(E_Zone *zone,
            int x,
            int y)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   e_zone_move_resize(zone, x, y, zone->w, zone->h);
}

E_API void
e_zone_resize(E_Zone *zone,
              int w,
              int h)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   e_zone_move_resize(zone, zone->x, zone->y, w, h);
}

E_API Eina_Bool
e_zone_move_resize(E_Zone *zone,
                   int x,
                   int y,
                   int w,
                   int h)
{
   E_Event_Zone_Move_Resize *ev;
   int dx, dy;

   E_OBJECT_CHECK_RETURN(zone, EINA_FALSE);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, EINA_FALSE);

   if ((x == zone->x) && (y == zone->y) && (w == zone->w) && (h == zone->h))
     return EINA_FALSE;

   dx = x - zone->x;
   dy = y - zone->y;
   zone->x = x;
   zone->y = y;
   zone->w = w;
   zone->h = h;

   evas_object_move(zone->bg_object, x, y);
   evas_object_move(zone->bg_event_object, x, y);
   evas_object_move(zone->bg_clip_object, x, y);
   evas_object_resize(zone->bg_object, w, h);
   evas_object_resize(zone->bg_event_object, w, h);
   evas_object_resize(zone->bg_clip_object, w, h);

   ev = E_NEW(E_Event_Zone_Move_Resize, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_MOVE_RESIZE, ev,
                   _e_zone_event_generic_free, NULL);

   _e_zone_edge_move_resize(zone);
   e_zone_bg_reconfigure(zone);
   e_zone_reconfigure_clients(zone, dx, dy);
   e_zone_useful_geometry_dirty(zone);
   return EINA_TRUE;
}

E_API E_Zone *
e_zone_current_get(void)
{
   Eina_List *l = NULL;
   E_Zone *zone;

   if (!e_comp) return NULL;
   if (!starting)
     {
        int x, y;

        ecore_evas_pointer_xy_get(e_comp->ee, &x, &y);
        EINA_LIST_FOREACH(e_comp->zones, l, zone)
          {
             if (E_INSIDE(x, y, zone->x, zone->y, zone->w, zone->h))
               return zone;
          }
     }
   if (!e_comp->zones) return NULL;
   return eina_list_data_get(e_comp->zones);
}

E_API void
e_zone_bg_reconfigure(E_Zone *zone)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   e_bg_zone_update(zone, E_BG_TRANSITION_CHANGE);
}

E_API void
e_zone_flip_coords_handle(E_Zone *zone,
                          int x,
                          int y)
{
   E_Event_Zone_Edge *zev;
   E_Binding_Edge *binding;
   E_Zone_Edge edge;
   Eina_List *l;
   E_Shelf *es;
   int ok = 0;
   int one_row = 1;
   int one_col = 1;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if (zone->flip.switching)
     {
        int cx, cy, w, h;

        switch (zone->flip.switching)
          {
           case E_ZONE_EDGE_LEFT:
             evas_object_geometry_get(zone->edge.left, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               zone->flip.switching = E_ZONE_EDGE_NONE;
             break;
           case E_ZONE_EDGE_RIGHT:
             evas_object_geometry_get(zone->edge.right, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               zone->flip.switching = E_ZONE_EDGE_NONE;
             break;
           case E_ZONE_EDGE_TOP:
             evas_object_geometry_get(zone->edge.top, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               zone->flip.switching = E_ZONE_EDGE_NONE;
             break;
           case E_ZONE_EDGE_BOTTOM:
             evas_object_geometry_get(zone->edge.bottom, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               zone->flip.switching = E_ZONE_EDGE_NONE;
             break;

           case E_ZONE_EDGE_TOP_LEFT:
             evas_object_geometry_get(zone->corner.left_top, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               {
                  evas_object_geometry_get(zone->corner.top_left, &cx, &cy, &w, &h);
                  if (!E_INSIDE(x, y, cx, cy, w, h))
                    zone->flip.switching = E_ZONE_EDGE_NONE;
               }
             break;
           case E_ZONE_EDGE_TOP_RIGHT:
             evas_object_geometry_get(zone->corner.right_top, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               {
                  evas_object_geometry_get(zone->corner.top_right, &cx, &cy, &w, &h);
                  if (!E_INSIDE(x, y, cx, cy, w, h))
                    zone->flip.switching = E_ZONE_EDGE_NONE;
               }
             break;
           case E_ZONE_EDGE_BOTTOM_RIGHT:
             evas_object_geometry_get(zone->corner.right_bottom, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               {
                  evas_object_geometry_get(zone->corner.bottom_right, &cx, &cy, &w, &h);
                  if (!E_INSIDE(x, y, cx, cy, w, h))
                    zone->flip.switching = E_ZONE_EDGE_NONE;
               }
             break;
           case E_ZONE_EDGE_BOTTOM_LEFT:
             evas_object_geometry_get(zone->corner.left_bottom, &cx, &cy, &w, &h);
             if (!E_INSIDE(x, y, cx, cy, w, h))
               {
                  evas_object_geometry_get(zone->corner.bottom_left, &cx, &cy, &w, &h);
                  if (!E_INSIDE(x, y, cx, cy, w, h))
                    zone->flip.switching = E_ZONE_EDGE_NONE;
               }
             break;
             default: break;
          }
        if (zone->flip.switching) return;
     }

   if (!e_config->edge_flip_dragging) return;
   /* if we have only 1 row we can flip up/down even if we have xinerama */
   if (eina_list_count(e_comp->zones) > 1)
     {
        Eina_List *zones;
        E_Zone *next_zone;
        int cx, cy;

        zones = e_comp->zones;
        next_zone = (E_Zone *)eina_list_data_get(zones);
        cx = next_zone->x;
        cy = next_zone->y;
        zones = eina_list_next(zones);
        EINA_LIST_FOREACH(eina_list_next(zones), zones, next_zone)
          {
             if (next_zone->x != cx) one_col = 0;
             if (next_zone->y != cy) one_row = 0;
          }
     }
   if (!E_INSIDE(x, y, zone->x, zone->y, zone->w, zone->h))
     goto noflip;
   if ((one_row) && (y == 0))
     edge = E_ZONE_EDGE_TOP;
   else if ((one_col) && (x == (zone->w - 1)))
     edge = E_ZONE_EDGE_RIGHT;
   else if ((one_row) && (y == (zone->h - 1)))
     edge = E_ZONE_EDGE_BOTTOM;
   else if ((one_col) && (x == 0))
     edge = E_ZONE_EDGE_LEFT;
   else
     {
noflip:
        if (zone->flip.es)
          e_shelf_toggle(zone->flip.es, 0);
        zone->flip.es = NULL;
        return;
     }
   EINA_LIST_FOREACH(e_shelf_list(), l, es)
     {
        if (es->zone != zone) continue;
        switch (es->gadcon->orient)
          {
           case E_GADCON_ORIENT_TOP:
           case E_GADCON_ORIENT_CORNER_TL:
           case E_GADCON_ORIENT_CORNER_TR:
             if (edge == E_ZONE_EDGE_TOP) ok = 1;
             break;

           case E_GADCON_ORIENT_BOTTOM:
           case E_GADCON_ORIENT_CORNER_BL:
           case E_GADCON_ORIENT_CORNER_BR:
             if (edge == E_ZONE_EDGE_BOTTOM) ok = 1;
             break;

           case E_GADCON_ORIENT_LEFT:
           case E_GADCON_ORIENT_CORNER_LT:
           case E_GADCON_ORIENT_CORNER_LB:
             if (edge == E_ZONE_EDGE_LEFT) ok = 1;
             break;

           case E_GADCON_ORIENT_RIGHT:
           case E_GADCON_ORIENT_CORNER_RT:
           case E_GADCON_ORIENT_CORNER_RB:
             if (edge == E_ZONE_EDGE_RIGHT) ok = 1;
             break;

           default:
             ok = 0;
             break;
          }

        if (!ok) continue;
        if (!E_INSIDE(x, y, es->x, es->y, es->w, es->h))
          continue;

        if (zone->flip.es)
          e_shelf_toggle(zone->flip.es, 0);

        zone->flip.es = es;
        e_shelf_toggle(es, 1);
     }
   ok = 0;
   switch (edge)
     {
      case E_ZONE_EDGE_LEFT:
        if (E_ZONE_FLIP_LEFT(zone)) ok = 1;
        break;

      case E_ZONE_EDGE_TOP:
        if (E_ZONE_FLIP_UP(zone)) ok = 1;
        break;

      case E_ZONE_EDGE_RIGHT:
        if (E_ZONE_FLIP_RIGHT(zone)) ok = 1;
        break;

      case E_ZONE_EDGE_BOTTOM:
        if (E_ZONE_FLIP_DOWN(zone)) ok = 1;
        break;

      default:
        ok = 0;
        break;
     }
   if (!ok) return;
   binding = e_bindings_edge_get("desk_flip_in_direction", edge, 0);
   if (!binding) binding = e_bindings_edge_get("desk_flip_by", edge, 0);
   if (binding && (!binding->timer))
     {
        zev = E_NEW(E_Event_Zone_Edge, 1);
        zev->zone = zone;
        zev->x = x;
        zev->y = y;
        zev->edge = edge;
        zone->flip.ev = zev;
        zone->flip.bind = binding;
        zone->flip.switching = edge;
        binding->timer = ecore_timer_loop_add(((double)binding->delay), _e_zone_cb_edge_timer, zone);
     }
}

E_API void
e_zone_desk_count_set(E_Zone *zone,
                      int x_count,
                      int y_count)
{
   E_Desk **new_desks;
   E_Desk *desk, *new_desk;
   E_Client *ec;
   E_Event_Zone_Desk_Count_Set *ev;
   int x, y, xx, yy, moved, nx, ny;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   xx = x_count;
   if (xx < 1) xx = 1;
   yy = y_count;
   if (yy < 1) yy = 1;

   /* Orphaned window catcher; in case desk count gets reset */
   moved = 0;
   if (zone->desk_x_current >= xx) moved = 1;
   if (zone->desk_y_current >= yy) moved = 1;
   if (moved)
     {
        nx = zone->desk_x_current;
        ny = zone->desk_y_current;
        if (zone->desk_x_current >= xx) nx = xx - 1;
        if (zone->desk_y_current >= yy) ny = yy - 1;
        e_desk_show(e_desk_at_xy_get(zone, nx, ny));
     }

   new_desks = malloc(xx * yy * sizeof(E_Desk *));
   for (x = 0; x < xx; x++)
     {
        for (y = 0; y < yy; y++)
          {
             if ((x < zone->desk_x_count) && (y < zone->desk_y_count))
               desk = zone->desks[x + (y * zone->desk_x_count)];
             else
               desk = e_desk_new(zone, x, y);
             new_desks[x + (y * xx)] = desk;
          }
     }

   /* catch windows that have fallen off the end if we got smaller */
   if (xx < zone->desk_x_count)
     {
        for (y = 0; y < zone->desk_y_count; y++)
          {
             new_desk = zone->desks[xx - 1 + (y * zone->desk_x_count)];
             for (x = xx; x < zone->desk_x_count; x++)
               {
                  desk = zone->desks[x + (y * zone->desk_x_count)];

                  E_CLIENT_FOREACH(ec)
                    {
                       if (ec->desk == desk)
                         e_client_desk_set(ec, new_desk);
                    }
                  e_object_del(E_OBJECT(desk));
               }
          }
     }
   if (yy < zone->desk_y_count)
     {
        for (x = 0; x < zone->desk_x_count; x++)
          {
             new_desk = zone->desks[x + ((yy - 1) * zone->desk_x_count)];
             for (y = yy; y < zone->desk_y_count; y++)
               {
                  desk = zone->desks[x + (y * zone->desk_x_count)];

                  E_CLIENT_FOREACH(ec)
                    {
                       if (ec->desk == desk)
                         e_client_desk_set(ec, new_desk);
                    }
                  e_object_del(E_OBJECT(desk));
               }
          }
     }
   free(zone->desks);
   zone->desks = new_desks;

   zone->desk_x_count = xx;
   zone->desk_y_count = yy;
   e_config->zone_desks_x_count = xx;
   e_config->zone_desks_y_count = yy;
   e_config_save_queue();

   /* Cannot call desk_current_get until the zone desk counts have been set
    * or else we end up with a "white background" because desk_current_get will
    * return NULL.
    */
   desk = e_desk_current_get(zone);
   if (desk)
     {
        /* need to simulate "startup" conditions to force desk show to reevaluate here */
        int s = starting;
        desk->visible = 0;
        starting = 1;
        e_desk_show(desk);
        starting = s;
     }

   e_zone_edge_flip_eval(zone);

   ev = E_NEW(E_Event_Zone_Desk_Count_Set, 1);
   if (!ev) return;
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_DESK_COUNT_SET, ev,
                   _e_zone_event_generic_free, NULL);
}

E_API void
e_zone_desk_count_get(E_Zone *zone,
                      int *x_count,
                      int *y_count)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if (x_count) *x_count = zone->desk_x_count;
   if (y_count) *y_count = zone->desk_y_count;
}

E_API void
e_zone_desk_flip_by(E_Zone *zone,
                    int dx,
                    int dy)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   dx = zone->desk_x_current + dx;
   dy = zone->desk_y_current + dy;
   e_zone_desk_flip_to(zone, dx, dy);
   e_zone_edge_flip_eval(zone);
}

E_API void
e_zone_desk_flip_to(E_Zone *zone,
                    int x,
                    int y)
{
   E_Desk *desk;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   if (e_config->desk_flip_wrap)
     {
        x = x % zone->desk_x_count;
        y = y % zone->desk_y_count;
        if (x < 0) x += zone->desk_x_count;
        if (y < 0) y += zone->desk_y_count;
     }
   else
     {
        if (x < 0) x = 0;
        else if (x >= zone->desk_x_count)
          x = zone->desk_x_count - 1;
        if (y < 0) y = 0;
        else if (y >= zone->desk_y_count)
          y = zone->desk_y_count - 1;
     }
   desk = e_desk_at_xy_get(zone, x, y);
   if (!desk) return;
   e_desk_show(desk);
   e_zone_edge_flip_eval(zone);
}

E_API void
e_zone_desk_linear_flip_by(E_Zone *zone,
                           int dx)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   dx = zone->desk_x_current +
     (zone->desk_y_current * zone->desk_x_count) + dx;
   if ((!e_config->desk_flip_wrap) &&
     ((dx < 0) || (dx >= zone->desk_x_count * zone->desk_y_count))) return;
   dx = dx % (zone->desk_x_count * zone->desk_y_count);
   while (dx < 0)
     dx += (zone->desk_x_count * zone->desk_y_count);
   e_zone_desk_linear_flip_to(zone, dx);
}

E_API void
e_zone_desk_linear_flip_to(E_Zone *zone,
                           int x)
{
   int y;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   y = x / zone->desk_x_count;
   x = x - (y * zone->desk_x_count);
   e_zone_desk_flip_to(zone, x, y);
}

E_API void
e_zone_edge_enable(void)
{
   const Eina_List *l;
   E_Zone *zone;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (zone->edge.left) evas_object_show(zone->edge.left);
        if (zone->edge.right) evas_object_show(zone->edge.right);
        if (zone->edge.top) evas_object_show(zone->edge.top);
        if (zone->edge.bottom) evas_object_show(zone->edge.bottom);
        if (zone->corner.left_top) evas_object_show(zone->corner.left_top);
        if (zone->corner.top_left) evas_object_show(zone->corner.top_left);
        if (zone->corner.top_right) evas_object_show(zone->corner.top_right);
        if (zone->corner.right_top) evas_object_show(zone->corner.right_top);
        if (zone->corner.right_bottom) evas_object_show(zone->corner.right_bottom);
        if (zone->corner.bottom_right) evas_object_show(zone->corner.bottom_right);
        if (zone->corner.bottom_left) evas_object_show(zone->corner.bottom_left);
        if (zone->corner.left_bottom) evas_object_show(zone->corner.left_bottom);
        e_zone_edge_flip_eval(zone);
     }
}

E_API void
e_zone_edge_disable(void)
{
   const Eina_List *l;
   E_Zone *zone;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        zone->flip.switching = E_ZONE_EDGE_NONE;
        if (zone->edge.left) evas_object_hide(zone->edge.left);
        if (zone->edge.right) evas_object_hide(zone->edge.right);
        if (zone->edge.top) evas_object_hide(zone->edge.top);
        if (zone->edge.bottom) evas_object_hide(zone->edge.bottom);
        if (zone->corner.left_top) evas_object_hide(zone->corner.left_top);
        if (zone->corner.top_left) evas_object_hide(zone->corner.top_left);
        if (zone->corner.top_right) evas_object_hide(zone->corner.top_right);
        if (zone->corner.right_top) evas_object_hide(zone->corner.right_top);
        if (zone->corner.right_bottom) evas_object_hide(zone->corner.right_bottom);
        if (zone->corner.bottom_right) evas_object_hide(zone->corner.bottom_right);
        if (zone->corner.bottom_left) evas_object_hide(zone->corner.bottom_left);
        if (zone->corner.left_bottom) evas_object_hide(zone->corner.left_bottom);
     }
}

E_API void
e_zone_edges_desk_flip_capable(E_Zone *zone, Eina_Bool l, Eina_Bool r, Eina_Bool t, Eina_Bool b)
{
#define NEED_FLIP_EDGE(x) \
  (e_bindings_edge_flippable_get(x) || e_bindings_edge_non_flippable_get(x))
#define NEED_EDGE(x) \
  (e_bindings_edge_non_flippable_get(x))
#define CHECK_EDGE(v, ed, obj)                                                                   \
  do {                                                                                           \
       if (v) {                                                                                  \
            if (NEED_FLIP_EDGE(ed)) { if (zone->edge.obj) evas_object_show(zone->edge.obj); } \
            else if (zone->edge.obj)                                                             \
              evas_object_hide(zone->edge.obj);                                               \
         }                                                                                       \
       else {                                                                                    \
            if (NEED_EDGE(ed)) { if (zone->edge.obj) evas_object_show(zone->edge.obj); }      \
            else if (zone->edge.obj)                                                             \
              evas_object_hide(zone->edge.obj);                                               \
         }                                                                                       \
    } while (0)

   CHECK_EDGE(l, E_ZONE_EDGE_LEFT, left);
   CHECK_EDGE(r, E_ZONE_EDGE_RIGHT, right);
   CHECK_EDGE(t, E_ZONE_EDGE_TOP, top);
   CHECK_EDGE(b, E_ZONE_EDGE_BOTTOM, bottom);

#define CHECK_CORNER(v1, v2, ed, obj1, obj2)                                \
  if ((!v1) && (!v2)) {                                                     \
       if (NEED_EDGE(ed)) {                                                 \
            if (zone->corner.obj1) evas_object_show(zone->corner.obj1);  \
            if (zone->corner.obj2) evas_object_show(zone->corner.obj2);  \
         }                                                                  \
       else {                                                               \
            if (zone->corner.obj1) evas_object_hide(zone->corner.obj1);  \
            if (zone->corner.obj2) evas_object_hide(zone->corner.obj2);  \
         }                                                                  \
    }                                                                       \
  else {                                                                    \
       if (NEED_FLIP_EDGE(ed)) {                                            \
            if (zone->corner.obj1) evas_object_show(zone->corner.obj1);  \
            if (zone->corner.obj2) evas_object_show(zone->corner.obj2);  \
         }                                                                  \
       else {                                                               \
            if (zone->corner.obj1) evas_object_hide(zone->corner.obj1);  \
            if (zone->corner.obj2) evas_object_hide(zone->corner.obj2);  \
         }                                                                  \
    }

   CHECK_CORNER(l, t, E_ZONE_EDGE_TOP_LEFT, left_top, top_left);
   CHECK_CORNER(r, t, E_ZONE_EDGE_TOP_RIGHT, right_top, top_right);
   CHECK_CORNER(l, b, E_ZONE_EDGE_BOTTOM_LEFT, left_bottom, bottom_left);
   CHECK_CORNER(r, b, E_ZONE_EDGE_BOTTOM_RIGHT, right_bottom, bottom_right);
}

E_API Eina_Bool
e_zone_exists_direction(E_Zone *zone, E_Zone_Edge edge)
{
   Eina_List *l;
   E_Zone *z2;

   EINA_LIST_FOREACH(e_comp->zones, l, z2)
     {
        if (zone == z2) continue;

        switch (edge)
          {
           case E_ZONE_EDGE_TOP_LEFT:
             if (((E_SPANS_COMMON(0, zone->x + zone->w, z2->x, z2->w)) &&
                  (z2->y < zone->y)) ||
                 ((E_SPANS_COMMON(0, zone->y + zone->h, z2->y, z2->h)) &&
                  (z2->x < zone->x)))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_TOP:
             if ((E_SPANS_COMMON(zone->x, zone->w, z2->x, z2->w)) &&
                 (z2->y < zone->y))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_TOP_RIGHT:
             if (((E_SPANS_COMMON(zone->x, 99999, z2->x, z2->w)) &&
                  (z2->y < zone->y)) ||
                 ((E_SPANS_COMMON(0, zone->y + zone->h, z2->y, z2->h)) &&
                  (z2->x >= (zone->x + zone->w))))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_LEFT:
             if ((E_SPANS_COMMON(zone->y, zone->h, z2->y, z2->h)) &&
                 (z2->x < zone->x))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_RIGHT:
             if ((E_SPANS_COMMON(zone->y, zone->h, z2->y, z2->h)) &&
                 (z2->x >= (zone->x + zone->w)))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_BOTTOM_LEFT:
             if (((E_SPANS_COMMON(0, zone->x + zone->w, z2->x, z2->w)) &&
                  (z2->y >= (zone->y + zone->h))) ||
                 ((E_SPANS_COMMON(zone->y, 99999, z2->y, z2->h)) &&
                  (z2->x < zone->x)))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_BOTTOM:
             if ((E_SPANS_COMMON(zone->x, zone->w, z2->x, z2->w)) &&
                 (z2->y >= (zone->y + zone->h)))
               return EINA_TRUE;
             break;

           case E_ZONE_EDGE_BOTTOM_RIGHT:
             if (((E_SPANS_COMMON(zone->x, 99999, z2->x, z2->w)) &&
                  (z2->y >= (zone->y + zone->h))) ||
                 ((E_SPANS_COMMON(zone->y, 99999, z2->y, z2->h)) &&
                  (z2->x < zone->x)))
               return EINA_TRUE;
             break;

           default:
             break;
          }
     }

   return EINA_FALSE;
}

E_API void
e_zone_edge_flip_eval(E_Zone *zone)
{
   Eina_Bool lf, rf, tf, bf;

   lf = rf = tf = bf = EINA_TRUE;
   if (zone->desk_x_count <= 1) lf = rf = EINA_FALSE;
   else if (!e_config->desk_flip_wrap)
     {
        if (zone->desk_x_current == 0) lf = EINA_FALSE;
        if (zone->desk_x_current == (zone->desk_x_count - 1)) rf = EINA_FALSE;
     }
   if (zone->desk_y_count <= 1) tf = bf = EINA_FALSE;
   else if (!e_config->desk_flip_wrap)
     {
        if (zone->desk_y_current == 0) tf = EINA_FALSE;
        if (zone->desk_y_current == (zone->desk_y_count - 1)) bf = EINA_FALSE;
     }
   e_zone_edges_desk_flip_capable(zone, lf, rf, tf, bf);
}

E_API void
e_zone_edge_new(E_Zone_Edge edge)
{
   const Eina_List *l;
   E_Zone *zone;
   int cw, ch;

   if (edge == E_ZONE_EDGE_NONE) return;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        // don't allow bindings on edges that are on the boundary
        // between zones
        if (e_zone_exists_direction(zone, edge)) continue;
        cw = zone->w * E_ZONE_CORNER_RATIO;
        ch = zone->h * E_ZONE_CORNER_RATIO;
        switch (edge)
          {
#define EDGE_NEW(MEMBER, X, Y, W, H) do { \
             if (!zone->MEMBER) \
               { \
                  zone->MEMBER = evas_object_rectangle_add(e_comp->evas); \
                  evas_object_name_set(zone->MEMBER, #MEMBER); \
                  evas_object_move(zone->MEMBER, (X), (Y)); \
                  evas_object_resize(zone->MEMBER, (W), (H)); \
                  evas_object_repeat_events_set(zone->MEMBER, 1); \
                  evas_object_data_set(zone->MEMBER, "comp_repeat", (void*)1); \
                  evas_object_color_set(zone->MEMBER, 0, 0, 0, 0); \
                  evas_object_event_callback_add(zone->MEMBER, EVAS_CALLBACK_MOUSE_MOVE, _e_zone_cb_mouse_move, zone); \
                  evas_object_event_callback_add(zone->MEMBER, EVAS_CALLBACK_MOUSE_IN, _e_zone_cb_mouse_in, zone); \
                  evas_object_event_callback_add(zone->MEMBER, EVAS_CALLBACK_MOUSE_OUT, _e_zone_cb_mouse_out, zone); \
                  evas_object_event_callback_add(zone->MEMBER, EVAS_CALLBACK_MOUSE_DOWN, _e_zone_cb_mouse_down, zone); \
                  evas_object_event_callback_add(zone->MEMBER, EVAS_CALLBACK_MOUSE_UP, _e_zone_cb_mouse_up, zone); \
                  evas_object_show(zone->MEMBER); \
               } \
            } while (0)

           case E_ZONE_EDGE_LEFT:
             EDGE_NEW(edge.left, zone->x, zone->y + ch, 1, zone->h - 2 * ch);
             break;
           case E_ZONE_EDGE_RIGHT:
             EDGE_NEW(edge.right, zone->x + zone->w - 1, zone->y + ch, 1, zone->h - 2 * ch);
             break;
           case E_ZONE_EDGE_TOP:
             EDGE_NEW(edge.top, zone->x + 1 + cw, zone->y, zone->w - 2 * cw - 2, 1);
             break;
           case E_ZONE_EDGE_BOTTOM:
             EDGE_NEW(edge.bottom, zone->x + 1 + cw, zone->y + zone->h - 1, zone->w - 2 - 2 * cw, 1);
             break;
           case E_ZONE_EDGE_TOP_LEFT:
             EDGE_NEW(corner.left_top, zone->x, zone->y, 1, ch);
             EDGE_NEW(corner.top_left, zone->x + 1, zone->y, cw, 1);
             break;
           case E_ZONE_EDGE_TOP_RIGHT:
             EDGE_NEW(corner.top_right, zone->x + zone->w - cw - 2, zone->y, cw, 1);
             EDGE_NEW(corner.right_top, zone->x + zone->w - 1, zone->y, 1, ch);
             break;
           case E_ZONE_EDGE_BOTTOM_RIGHT:
             EDGE_NEW(corner.right_bottom, zone->x + zone->w - 1, zone->y + zone->h - ch, 1, ch);
             EDGE_NEW(corner.bottom_right, zone->x + zone->w - cw - 2, zone->y + zone->h - 1, cw, 1);
             break;
           case E_ZONE_EDGE_BOTTOM_LEFT:
             EDGE_NEW(corner.bottom_left, zone->x + 1, zone->y + zone->h - 1, cw, 1);
             EDGE_NEW(corner.left_bottom, zone->x, zone->y + zone->h - ch, 1, ch);
             break;
           default: continue;
          }
        if (e_config->fullscreen_flip)
          e_zone_edge_win_layer_set(zone, E_LAYER_CLIENT_EDGE_FULLSCREEN);
        else
          e_zone_edge_win_layer_set(zone, E_LAYER_CLIENT_EDGE);
     }
}

E_API void
e_zone_edge_free(E_Zone_Edge edge)
{
   const Eina_List *l;
   E_Zone *zone;

   if (edge == E_ZONE_EDGE_NONE) return;
   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (zone->flip.switching == edge)
          zone->flip.switching = E_ZONE_EDGE_NONE;
        switch (edge)
          {
           case E_ZONE_EDGE_NONE:
             /* noop */
             break;

           case E_ZONE_EDGE_LEFT:
             E_FREE_FUNC(zone->edge.left, evas_object_del);
             break;

           case E_ZONE_EDGE_RIGHT:
             E_FREE_FUNC(zone->edge.right, evas_object_del);
             break;

           case E_ZONE_EDGE_TOP:
             E_FREE_FUNC(zone->edge.top, evas_object_del);
             break;

           case E_ZONE_EDGE_BOTTOM:
             E_FREE_FUNC(zone->edge.bottom, evas_object_del);
             break;

           case E_ZONE_EDGE_TOP_LEFT:
             E_FREE_FUNC(zone->corner.left_top, evas_object_del);
             E_FREE_FUNC(zone->corner.top_left, evas_object_del);
             break;

           case E_ZONE_EDGE_TOP_RIGHT:
             E_FREE_FUNC(zone->corner.top_right, evas_object_del);
             E_FREE_FUNC(zone->corner.right_top, evas_object_del);
             break;

           case E_ZONE_EDGE_BOTTOM_RIGHT:
             E_FREE_FUNC(zone->corner.right_bottom, evas_object_del);
             E_FREE_FUNC(zone->corner.bottom_right, evas_object_del);
             break;

           case E_ZONE_EDGE_BOTTOM_LEFT:
             E_FREE_FUNC(zone->corner.bottom_left, evas_object_del);
             E_FREE_FUNC(zone->corner.left_bottom, evas_object_del);
             break;
          }
     }
}

E_API void
e_zone_edge_win_layer_set(E_Zone *zone, E_Layer layer)
{
#define EDGE_STACK(EDGE) do { \
   if (zone->EDGE) \
     { \
       evas_object_layer_set(zone->EDGE, layer); \
       evas_object_stack_below(zone->EDGE, e_comp->layers[e_comp_canvas_layer_map(layer)].obj); \
     } \
   } while (0)

   EDGE_STACK(corner.left_bottom);
   EDGE_STACK(corner.left_top);
   EDGE_STACK(corner.top_left);
   EDGE_STACK(corner.top_right);
   EDGE_STACK(corner.right_top);
   EDGE_STACK(corner.right_bottom);
   EDGE_STACK(corner.bottom_right);
   EDGE_STACK(corner.bottom_left);

   EDGE_STACK(edge.left);
   EDGE_STACK(edge.right);
   EDGE_STACK(edge.top);
   EDGE_STACK(edge.bottom);
}

E_API void
e_zone_fade_handle(E_Zone *zone, int out, double tim)
{
   EINA_SAFETY_ON_NULL_RETURN(zone);
   if (out == 1)
     {
        if ((e_backlight_exists()) && (!e_comp_config_get()->nofade))
          {
             e_backlight_update();
             zone->bloff = EINA_TRUE;
             zone->bl = e_backlight_level_get(zone);
             e_backlight_level_set(zone, 0.0, tim);
          }
     }
   else
     {
        if ((e_backlight_exists()) && (!e_comp_config_get()->nofade))
          {
             zone->bloff = EINA_FALSE;
             e_backlight_update();
             if (e_backlight_mode_get(zone) != E_BACKLIGHT_MODE_NORMAL)
               e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
             else
               e_backlight_level_set(zone, e_config->backlight.normal, tim);
          }
     }
}

static void
_e_zone_useful_geometry_calc(const E_Zone *zone, int dx, int dy, int *x, int *y, int *w, int *h)
{
   E_Desk *desk;
   E_Zone_Obstacle *obs;
   Eina_Tiler *tiler;
   int zx, zy, zw, zh;
   Eina_Iterator *it;
   Eina_Rectangle geom = { 0 } , *rect;
   int size = 0;

   zx = zone->x;
   zy = zone->y;
   zw = zone->w;
   zh = zone->h;
   tiler = eina_tiler_new(zw, zh);
   eina_tiler_tile_size_set(tiler, 1, 1);
   eina_tiler_rect_add(tiler, &(Eina_Rectangle){0, 0, zw, zh});
   EINA_INLIST_FOREACH(zone->obstacles, obs)
     {
        if (!E_INTERSECTS(obs->x, obs->y, obs->w, obs->h, zx, zy, zw, zh)) continue;
        if (obs->vertical)
          eina_tiler_rect_del(tiler, &(Eina_Rectangle){obs->x - zx, 0, obs->w, zh});
        else
          eina_tiler_rect_del(tiler, &(Eina_Rectangle){0, obs->y - zy, zw, obs->h});
     }
   desk = e_desk_at_xy_get(zone, dx, dy);
   if (desk)
     {
        EINA_INLIST_FOREACH(desk->obstacles, obs)
          {
             if (!E_INTERSECTS(obs->x, obs->y, obs->w, obs->h, zx, zy, zw, zh)) continue;
             if (obs->vertical)
               eina_tiler_rect_del(tiler, &(Eina_Rectangle){obs->x - zx, 0, obs->w, zh});
             else
               eina_tiler_rect_del(tiler, &(Eina_Rectangle){0, obs->y - zy, zw, obs->h});
          }
     }
   it = eina_tiler_iterator_new(tiler);
   EINA_ITERATOR_FOREACH(it, rect)
     {
        if (rect->w * rect->h < size) continue;
        size = rect->w * rect->h;
        geom = *rect;
     }
   eina_iterator_free(it);
   eina_tiler_free(tiler);

   if (x) *x = geom.x + zx;
   if (y) *y = geom.y + zy;
   if (w) *w = geom.w;
   if (h) *h = geom.h;
}

/**
 * Get (or calculate) the useful (or free, without any shelves) area.
 */
E_API Eina_Bool
e_zone_useful_geometry_get(E_Zone *zone,
                           int *x,
                           int *y,
                           int *w,
                           int *h)
{
   int zx, zy, zw, zh;

   E_OBJECT_CHECK_RETURN(zone, EINA_FALSE);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, EINA_FALSE);

   if (zone->useful_geometry_dirty)
     {
        _e_zone_useful_geometry_calc(zone, zone->desk_x_current, zone->desk_y_current, &zx, &zy, &zw, &zh);
        memcpy(&zone->useful_geometry[0], &zone->useful_geometry[1], sizeof(Eina_Rectangle));
        zone->useful_geometry[1].x = zx;
        zone->useful_geometry[1].y = zy;
        zone->useful_geometry[1].w = zw;
        zone->useful_geometry[1].h = zh;
        zone->useful_geometry_changed =
          !!memcmp(&zone->useful_geometry[0], &zone->useful_geometry[1], sizeof(Eina_Rectangle));

     }
   zone->useful_geometry_dirty = 0;

   if (x) *x = zone->useful_geometry[1].x;
   if (y) *y = zone->useful_geometry[1].y;
   if (w) *w = zone->useful_geometry[1].w;
   if (h) *h = zone->useful_geometry[1].h;
   return zone->useful_geometry_changed;
}

E_API void
e_zone_desk_useful_geometry_get(const E_Zone *zone, const E_Desk *desk, int *x, int *y, int *w, int *h)
{
   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   E_OBJECT_CHECK(desk);
   E_OBJECT_TYPE_CHECK(desk, E_DESK_TYPE);

   if (desk->zone != zone) CRI("zone/desk mismatch!");

   _e_zone_useful_geometry_calc(zone, desk->x, desk->y, x, y, w, h);
}

/**
 * Mark as dirty so e_zone_useful_geometry_get() will need to recalculate.
 *
 * Call this function when shelves are added or important properties changed.
 */
E_API void
e_zone_useful_geometry_dirty(E_Zone *zone)
{
   E_Event_Zone_Move_Resize *ev;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);

   /* ignore if pending event already exists */
   if (zone->useful_geometry_dirty) return;

   zone->useful_geometry_dirty = 1;
   if (!e_zone_useful_geometry_get(zone, NULL, NULL, NULL, NULL)) return;
   ev = E_NEW(E_Event_Zone_Move_Resize, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_USEFUL_GEOMETRY_CHANGED, ev, _e_zone_event_generic_free, NULL);

   if (!stopping)
     e_comp_clients_rescale();
}

E_API E_Zone_Obstacle *
e_zone_obstacle_add(E_Zone *zone, E_Desk *desk, Eina_Rectangle *geom, Eina_Bool vertical)
{
   E_Zone_Obstacle *obs;

   E_OBJECT_CHECK_RETURN(zone, NULL);
   E_OBJECT_TYPE_CHECK_RETURN(zone, E_ZONE_TYPE, NULL);
   if (desk)
     {
        E_OBJECT_CHECK_RETURN(desk, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(desk, E_DESK_TYPE, NULL);
        if (desk->zone != zone)
          {
             ERR("zone != desk->zone");
             return NULL;
          }
     }
   obs = E_OBJECT_ALLOC(E_Zone_Obstacle, E_ZONE_OBSTACLE_TYPE, _e_zone_obstacle_free);
   obs->x = geom->x, obs->y = geom->y;
   obs->w = geom->w, obs->h = geom->h;
   obs->owner = E_OBJECT(desk) ?: E_OBJECT(zone);
   obs->vertical = !!vertical;
   if (desk)
     {
        desk->obstacles = eina_inlist_append(desk->obstacles, EINA_INLIST_GET(obs));
        if (desk->visible)
          e_zone_useful_geometry_dirty(desk->zone);
     }
   else
     {
        zone->obstacles = eina_inlist_append(zone->obstacles, EINA_INLIST_GET(obs));
        e_zone_useful_geometry_dirty(zone);
     }
   return obs;
}

E_API void
e_zone_obstacle_modify(E_Zone_Obstacle *obs, Eina_Rectangle *geom, Eina_Bool vertical)
{
   E_Zone *zone;
   E_Desk *desk;

   E_OBJECT_CHECK(obs);
   E_OBJECT_TYPE_CHECK(obs, E_ZONE_OBSTACLE_TYPE);
   EINA_SAFETY_ON_NULL_RETURN(geom);
   if ((obs->x == geom->x) && (obs->y == geom->y) && (obs->w == geom->w) && (obs->h == geom->h))
     return;
   obs->x = geom->x, obs->y = geom->y;
   obs->w = geom->w, obs->h = geom->h;
   obs->vertical = !!vertical;

   if (obs->owner->type == E_DESK_TYPE)
     {
        desk = (void *)obs->owner;
        if (desk->visible)
          e_zone_useful_geometry_dirty(desk->zone);
     }
   else
     {
        zone = (void *)obs->owner;
        e_zone_useful_geometry_dirty(zone);
     }
}

E_API void
e_zone_stow(E_Zone *zone)
{
   E_Event_Zone_Stow *ev;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   if (zone->stowed) return;
   ev = E_NEW(E_Event_Zone_Stow, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_STOW, ev, _e_zone_event_generic_free, NULL);

   zone->stowed = EINA_TRUE;
}

E_API void
e_zone_unstow(E_Zone *zone)
{
   E_Event_Zone_Unstow *ev;

   E_OBJECT_CHECK(zone);
   E_OBJECT_TYPE_CHECK(zone, E_ZONE_TYPE);
   if (!zone->stowed) return;
   ev = E_NEW(E_Event_Zone_Unstow, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_UNSTOW, ev, _e_zone_event_generic_free, NULL);

   zone->stowed = EINA_FALSE;
}

/* local subsystem functions */
static void
_e_zone_free(E_Zone *zone)
{
   int x, y;

   //printf("@@@@@@@@@@ e_zone_free: %i %i | %i %i %ix%i = %p\n", zone->num, zone->id, zone->x, zone->y, zone->w, zone->h, zone);
   /* Delete the edge windows if they exist */
   E_FREE_FUNC(zone->edge.top, evas_object_del);
   E_FREE_FUNC(zone->edge.bottom, evas_object_del);
   E_FREE_FUNC(zone->edge.left, evas_object_del);
   E_FREE_FUNC(zone->edge.right, evas_object_del);
   E_FREE_FUNC(zone->corner.left_bottom, evas_object_del);
   E_FREE_FUNC(zone->corner.left_top, evas_object_del);
   E_FREE_FUNC(zone->corner.top_left, evas_object_del);
   E_FREE_FUNC(zone->corner.top_right, evas_object_del);
   E_FREE_FUNC(zone->corner.right_top, evas_object_del);
   E_FREE_FUNC(zone->corner.right_bottom, evas_object_del);
   E_FREE_FUNC(zone->corner.bottom_right, evas_object_del);
   E_FREE_FUNC(zone->corner.bottom_left, evas_object_del);

   /* Delete the object event callbacks */
   evas_object_event_callback_del(zone->bg_event_object,
                                  EVAS_CALLBACK_MOUSE_DOWN,
                                  _e_zone_cb_bg_mouse_down);
   evas_object_event_callback_del(zone->bg_event_object,
                                  EVAS_CALLBACK_MOUSE_UP,
                                  _e_zone_cb_bg_mouse_up);

   E_FREE_FUNC(zone->cur_mouse_action, e_object_unref);

   /* remove handlers */
   E_FREE_LIST(zone->handlers, ecore_event_handler_del);

   if (zone->name) eina_stringshare_del(zone->name);
   e_comp->zones = eina_list_remove(e_comp->zones, zone);
   evas_object_del(zone->bg_event_object);
   evas_object_del(zone->bg_clip_object);
   evas_object_del(zone->bg_object);
   if (zone->prev_bg_object) evas_object_del(zone->prev_bg_object);
   if (zone->transition_object) evas_object_del(zone->transition_object);

   evas_object_del(zone->base);
   evas_object_del(zone->over);
   if ((!stopping) && (!e_comp_config_get()->nofade))
     {
        if (zone->bloff)
          {
             if (e_backlight_mode_get(zone) != E_BACKLIGHT_MODE_NORMAL)
               e_backlight_mode_set(zone, E_BACKLIGHT_MODE_NORMAL);
             e_backlight_level_set(zone, e_config->backlight.normal, 0.0);
          }
     }

   /* free desks */
   while (zone->obstacles)
     {
        E_Object *obs = (void *)EINA_INLIST_CONTAINER_GET(zone->obstacles, E_Zone_Obstacle);
        e_object_del(obs);
     }
   for (x = 0; x < zone->desk_x_count; x++)
     {
        for (y = 0; y < zone->desk_y_count; y++)
          {
             e_object_del(E_OBJECT(zone->desks[x + (y * zone->desk_x_count)]));
             zone->desks[x + (y * zone->desk_x_count)] = NULL;
          }
     }
   free(zone->desks);
   free(zone->randr2_id);
   free(zone);
}

static void
_e_zone_cb_bg_mouse_down(void *data,
                         Evas *evas       EINA_UNUSED,
                         Evas_Object *obj EINA_UNUSED,
                         void *event_info)
{
   E_Zone *zone = data;
   Evas_Event_Mouse_Down *ev = event_info;

   if (e_comp_util_mouse_grabbed()) return;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;

   if (!zone->cur_mouse_action)
     {
        zone->cur_mouse_action =
          e_bindings_mouse_down_evas_event_handle(E_BINDING_CONTEXT_ZONE,
                                             E_OBJECT(zone), event_info);
        if (zone->cur_mouse_action)
          {
             if ((!zone->cur_mouse_action->func.end_mouse) &&
                 (!zone->cur_mouse_action->func.end))
               zone->cur_mouse_action = NULL;
             if (zone->cur_mouse_action)
               e_object_ref(E_OBJECT(zone->cur_mouse_action));
          }
     }
}

static void
_e_zone_cb_bg_mouse_up(void *data,
                       Evas *evas       EINA_UNUSED,
                       Evas_Object *obj EINA_UNUSED,
                       void *event_info)
{
   E_Zone *zone = data;
   Evas_Event_Mouse_Up *ev = event_info;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (zone->cur_mouse_action)
     {
        E_Binding_Event_Mouse_Button event;

        e_bindings_evas_event_mouse_button_convert(event_info, &event);
        if (zone->cur_mouse_action->func.end_mouse)
          zone->cur_mouse_action->func.end_mouse(E_OBJECT(zone), "", &event);
        else if (zone->cur_mouse_action->func.end)
          zone->cur_mouse_action->func.end(E_OBJECT(zone), "");

        e_object_unref(E_OBJECT(zone->cur_mouse_action));
        zone->cur_mouse_action = NULL;
     }
   else
     {
        E_Binding_Event_Mouse_Button event;

        e_bindings_ecore_event_mouse_button_convert(event_info, &event);
        e_bindings_mouse_up_event_handle(E_BINDING_CONTEXT_ZONE,
                                         E_OBJECT(zone), &event);
     }
}

static Eina_Bool
_e_zone_cb_edge_timer(void *data)
{
   E_Zone *zone;
   E_Action *act;

   zone = data;
   act = e_action_find(zone->flip.bind->action);
   if (!act)
     {
        E_FREE(zone->flip.ev);
        return ECORE_CALLBACK_CANCEL;
     }

   if (act->func.go_edge)
     act->func.go_edge(E_OBJECT(zone), zone->flip.bind->params, zone->flip.ev);
   else if (act->func.go)
     act->func.go(E_OBJECT(zone), zone->flip.bind->params);

   zone->flip.bind->timer = NULL;

   E_FREE(zone->flip.ev);
   return ECORE_CALLBACK_CANCEL;
}

static void
_e_zone_event_generic_free(void *data EINA_UNUSED, void *ev)
{
   struct _E_Event_Zone_Generic *e;
   // also handes E_Event_Zone_Add, E_Event_Zone_Del, E_Event_Zone_Stow,
   // E_Event_Zone_Unstow, E_Event_Zone_Desk_Count_Set due to them all
   // having the same content

   e = ev;
   e_object_unref(E_OBJECT(e->zone));
   free(e);
}

static void
_e_zone_object_del_attach(void *o)
{
   E_Zone *zone;
   E_Event_Zone_Del *ev;

   zone = o;
   if (stopping) return;
   ev = E_NEW(E_Event_Zone_Del, 1);
   ev->zone = zone;
   e_object_ref(E_OBJECT(ev->zone));
   ecore_event_add(E_EVENT_ZONE_DEL, ev, _e_zone_event_generic_free, NULL);
}

static E_Zone_Edge
_e_zone_detect_edge(E_Zone *zone, Evas_Object *obj)
{
   E_Zone_Edge edge = E_ZONE_EDGE_NONE;

   if (obj == zone->edge.left)
     edge = E_ZONE_EDGE_LEFT;
   else if (obj == zone->edge.top)
     edge = E_ZONE_EDGE_TOP;
   else if (obj == zone->edge.right)
     edge = E_ZONE_EDGE_RIGHT;
   else if (obj == zone->edge.bottom)
     edge = E_ZONE_EDGE_BOTTOM;
   else if ((obj == zone->corner.left_top) ||
            (obj == zone->corner.top_left))
     edge = E_ZONE_EDGE_TOP_LEFT;
   else if ((obj == zone->corner.right_top) ||
            (obj == zone->corner.top_right))
     edge = E_ZONE_EDGE_TOP_RIGHT;
   else if ((obj == zone->corner.right_bottom) ||
            (obj == zone->corner.bottom_right))
     edge = E_ZONE_EDGE_BOTTOM_RIGHT;
   else if ((obj == zone->corner.left_bottom) ||
            (obj == zone->corner.bottom_left))
     edge = E_ZONE_EDGE_BOTTOM_LEFT;
   return edge;
}

static void
_e_zone_edge_move_resize(E_Zone *zone)
{
   int cw;
   int ch;

   cw = zone->w * E_ZONE_CORNER_RATIO;
   ch = zone->h * E_ZONE_CORNER_RATIO;

   evas_object_geometry_set(zone->corner.left_bottom,
             zone->x, zone->y + zone->h - ch, 1, ch);
   evas_object_geometry_set(zone->edge.left,
             zone->x, zone->y + ch, 1, zone->h - 2 * ch);
   evas_object_geometry_set(zone->corner.left_top,
             zone->x, zone->y, 1, ch);

   evas_object_geometry_set(zone->corner.top_left,
             zone->x + 1, zone->y, cw, 1);
   evas_object_geometry_set(zone->edge.top,
             zone->x + 1 + cw, zone->y, zone->w - 2 * cw - 2, 1);
   evas_object_geometry_set(zone->corner.top_right,
             zone->x + zone->w - cw - 2, zone->y, cw, 1);

   evas_object_geometry_set(zone->corner.right_top,
             zone->x + zone->w - 1, zone->y, 1, ch);
   evas_object_geometry_set(zone->edge.right,
             zone->x + zone->w - 1, zone->y + ch, 1, zone->h - 2 * ch);
   evas_object_geometry_set(zone->corner.right_bottom,
             zone->x + zone->w - 1, zone->y + zone->h - ch, 1, ch);

   evas_object_geometry_set(zone->corner.bottom_right,
             zone->x + 1, zone->y + zone->h - 1, cw, 1);
   evas_object_geometry_set(zone->edge.bottom,
             zone->x + 1 + cw, zone->y + zone->h - 1, zone->w - 2 - 2 * cw, 1);
   evas_object_geometry_set(zone->corner.bottom_left,
             zone->x + zone->w - cw - 2, zone->y + zone->h - 1, cw, 1);
}

static void
_e_zone_obstacle_free(E_Zone_Obstacle *obs)
{
   E_Zone *zone;
   E_Desk *desk;

   if (obs->owner->type == E_DESK_TYPE)
     {
        desk = (void *)obs->owner;
        desk->obstacles = eina_inlist_remove(desk->obstacles, EINA_INLIST_GET(obs));
        if (desk->visible)
          e_zone_useful_geometry_dirty(desk->zone);
     }
   else
     {
        zone = (void *)obs->owner;
        zone->obstacles = eina_inlist_remove(zone->obstacles, EINA_INLIST_GET(obs));
        e_zone_useful_geometry_dirty(zone);
     }
   free(obs);
}

E_API E_Zone *
e_zone_for_id_get(const char *id)
{
   Eina_List *l = NULL;
   E_Zone *zone;

   if (!e_comp) return NULL;

   EINA_LIST_FOREACH(e_comp->zones, l, zone)
     {
        if (eina_streq(zone->randr2_id, id))
          return zone;
     }

   return NULL;
}
#include "e.h"

#define SMART_NAME     "e_zoomap"
#define API_ENTRY      E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if ((!obj) || (!sd) || (evas_object_type_get(obj) && strcmp(evas_object_type_get(obj), SMART_NAME)))
#define INTERNAL_ENTRY E_Smart_Data * sd; sd = evas_object_smart_data_get(obj); if (!sd) return;
typedef struct _E_Smart_Data E_Smart_Data;

struct _E_Smart_Data
{
   Evas_Object *smart_obj, *child_obj;
   Evas_Coord   x, y, w, h;
   Evas_Coord   child_w, child_h;
   unsigned int recurse;
   Eina_Bool    solid E_BITFIELD;
   Eina_Bool    smooth E_BITFIELD;
   Eina_Bool    always E_BITFIELD;
};

/* local subsystem functions */
static void _e_zoomap_smart_child_del_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _e_zoomap_smart_child_resize_hook(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _e_zoomap_smart_reconfigure(E_Smart_Data *sd);
static void _e_zoomap_smart_add(Evas_Object *obj);
static void _e_zoomap_smart_del(Evas_Object *obj);
static void _e_zoomap_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y);
static void _e_zoomap_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h);
static void _e_zoomap_smart_show(Evas_Object *obj);
static void _e_zoomap_smart_hide(Evas_Object *obj);
static void _e_zoomap_smart_color_set(Evas_Object *obj, int r, int g, int b, int a);
static void _e_zoomap_smart_clip_set(Evas_Object *obj, Evas_Object *clip);
static void _e_zoomap_smart_clip_unset(Evas_Object *obj);
static void _e_zoomap_smart_init(void);

/* local subsystem globals */
static Evas_Smart *_e_smart = NULL;

/* externally accessible functions */
E_API Evas_Object *
e_zoomap_add(Evas *evas)
{
   _e_zoomap_smart_init();
   return evas_object_smart_add(evas, _e_smart);
}

E_API void
e_zoomap_child_set(Evas_Object *obj, Evas_Object *child)
{
   API_ENTRY return;
   if (child == sd->child_obj) return;
   if (sd->child_obj)
     {
        evas_object_map_set(sd->child_obj, NULL);
        evas_object_map_enable_set(sd->child_obj, EINA_FALSE);
        evas_object_clip_unset(sd->child_obj);
        evas_object_smart_member_del(sd->child_obj);
        evas_object_event_callback_del(sd->child_obj, EVAS_CALLBACK_DEL,
                                       _e_zoomap_smart_child_del_hook);
        evas_object_event_callback_del(sd->child_obj, EVAS_CALLBACK_RESIZE,
                                       _e_zoomap_smart_child_resize_hook);
        sd->child_obj = NULL;
     }
   if (child)
     {
        int r, g, b, a;

        sd->child_obj = child;
        evas_object_smart_member_add(sd->child_obj, sd->smart_obj);
        evas_object_geometry_get(sd->child_obj, NULL, NULL,
                                 &sd->child_w, &sd->child_h);
        evas_object_event_callback_add(child, EVAS_CALLBACK_DEL,
                                       _e_zoomap_smart_child_del_hook, sd);
        evas_object_event_callback_add(child, EVAS_CALLBACK_RESIZE,
                                       _e_zoomap_smart_child_resize_hook, sd);
        if (evas_object_visible_get(obj)) evas_object_show(sd->child_obj);
        else evas_object_hide(sd->child_obj);
        evas_object_color_get(sd->smart_obj, &r, &g, &b, &a);
        evas_object_color_set(sd->child_obj, r, g, b, a);
        evas_object_clip_set(sd->child_obj, evas_object_clip_get(sd->smart_obj));
        _e_zoomap_smart_reconfigure(sd);
     }
}

E_API void
e_zoomap_child_resize(Evas_Object *obj, int w, int h)
{
   API_ENTRY return;
   evas_object_resize(sd->child_obj, w, h);
}

E_API Evas_Object *
e_zoomap_child_get(Evas_Object *obj)
{
   API_ENTRY return NULL;
   return sd->child_obj;
}

E_API void
e_zoomap_smooth_set(Evas_Object *obj, Eina_Bool smooth)
{
   API_ENTRY return;
   smooth = !!smooth;
   if (sd->smooth == smooth) return;
   sd->smooth = smooth;
   _e_zoomap_smart_reconfigure(sd);
}

E_API Eina_Bool
e_zoomap_smooth_get(Evas_Object *obj)
{
   API_ENTRY return EINA_FALSE;
   return sd->smooth;
}

E_API void
e_zoomap_solid_set(Evas_Object *obj, Eina_Bool solid)
{
   API_ENTRY return;
   solid = !!solid;
   if (sd->solid == solid) return;
   sd->solid = solid;
   _e_zoomap_smart_reconfigure(sd);
}

E_API Eina_Bool
e_zoomap_solid_get(Evas_Object *obj)
{
   API_ENTRY return EINA_FALSE;
   return sd->solid;
}

E_API void
e_zoomap_always_set(Evas_Object *obj, Eina_Bool always)
{
   API_ENTRY return;
   always = !!always;
   if (sd->always == always) return;
   sd->always = always;
   _e_zoomap_smart_reconfigure(sd);
}

E_API Eina_Bool
e_zoomap_always_get(Evas_Object *obj)
{
   API_ENTRY return EINA_FALSE;
   return sd->always;
}

E_API void
e_zoomap_child_edje_solid_setup(Evas_Object *obj)
{
   const char *s;
   Eina_Bool solid;

   API_ENTRY return;
   if (!sd->child_obj) return;
   s = edje_object_data_get(sd->child_obj, "argb");
   if (!s) s = edje_object_data_get(sd->child_obj, "shaped");
   solid = (!s) || (s[0] != '1');
   if (sd->solid == solid) return;
   sd->solid = solid;
   _e_zoomap_smart_reconfigure(sd);
}

/* local subsystem functions */
static void
_e_zoomap_smart_child_del_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;

   sd = data;
   sd->child_obj = NULL;
}

static void
_e_zoomap_smart_child_resize_hook(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   E_Smart_Data *sd;
   Evas_Coord w, h;

   sd = data;
   if (!sd->child_obj) return;
   evas_object_geometry_get(sd->child_obj, NULL, NULL, &w, &h);
   if ((w != sd->child_w) || (h != sd->child_h))
     {
        sd->child_w = w;
        sd->child_h = h;
        _e_zoomap_smart_reconfigure(sd);
     }
}

static void
_e_zoomap_smart_reconfigure(E_Smart_Data *sd)
{
   if (!sd->child_obj) return;
   sd->recurse++;
   if ((!sd->always) &&
       ((sd->w == sd->child_w) && (sd->h == sd->child_h)))
     {
        evas_object_map_set(sd->child_obj, NULL);
        evas_object_map_enable_set(sd->child_obj, EINA_FALSE);
        evas_object_move(sd->child_obj, sd->x, sd->y);
        evas_object_resize(sd->child_obj, sd->w, sd->h);
     }
   else
     {
        Evas_Map *m;
        Evas *e = evas_object_evas_get(sd->child_obj);
        Evas_Coord cx = 0, cy = 0;
        int r = 0, g = 0, b = 0, a = 0;

        evas_object_geometry_get(sd->child_obj, &cx, &cy, NULL, NULL);
        if (sd->recurse != 1)
          {
             /* recursion: do move and exit to set map in outer call */
             evas_object_move(sd->child_obj, sd->x, sd->y);
             sd->recurse--;
             return;
          }
        if ((cx != sd->x) || (cy != sd->y))
          {
             evas_smart_objects_calculate(e);
             evas_nochange_push(e);
             evas_object_move(sd->child_obj, sd->x, sd->y);
             evas_smart_objects_calculate(e);
             evas_nochange_pop(e);
          }
        evas_object_color_get(sd->child_obj, &r, &g, &b, &a);
        m = evas_map_new(4);
        evas_map_util_points_populate_from_geometry(m, sd->x, sd->y,
                                                    sd->w, sd->h, 0);
        evas_map_point_image_uv_set(m, 0, 0,           0);
        evas_map_point_image_uv_set(m, 1, sd->child_w, 0);
        evas_map_point_image_uv_set(m, 2, sd->child_w, sd->child_h);
        evas_map_point_image_uv_set(m, 3, 0,           sd->child_h);
        evas_map_smooth_set(m, sd->smooth);
        evas_map_point_color_set(m, 0, r, g, b, a);
        evas_map_point_color_set(m, 1, r, g, b, a);
        evas_map_point_color_set(m, 2, r, g, b, a);
        evas_map_point_color_set(m, 3, r, g, b, a);
        if (a >= 255) evas_map_alpha_set(m, !sd->solid);
        else evas_map_alpha_set(m, EINA_TRUE);
        evas_object_map_set(sd->child_obj, m);
        evas_object_map_enable_set(sd->child_obj, EINA_TRUE);
        evas_map_free(m);
     }
   sd->recurse--;
}

static void
_e_zoomap_smart_add(Evas_Object *obj)
{
   E_Smart_Data *sd;

   sd = E_NEW(E_Smart_Data, 1);
   if (!sd) return;
   sd->smart_obj = obj;
   sd->x = sd->y = sd->w = sd->h = 0;
   sd->solid = EINA_FALSE;
   sd->always = EINA_FALSE;
   sd->smooth = EINA_TRUE;
   evas_object_smart_data_set(obj, sd);
}

static void
_e_zoomap_smart_del(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj)
     {
        Evas_Object *o = sd->child_obj;

        sd->child_obj = NULL;
        evas_object_event_callback_del(o, EVAS_CALLBACK_DEL,
                                       _e_zoomap_smart_child_del_hook);
        evas_object_event_callback_del(o, EVAS_CALLBACK_RESIZE,
                                       _e_zoomap_smart_child_resize_hook);
        evas_object_del(o);
     }
   E_FREE(sd);
}

static void
_e_zoomap_smart_move(Evas_Object *obj, Evas_Coord x, Evas_Coord y)
{
   INTERNAL_ENTRY;
   sd->x = x;
   sd->y = y;
   _e_zoomap_smart_reconfigure(sd);
}

static void
_e_zoomap_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   INTERNAL_ENTRY;
   sd->w = w;
   sd->h = h;
   _e_zoomap_smart_reconfigure(sd);
}

static void
_e_zoomap_smart_show(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj) evas_object_show(sd->child_obj);
   if (!evas_object_map_enable_get(sd->child_obj)) _e_zoomap_smart_reconfigure(sd);
}

static void
_e_zoomap_smart_hide(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if ((!sd->always) &&
       (evas_object_map_enable_get(sd->child_obj)))
     {
        evas_object_map_set(sd->child_obj, NULL);
        evas_object_map_enable_set(sd->child_obj, EINA_FALSE);
        evas_object_move(sd->child_obj, sd->x, sd->y);
        evas_object_resize(sd->child_obj, sd->w, sd->h);
     }
   if (sd->child_obj) evas_object_hide(sd->child_obj);
}

static void
_e_zoomap_smart_color_set(Evas_Object *obj, int r, int g, int b, int a)
{
   INTERNAL_ENTRY;
   if (sd->child_obj) evas_object_color_set(sd->child_obj, r, g, b, a);
}

static void
_e_zoomap_smart_clip_set(Evas_Object *obj, Evas_Object *clip)
{
   INTERNAL_ENTRY;
   if (sd->child_obj) evas_object_clip_set(sd->child_obj, clip);
}

static void
_e_zoomap_smart_clip_unset(Evas_Object *obj)
{
   INTERNAL_ENTRY;
   if (sd->child_obj) evas_object_clip_unset(sd->child_obj);
}

/* never need to touch this */
static void
_e_zoomap_smart_init(void)
{
   static const Evas_Smart_Class sc =
     {
        SMART_NAME, EVAS_SMART_CLASS_VERSION,
        _e_zoomap_smart_add, _e_zoomap_smart_del, _e_zoomap_smart_move, _e_zoomap_smart_resize,
        _e_zoomap_smart_show, _e_zoomap_smart_hide, _e_zoomap_smart_color_set, _e_zoomap_smart_clip_set,
        _e_zoomap_smart_clip_unset, NULL, NULL, NULL, NULL, NULL, NULL, NULL
     };
   if (_e_smart) return;
   _e_smart = evas_smart_class_new(&sc);
}

