#ifdef HAVE_CONFIG_H
# include "elementary_config.h"
#endif

#include <Elementary.h>
#include <Elementary_Cursor.h>

#include "elm_priv.h"

#ifdef HAVE_ELEMENTARY_X
#include <Ecore_X.h>
#include <Ecore_X_Cursor.h>
#endif

#ifdef HAVE_ELEMENTARY_WIN32
#include <Ecore_Win32.h>
#endif

#define _cursor_key "_elm_cursor"

struct _Cursor_Id
{
   const char *name;

#if defined(HAVE_ELEMENTARY_X) || defined(HAVE_ELEMENTARY_COCOA) || defined(HAVE_ELEMENTARY_WIN32)
   int id; /* For X */
   int cid; /* For Cocoa */
#endif
};

#if defined(HAVE_ELEMENTARY_X)
# if defined(HAVE_ELEMENTARY_COCOA)
#  define CURSOR(_name, _id, _cid) {_name, ECORE_X_CURSOR_##_id, _cid}
# else
#  define CURSOR(_name, _id, _cid) { _name, ECORE_X_CURSOR_##_id, -1 }
# endif
#elif defined(HAVE_ELEMENTARY_COCOA)
#  define CURSOR(_name, _id, _cid) {_name, -1, _cid}
#elif defined(HAVE_ELEMENTARY_WIN32)
#  define CURSOR(_name, _id, _cid) {_name, ECORE_WIN32_CURSOR_X11_SHAPE_##_id, -1 }
#else
#  define CURSOR(_name, _id, _cid) { _name }
#endif

#if defined(HAVE_ELEMENTARY_X) || defined(HAVE_ELEMENTARY_COCOA) || defined(HAVE_ELEMENTARY_WIN32)
/* Please keep order in sync with Ecore_X_Cursor.h values! */
static struct _Cursor_Id _cursors[] =
{
   CURSOR(ELM_CURSOR_X                  , X                  , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_ARROW              , ARROW              , ECORE_COCOA_CURSOR_ARROW),
   CURSOR(ELM_CURSOR_BASED_ARROW_DOWN   , BASED_ARROW_DOWN   , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BASED_ARROW_UP     , UP                 , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BOAT               , BOAT               , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BOGOSITY           , BOGOSITY           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BOTTOM_LEFT_CORNER , BOTTOM_LEFT_CORNER , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BOTTOM_RIGHT_CORNER, BOTTOM_RIGHT_CORNER, ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_BOTTOM_SIDE        , BOTTOM_SIDE        , ECORE_COCOA_CURSOR_RESIZE_DOWN),
   CURSOR(ELM_CURSOR_BOTTOM_TEE         , BOTTOM_TEE         , ECORE_COCOA_CURSOR_RESIZE_DOWN),
   CURSOR(ELM_CURSOR_BOX_SPIRAL         , BOX_SPIRAL         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_CENTER_PTR         , CENTER_PTR         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_CIRCLE             , CIRCLE             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_CLOCK              , CLOCK              , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_COFFEE_MUG         , COFFEE_MUG         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_CROSS              , CROSS              , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_CROSS_REVERSE      , CROSS_REVERSE      , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_CROSSHAIR          , CROSSHAIR          , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_DIAMOND_CROSS      , DIAMOND_CROSS      , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_DOT                , DOT                , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_DOT_BOX_MASK       , DOT_BOX_MASK       , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_DOUBLE_ARROW       , DOUBLE_ARROW       , ECORE_COCOA_CURSOR_RESIZE_UP_DOWN),
   CURSOR(ELM_CURSOR_DRAFT_LARGE        , DRAFT_LARGE        , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_DRAFT_SMALL        , DRAFT_SMALL        , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_DRAPED_BOX         , DRAPED_BOX         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_EXCHANGE           , EXCHANGE           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_FLEUR              , FLEUR              , ECORE_COCOA_CURSOR_CLOSED_HAND),
   CURSOR(ELM_CURSOR_GOBBLER            , GOBBLER            , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_GUMBY              , GUMBY              , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_HAND1              , HAND1              , ECORE_COCOA_CURSOR_POINTING_HAND),
   CURSOR(ELM_CURSOR_HAND2              , HAND2              , ECORE_COCOA_CURSOR_POINTING_HAND),
   CURSOR(ELM_CURSOR_HEART              , HEART              , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_ICON               , ICON               , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_IRON_CROSS         , IRON_CROSS         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_LEFT_PTR           , LEFT_PTR           , ECORE_COCOA_CURSOR_ARROW),
   CURSOR(ELM_CURSOR_LEFT_SIDE          , LEFT_SIDE          , ECORE_COCOA_CURSOR_RESIZE_LEFT),
   CURSOR(ELM_CURSOR_LEFT_TEE           , LEFT_TEE           , ECORE_COCOA_CURSOR_RESIZE_LEFT),
   CURSOR(ELM_CURSOR_LEFTBUTTON         , LEFTBUTTON         , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_LL_ANGLE           , LL_ANGLE           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_LR_ANGLE           , LR_ANGLE           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_MAN                , MAN                , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_MIDDLEBUTTON       , MIDDLEBUTTON       , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_MOUSE              , MOUSE              , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_PENCIL             , PENCIL             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_PIRATE             , PIRATE             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_PLUS               , PLUS               , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_QUESTION_ARROW     , QUESTION_ARROW     , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_RIGHT_PTR          , RIGHT_PTR          , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_RIGHT_SIDE         , RIGHT_SIDE         , ECORE_COCOA_CURSOR_RESIZE_RIGHT),
   CURSOR(ELM_CURSOR_RIGHT_TEE          , RIGHT_TEE          , ECORE_COCOA_CURSOR_RESIZE_RIGHT),
   CURSOR(ELM_CURSOR_RIGHTBUTTON        , RIGHTBUTTON        , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_RTL_LOGO           , RTL_LOGO           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_SAILBOAT           , SAILBOAT           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_SB_DOWN_ARROW      , SB_DOWN_ARROW      , ECORE_COCOA_CURSOR_RESIZE_DOWN),
   CURSOR(ELM_CURSOR_SB_H_DOUBLE_ARROW  , SB_H_DOUBLE_ARROW  , ECORE_COCOA_CURSOR_RESIZE_LEFT_RIGHT),
   CURSOR(ELM_CURSOR_SB_LEFT_ARROW      , SB_LEFT_ARROW      , ECORE_COCOA_CURSOR_RESIZE_LEFT),
   CURSOR(ELM_CURSOR_SB_RIGHT_ARROW     , SB_RIGHT_ARROW     , ECORE_COCOA_CURSOR_RESIZE_RIGHT),
   CURSOR(ELM_CURSOR_SB_UP_ARROW        , SB_UP_ARROW        , ECORE_COCOA_CURSOR_RESIZE_UP),
   CURSOR(ELM_CURSOR_SB_V_DOUBLE_ARROW  , SB_V_DOUBLE_ARROW  , ECORE_COCOA_CURSOR_RESIZE_UP_DOWN),
   CURSOR(ELM_CURSOR_SHUTTLE            , SHUTTLE            , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_SIZING             , SIZING             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_SPIDER             , SPIDER             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_SPRAYCAN           , SPRAYCAN           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_STAR               , STAR               , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_TARGET             , TARGET             , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_TCROSS             , TCROSS             , ECORE_COCOA_CURSOR_CROSSHAIR),
   CURSOR(ELM_CURSOR_TOP_LEFT_ARROW     , TOP_LEFT_ARROW     , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_TOP_LEFT_CORNER    , TOP_LEFT_CORNER    , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_TOP_RIGHT_CORNER   , TOP_RIGHT_CORNER   , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_TOP_SIDE           , TOP_SIDE           , ECORE_COCOA_CURSOR_RESIZE_UP),
   CURSOR(ELM_CURSOR_TOP_TEE            , TOP_TEE            , ECORE_COCOA_CURSOR_RESIZE_UP),
   CURSOR(ELM_CURSOR_TREK               , TREK               , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_UL_ANGLE           , UL_ANGLE           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_UMBRELLA           , UMBRELLA           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_UR_ANGLE           , UR_ANGLE           , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_WATCH              , WATCH              , ECORE_COCOA_CURSOR_DEFAULT),
   CURSOR(ELM_CURSOR_XTERM              , XTERM              , ECORE_COCOA_CURSOR_IBEAM)
};

static const int _cursors_count = sizeof(_cursors)/sizeof(struct _Cursor_Id);
#endif

#define ELM_CURSOR_GET_OR_RETURN(cur, obj, ...)         \
  Elm_Cursor *cur;                                      \
  do                                                    \
    {                                                   \
       if (!(obj))                                      \
         {                                              \
            CRI("Null pointer: " #obj);            \
            return __VA_ARGS__;                         \
         }                                              \
       cur = evas_object_data_get((obj), _cursor_key);  \
       if (!cur)                                        \
         {                                              \
            ERR("Object does not have cursor: " #obj);  \
            return __VA_ARGS__;                         \
         }                                              \
    }                                                   \
  while (0)

struct _Elm_Cursor
{
   Evas_Object *obj, *hotobj;
   Evas_Object *eventarea, *owner;
   const char *style, *cursor_name;
   int hot_x, hot_y;
   Ecore_Evas *ee;
   Evas *evas;
   Ecore_Job *hotupdate_job;
#ifdef HAVE_ELEMENTARY_X
   struct {
     Ecore_X_Cursor cursor;
     Ecore_X_Window win;
   } x;
#endif
#ifdef HAVE_ELEMENTARY_WL2
   struct {
     Ecore_Wl2_Window *win;
   } wl;
#endif
#ifdef HAVE_ELEMENTARY_WIN32
   struct {
     Ecore_Win32_Cursor *cursor;
     Ecore_Win32_Window *win;
   } win32;
#endif

#ifdef HAVE_ELEMENTARY_COCOA
   struct {
      Ecore_Cocoa_Cursor  cursor;
      Ecore_Cocoa_Window *win;
   } cocoa;
#endif
   struct
   {
      Evas_Object *obj;
      int layer;
      int x, y;
   } prev;

   Eina_Bool visible:1;
   Eina_Bool use_engine:1;
   Eina_Bool theme_search:1;
};

static void
_elm_cursor_obj_hints(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Elm_Cursor *cur = data;
   int x, y;

   evas_object_size_hint_min_get(cur->obj, &x, &y);
   if ((x < 8) || (y < 8))
     {
        x = 8;
        y = 8;
     }
   evas_object_resize(cur->obj, x, y);
}

static void
_elm_cursor_obj_del(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Elm_Cursor *cur = data;

   if (cur)
     {
        evas_object_event_callback_del_full(cur->obj, EVAS_CALLBACK_DEL,
                                            _elm_cursor_obj_del, cur);
        evas_object_event_callback_del_full(cur->obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                            _elm_cursor_obj_hints, cur);
        cur->obj = NULL;
        ELM_SAFE_FREE(cur->hotobj, evas_object_del);
     }
}

static void
_elm_cursor_set_hot_spots(Elm_Cursor *cur)
{
   const char *str;
   Evas_Coord cx, cy, cw, ch, x, y, w, h;
   int prev_hot_x, prev_hot_y;

   if (!cur->visible) return;

   prev_hot_x = cur->hot_x;
   prev_hot_y = cur->hot_y;

   evas_object_geometry_get(cur->obj, &cx, &cy, &cw, &ch);
   evas_object_geometry_get(cur->hotobj, &x, &y, &w, &h);
   cur->hot_x = (x + (w / 2)) - cx;
   cur->hot_y = (y + (h / 2)) - cy;

   str = edje_object_data_get(cur->obj, "hot_x");
   if (str) cur->hot_x = atoi(str);
   str = edje_object_data_get(cur->obj, "hot_y");
   if (str) cur->hot_y = atoi(str);

   if ((cur->visible) &&
       ((prev_hot_x != cur->hot_x) || (prev_hot_y != cur->hot_y)))
     {
        ecore_evas_object_cursor_set(cur->ee, cur->obj,
                                     ELM_OBJECT_LAYER_CURSOR,
                                     cur->hot_x, cur->hot_y);
     }
}

static void
_elm_cursor_set_hot_spots_job(void *data)
{
   Elm_Cursor *cur = data;

   cur->hotupdate_job = NULL;

   _elm_cursor_set_hot_spots(cur);
}

static void
_elm_cursor_hot_change(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Elm_Cursor *cur = data;
   if (cur->hotupdate_job) ecore_job_del(cur->hotupdate_job);
   cur->hotupdate_job = ecore_job_add(_elm_cursor_set_hot_spots_job, data);
}

static Eina_Bool
_elm_cursor_obj_add(Evas_Object *obj, Elm_Cursor *cur)
{
#ifdef HAVE_ELEMENTARY_WL2
   const char *engine_name;

   engine_name = ecore_evas_engine_name_get(cur->ee);
   if ((engine_name) &&
       ((!strcmp(engine_name, ELM_WAYLAND_SHM)) ||
           (!strcmp(engine_name, ELM_WAYLAND_EGL))))
     return EINA_FALSE;
#endif

   cur->obj = edje_object_add(cur->evas);
   if (!cur->obj) return EINA_FALSE;
   edje_object_freeze(cur->obj);
   edje_object_update_hints_set(cur->obj, 1);

   if (elm_widget_theme_object_set(obj, cur->obj, "cursor", cur->cursor_name,
                             cur->style ? cur->style : "default") == EFL_UI_THEME_APPLY_ERROR_GENERIC)
     {
        ELM_SAFE_FREE(cur->obj, evas_object_del);
        return EINA_FALSE;
     }
   evas_object_data_set(cur->obj, "elm-cursor", (void*)1);
   cur->hotobj = evas_object_rectangle_add(cur->evas);
   evas_object_color_set(cur->hotobj, 0, 0, 0, 0);
   evas_object_event_callback_add(cur->obj, EVAS_CALLBACK_MOVE,
                                  _elm_cursor_hot_change, cur);
   evas_object_event_callback_add(cur->obj, EVAS_CALLBACK_RESIZE,
                                  _elm_cursor_hot_change, cur);
   evas_object_event_callback_add(cur->hotobj, EVAS_CALLBACK_MOVE,
                                  _elm_cursor_hot_change, cur);
   evas_object_event_callback_add(cur->hotobj, EVAS_CALLBACK_RESIZE,
                                  _elm_cursor_hot_change, cur);

   if (elm_widget_is_legacy(obj))
     {
        if (edje_object_part_exists(cur->obj, "elm.swallow.hotspot"))
          edje_object_part_swallow(cur->obj, "elm.swallow.hotspot", cur->hotobj);
        else if (edje_object_part_exists(cur->obj, "elm.content.hotspot"))
          edje_object_part_swallow(cur->obj, "elm.content.hotspot", cur->hotobj);
        else
          {
             ELM_SAFE_FREE(cur->hotobj, evas_object_del);
             ELM_SAFE_FREE(cur->obj, evas_object_del);
             return EINA_FALSE;
          }
     }
   else
     {
        if (edje_object_part_exists(cur->obj, "efl.hotspot"))
          edje_object_part_swallow(cur->obj, "efl.hotspot", cur->hotobj);
        else if (edje_object_part_exists(cur->obj, "efl.content.hotspot"))
          edje_object_part_swallow(cur->obj, "efl.content.hotspot", cur->hotobj);
        else
          {
             ELM_SAFE_FREE(cur->hotobj, evas_object_del);
             ELM_SAFE_FREE(cur->obj, evas_object_del);
             return EINA_FALSE;
          }
     }

   evas_object_event_callback_add(cur->obj, EVAS_CALLBACK_DEL,
                                  _elm_cursor_obj_del, cur);
   evas_object_event_callback_add(cur->obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                  _elm_cursor_obj_hints, cur);
   edje_object_thaw(cur->obj);

   return EINA_TRUE;
}

static void
_elm_cursor_set(Elm_Cursor *cur)
{
   evas_event_freeze(cur->evas);
   if (!cur->use_engine)
     {
        if (cur->visible) goto end;

        if (!cur->obj)
          _elm_cursor_obj_add(cur->owner, cur);
        if (cur->obj)
          {
             ecore_evas_cursor_get(cur->ee, &cur->prev.obj, &cur->prev.layer, &cur->prev.x, &cur->prev.y);
             if (cur->prev.obj)
               {
                  if (evas_object_data_get(cur->prev.obj, "elm-cursor"))
                    memset(&cur->prev, 0, sizeof(cur->prev));
                  else
                    ecore_evas_cursor_unset(cur->ee);
               }
             ecore_evas_object_cursor_set(cur->ee, cur->obj,
                                       ELM_OBJECT_LAYER_CURSOR, cur->hot_x,
                                       cur->hot_y);
          }
        cur->visible = !!cur->obj;
     }
   else
     {
        cur->visible = EINA_TRUE;
        if (cur->obj)
          {
             evas_object_del(cur->obj);
             cur->obj = NULL;
             ELM_SAFE_FREE(cur->hotobj, evas_object_del);
          }
#ifdef HAVE_ELEMENTARY_X
        if (cur->x.win)
          ecore_x_window_cursor_set(cur->x.win, cur->x.cursor);
#endif
#ifdef HAVE_ELEMENTARY_WL2
        if (cur->wl.win)
          {
             Evas_Object *top;

             top = elm_widget_top_get(cur->owner);
             if ((top) && (efl_isa(top, EFL_UI_WIN_CLASS)))
               _elm_win_wl_cursor_set(top, cur->cursor_name);
          }
#endif

#ifdef HAVE_ELEMENTARY_COCOA
        if (cur->cocoa.win)
          ecore_cocoa_window_cursor_set(cur->cocoa.win, cur->cocoa.cursor);
#endif

#ifdef HAVE_ELEMENTARY_WIN32
        if (cur->win32.win)
          ecore_win32_window_cursor_set(cur->win32.win, cur->win32.cursor);
#endif
     }
end:
   evas_event_thaw(cur->evas);
}

static void
_elm_cursor_mouse_in(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Elm_Cursor *cur = data;

   Evas_Event_Mouse_In *ev = event_info;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;

   _elm_cursor_set(cur);
}

static void
_elm_cursor_mouse_out(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Evas_Object *sobj_parent;
   Elm_Cursor *pcur = NULL;
   Elm_Cursor *cur = data;

   Evas_Event_Mouse_Out *ev = event_info;
   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;

   if (!cur->visible) return;
   evas_event_freeze(cur->evas);
   cur->visible = EINA_FALSE;

   sobj_parent = evas_object_data_get(cur->eventarea, "elm-parent");
   while (sobj_parent)
     {
        pcur = evas_object_data_get((sobj_parent), _cursor_key);
        if ((pcur) && (pcur->visible)) break;
        sobj_parent = evas_object_data_get(sobj_parent, "elm-parent");
     }

   if (pcur)
     {
        pcur->visible = EINA_FALSE;
        evas_event_thaw(cur->evas);
        _elm_cursor_set(pcur);
        return;
     }

   if (!cur->use_engine)
     {
        if (cur->prev.obj)
          ecore_evas_object_cursor_set(cur->ee, cur->prev.obj, cur->prev.layer,
                                       cur->prev.x, cur->prev.y);
        else
          ecore_evas_object_cursor_set(cur->ee, NULL, ELM_OBJECT_LAYER_CURSOR,
                                       cur->hot_x, cur->hot_y);
        memset(&cur->prev, 0, sizeof(cur->prev));
     }
   else
     {
#ifdef HAVE_ELEMENTARY_X
        if (cur->x.win)
          ecore_x_window_cursor_set(cur->x.win, ECORE_X_CURSOR_X);
#endif
#ifdef HAVE_ELEMENTARY_WL2
        if (cur->wl.win)
          {
             Evas_Object *top;

             top = elm_widget_top_get(cur->owner);
             if ((top) && (efl_isa(top, EFL_UI_WIN_CLASS)))
               _elm_win_wl_cursor_set(top, NULL);
          }
#endif

#ifdef HAVE_ELEMENTARY_COCOA
        if (cur->cocoa.win)
          ecore_cocoa_window_cursor_set(cur->cocoa.win, ECORE_COCOA_CURSOR_DEFAULT);
#endif

#ifdef HAVE_ELEMENTARY_WIN32
        if (cur->win32.win)
          ecore_win32_window_cursor_set(cur->win32.win, ecore_win32_cursor_shaped_new(ECORE_WIN32_CURSOR_SHAPE_ARROW));
#endif
     }
   evas_event_thaw(cur->evas);
}

static void
_elm_cursor_del(void *data EINA_UNUSED, Evas *evas EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   elm_object_cursor_unset(obj);
}

#if defined(HAVE_ELEMENTARY_X) || defined(HAVE_ELEMENTARY_COCOA) || defined(HAVE_ELEMENTARY_WIN32)
static int
_elm_cursor_strcmp(const void *data1, const void *data2)
{
   const struct _Cursor_Id *c1 = data1;
   const struct _Cursor_Id *c2 = data2;
   return strcmp (c1->name, c2->name);
}
#endif

static void
_elm_cursor_cur_set(Elm_Cursor *cur)
{
   if (!cur->theme_search)
     {
        INF("Using only engine cursors");
        cur->use_engine = EINA_TRUE;
     }
   else if (_elm_cursor_obj_add(cur->owner, cur))
     {
        _elm_cursor_set_hot_spots(cur);
        cur->use_engine = EINA_FALSE;
        elm_widget_cursor_add(cur->owner, cur);
     }
   else
     {
        INF("Cursor couldn't be found on theme: %s", cur->cursor_name);
        cur->use_engine = EINA_TRUE;
     }

#ifdef HAVE_ELEMENTARY_DRM
   const char *engine_name;

   engine_name = ecore_evas_engine_name_get(cur->ee);
   if ((engine_name) && (!strcmp(engine_name, ELM_DRM)))
     cur->use_engine = EINA_FALSE;
#endif

   if (cur->use_engine)
     {
        Evas_Object *top;

        top = elm_widget_top_get(cur->owner);
        if ((top) && (efl_isa(top, EFL_UI_WIN_CLASS)))
          {
#ifdef HAVE_ELEMENTARY_X
             cur->x.win = elm_win_xwindow_get(top);
             if (cur->x.win)
               {
                  struct _Cursor_Id *cur_id;

                  cur_id = bsearch(&(cur->cursor_name), _cursors, _cursors_count,
                                   sizeof(struct _Cursor_Id), _elm_cursor_strcmp);

                  if (!cur_id)
                    {
                       INF("X cursor couldn't be found: %s. Using default.",
                           cur->cursor_name);
                       cur->x.cursor = ecore_x_cursor_shape_get(ECORE_X_CURSOR_X);
                    }
                  else
                    cur->x.cursor = ecore_x_cursor_shape_get(cur_id->id);
               }
#endif

#ifdef HAVE_ELEMENTARY_COCOA
             cur->cocoa.win = elm_win_cocoa_window_get(top);
             if (cur->cocoa.win)
               {
                  struct _Cursor_Id *cur_id;

                  cur_id = bsearch(&(cur->cursor_name), _cursors, _cursors_count,
                                   sizeof(struct _Cursor_Id), _elm_cursor_strcmp);
                  if (!cur_id)
                    {
                       INF("Cocoa Cursor couldn't be found: %s. Using default...",
                           cur->cursor_name);
                       cur->cocoa.cursor = ECORE_COCOA_CURSOR_DEFAULT;
                    }
                  else
                    cur->cocoa.cursor = cur_id->cid;
               }
#endif
#ifdef HAVE_ELEMENTARY_WL2
             cur->wl.win = elm_win_wl_window_get(top);
#endif
#ifdef HAVE_ELEMENTARY_WIN32
             cur->win32.win = elm_win_win32_window_get(top);
             if (cur->win32.win)
               {
                  struct _Cursor_Id *cur_id;

                  cur_id = bsearch(&(cur->cursor_name), _cursors, _cursors_count,
                                   sizeof(struct _Cursor_Id), _elm_cursor_strcmp);

                  if (!cur_id)
                    {
                       INF("Win32 X cursor couldn't be found: %s. Using default.",
                           cur->cursor_name);
                       cur->win32.cursor = ecore_win32_cursor_shaped_new(ECORE_WIN32_CURSOR_SHAPE_ARROW);
                    }
                  else
                    cur->win32.cursor = (Ecore_Win32_Cursor *)ecore_win32_cursor_x11_shaped_get(cur_id->id);
               }
#endif
          }
     }

   if (efl_canvas_pointer_inside_get(cur->eventarea, NULL))
     _elm_cursor_set(cur);
}

/**
 * Set the cursor to be shown when mouse is over the object
 *
 * Set the cursor that will be displayed when mouse is over the
 * object. The object can have only one cursor set to it, so if
 * this function is called twice for an object, the previous set
 * will be unset.
 * If using X cursors, a definition of all the valid cursor names
 * is listed on Elementary_Cursors.h. If an invalid name is set
 * the default cursor will be used.
 *
 * This is an internal function that is used by objects with sub-items
 * that want to provide different cursors for each of them. The @a
 * owner object should be an elm_widget and will be used to track
 * theme changes and to feed @a func and @a del_cb. The @a eventarea
 * may be any object and is the one that should be used later on with
 * elm_object_cursor apis, such as elm_object_cursor_unset().
 *
 * @param eventarea the object being attached a cursor.
 * @param owner the elm_widget that owns this object, will be used to
 *        track theme changes and to be used in @a func or @a del_cb.
 * @param cursor the cursor name to be used.
 *
 * @internal
 * @ingroup Elm_Cursors
 */
void
elm_object_sub_cursor_set(Evas_Object *eventarea, Evas_Object *owner, const char *cursor)
{
   Elm_Cursor *cur = NULL;

   cur = evas_object_data_get(eventarea, _cursor_key);
   if (cur)
     elm_object_cursor_unset(eventarea);

   if (!cursor) return;

   cur = ELM_NEW(Elm_Cursor);
   if (!cur) return;

   cur->owner = owner;
   cur->eventarea = eventarea;
   cur->theme_search = !_elm_config->cursor_engine_only;
   cur->visible = EINA_FALSE;
   cur->style = eina_stringshare_add("default");

   cur->cursor_name = eina_stringshare_add(cursor);
   if (!cur->cursor_name)
     ERR("Could not store cursor name %s", cursor);

   cur->evas = evas_object_evas_get(eventarea);
   cur->ee = ecore_evas_ecore_evas_get(cur->evas);

   _elm_cursor_cur_set(cur);

   evas_object_data_set(eventarea, _cursor_key, cur);

   evas_object_event_callback_add(eventarea, EVAS_CALLBACK_MOUSE_IN,
                                  _elm_cursor_mouse_in, cur);
   evas_object_event_callback_add(eventarea, EVAS_CALLBACK_MOUSE_OUT,
                                  _elm_cursor_mouse_out, cur);
   evas_object_event_callback_add(eventarea, EVAS_CALLBACK_DEL,
                                  _elm_cursor_del, cur);
}

EOLIAN Eina_Bool
_efl_ui_widget_cursor_set(Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED,
                                     const char *cursor)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE);
   elm_object_sub_cursor_set(obj, obj, cursor);
   return EINA_TRUE;
}

const char *
elm_object_sub_cursor_get(const Evas_Object *obj)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj, NULL);
   return cur->cursor_name;
}

EOLIAN const char *
_efl_ui_widget_cursor_get(const Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED)
{
   return elm_object_sub_cursor_get(obj);
}

EAPI void
elm_object_cursor_unset(Evas_Object *obj)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj);

   eina_stringshare_del(cur->cursor_name);
   cur->cursor_name = NULL;
   eina_stringshare_del(cur->style);
   cur->style = NULL;

   if (cur->owner)
     elm_widget_cursor_del(cur->owner, cur);

   if (cur->obj)
     {
        evas_object_event_callback_del_full(cur->obj, EVAS_CALLBACK_DEL,
                                            _elm_cursor_obj_del, cur);
        evas_object_event_callback_del_full(cur->obj, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
                                            _elm_cursor_obj_hints, cur);
        ELM_SAFE_FREE(cur->obj, evas_object_del);
     }

   if (cur->visible)
     {
        if (!cur->use_engine)
          ecore_evas_object_cursor_set(cur->ee, NULL, ELM_OBJECT_LAYER_CURSOR,
                                       cur->hot_x, cur->hot_y);
#ifdef HAVE_ELEMENTARY_X
        else if (cur->x.win)
          ecore_x_window_cursor_set(cur->x.win, ECORE_X_CURSOR_X);
#endif
#ifdef HAVE_ELEMENTARY_COCOA
        else if (cur->cocoa.win)
          ecore_cocoa_window_cursor_set(cur->cocoa.win, ECORE_COCOA_CURSOR_DEFAULT);
#endif
#ifdef HAVE_ELEMENTARY_WL2
        else if (cur->wl.win)
          {
             Evas_Object *top;

             top = elm_widget_top_get(cur->owner);
             if ((top) && (efl_isa(top, EFL_UI_WIN_CLASS)))
               _elm_win_wl_cursor_set(top, NULL);
          }
#endif
#ifdef HAVE_ELEMENTARY_WIN32
        else
          ecore_win32_window_cursor_set(cur->win32.win, ecore_win32_cursor_shaped_new(ECORE_WIN32_CURSOR_SHAPE_ARROW));
#endif
     }

   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOUSE_IN,
                                  _elm_cursor_mouse_in, cur);
   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOUSE_OUT,
                                  _elm_cursor_mouse_out, cur);
   evas_object_event_callback_del_full(obj, EVAS_CALLBACK_DEL,
                                       _elm_cursor_del, cur);
   evas_object_data_del(obj, _cursor_key);

   if (cur->hotupdate_job) ecore_job_del(cur->hotupdate_job);
   cur->hotupdate_job = NULL;

   free(cur);
}

Eina_Bool
elm_object_sub_cursor_style_set(Evas_Object *obj, const char *style)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj, EINA_FALSE);

   if (!eina_stringshare_replace(&cur->style, style))
     ERR("Could not set current style=%s", style);

   if (cur->use_engine) return EINA_FALSE;

   if (!cur->obj)
     {
        if (!_elm_cursor_obj_add(cur->owner, cur))
          {
             ERR("Could not create cursor object");
             return EINA_FALSE;
          }
        else
          _elm_cursor_set_hot_spots(cur);
     }
   else
     {
        if (elm_widget_theme_object_set(obj, cur->obj, "cursor", cur->cursor_name,
                                   style) == EFL_UI_THEME_APPLY_ERROR_GENERIC)
          {
             ERR("Could not apply the theme to the cursor style=%s", style);
             return EINA_FALSE;
          }
        else
          _elm_cursor_set_hot_spots(cur);
     }

   return EINA_TRUE;
}

EOLIAN Eina_Bool
_efl_ui_widget_cursor_style_set(Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED,
                                           const char *style)
{
   return elm_object_sub_cursor_style_set(obj, style);
}

const char *
elm_object_sub_cursor_style_get(const Evas_Object *obj)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj, NULL);
   return cur->style ? cur->style : "default";
}

EOLIAN const char *
_efl_ui_widget_cursor_style_get(const Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED)
{
   return elm_object_sub_cursor_style_get(obj);
}

/**
 * Notify cursor should recalculate its theme.
 * @internal
 */
void
elm_cursor_theme(Elm_Cursor *cur)
{
   if ((!cur) || (!cur->obj)) return;
   if (elm_widget_theme_object_set(cur->owner, cur->obj, "cursor",
                              cur->cursor_name, cur->style) == EFL_UI_THEME_APPLY_ERROR_GENERIC)
     ERR("Could not apply the theme to the cursor style=%s", cur->style);
   else
     _elm_cursor_set_hot_spots(cur);
}

Eina_Bool
elm_object_sub_cursor_theme_search_enabled_set(Evas_Object *obj, Eina_Bool theme_search)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj, EINA_FALSE);
   cur->theme_search = theme_search;
   ELM_SAFE_FREE(cur->obj, evas_object_del);
   _elm_cursor_cur_set(cur);
   return EINA_TRUE;
}

EOLIAN Eina_Bool
_efl_ui_widget_cursor_theme_search_enabled_set(Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED,
                                                             Eina_Bool theme_search)
{
   return elm_object_sub_cursor_theme_search_enabled_set(obj, theme_search);
}

Eina_Bool
elm_object_sub_cursor_theme_search_enabled_get(const Evas_Object *obj)
{
   ELM_CURSOR_GET_OR_RETURN(cur, obj, EINA_FALSE);
   return cur->theme_search;
}

EOLIAN Eina_Bool
_efl_ui_widget_cursor_theme_search_enabled_get(const Evas_Object *obj, Elm_Widget_Smart_Data *pd EINA_UNUSED)
{
   return elm_object_sub_cursor_theme_search_enabled_get(obj);
}
