#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "ecore_wl2_private.h"

#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "efl-hints-client-protocol.h"

static Eina_Hash *_server_displays = NULL;
static Eina_Hash *_client_displays = NULL;

static Eina_Bool _cb_connect_data(void *data, Ecore_Fd_Handler *hdl);
static Eina_Bool _ecore_wl2_display_connect(Ecore_Wl2_Display *ewd, Eina_Bool sync);

static void _ecore_wl2_display_sync_add(Ecore_Wl2_Display *ewd);

void
_display_event_free(void *d, void *event)
{
   ecore_wl2_display_disconnect(d);
   free(event);
}

static void
_ecore_wl2_display_event(Ecore_Wl2_Display *ewd, int event)
{
   Ecore_Wl2_Event_Connect *ev;

   ev = calloc(1, sizeof(Ecore_Wl2_Event_Connect));
   EINA_SAFETY_ON_NULL_RETURN(ev);
   ev->display = ewd;
   ewd->refs++;
   ecore_event_add(event, ev, _display_event_free, ewd);
}

static void
_ecore_wl2_display_signal_exit(void)
{
   Ecore_Event_Signal_Exit *ev;

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

   ev->quit = EINA_TRUE;
   ecore_event_add(ECORE_EVENT_SIGNAL_EXIT, ev, NULL, NULL);
}

static void
_dmabuf_cb_format(void *data EINA_UNUSED, struct zwp_linux_dmabuf_v1 *dmabuf EINA_UNUSED, uint32_t format EINA_UNUSED)
{
   /* It would be awfully nice if this actually happened */
};

static const struct zwp_linux_dmabuf_v1_listener _dmabuf_listener =
{
   _dmabuf_cb_format,
   NULL
};

static void
_xdg_shell_cb_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
{
   xdg_wm_base_pong(shell, serial);
   ecore_wl2_display_flush(data);
}

static const struct xdg_wm_base_listener _xdg_shell_listener =
{
   _xdg_shell_cb_ping,
};

static void
_zxdg_shell_cb_ping(void *data, struct zxdg_shell_v6 *shell, uint32_t serial)
{
   zxdg_shell_v6_pong(shell, serial);
   ecore_wl2_display_flush(data);
}

static const struct zxdg_shell_v6_listener _zxdg_shell_listener =
{
   _zxdg_shell_cb_ping,
};

static void
_session_recovery_create_uuid(void *data EINA_UNUSED, struct zwp_e_session_recovery *session_recovery EINA_UNUSED, struct wl_surface *surface, const char *uuid)
{
   Ecore_Wl2_Window *win;

   /* surface may have been destroyed */
   if (!surface) return;
   win = wl_surface_get_user_data(surface);

   eina_stringshare_replace(&win->uuid, uuid);
}

static const struct zwp_e_session_recovery_listener _session_listener =
{
   _session_recovery_create_uuid,
};

static void
_aux_hints_supported_aux_hints(void *data, struct efl_aux_hints *aux_hints EINA_UNUSED, struct wl_surface *surface_resource, struct wl_array *hints, uint32_t num_hints)
{
   Ecore_Wl2_Display *ewd = data;
   struct wl_surface *surface = surface_resource;
   Ecore_Wl2_Window *win = NULL;
   char *p = NULL;
   char **str = NULL;
   const char *hint = NULL;
   unsigned int i = 0;
   Ecore_Wl2_Event_Aux_Hint_Supported *ev;

   if (!surface) return;
   win = _ecore_wl2_display_window_surface_find(ewd, surface_resource);
   if (!win) return;

   p = hints->data;
   str = calloc(num_hints, sizeof(char *));
   if (!str) return;

   while ((const char *)p < ((const char *)hints->data + hints->size))
     {
        str[i] = (char *)eina_stringshare_add(p);
        p += strlen(p) + 1;
        i++;
     }
   for (i = 0; i < num_hints; i++)
     {
        hint = eina_stringshare_add(str[i]);
        win->supported_aux_hints =
               eina_list_append(win->supported_aux_hints, hint);
     }
   if (str)
     {
        for (i = 0; i < num_hints; i++)
          {
             if (str[i])
               {
                  eina_stringshare_del(str[i]);
                  str[i] = NULL;
               }
          }
        free(str);
     }

   if (!(ev = calloc(1, sizeof(Ecore_Wl2_Event_Aux_Hint_Supported)))) return;
   ev->win = win;
   ev->display = ewd;
   ewd->refs++;
   ecore_event_add(ECORE_WL2_EVENT_AUX_HINT_SUPPORTED, ev,
                   _display_event_free, ewd);
}

static void
_aux_hints_allowed_aux_hint(void *data, struct efl_aux_hints *aux_hints  EINA_UNUSED, struct wl_surface *surface_resource, int id)
{
   struct wl_surface *surface = surface_resource;
   Ecore_Wl2_Window *win = NULL;
   Ecore_Wl2_Display *ewd = data;
   Ecore_Wl2_Event_Aux_Hint_Allowed *ev;

   if (!surface) return;
   win = _ecore_wl2_display_window_surface_find(ewd, surface_resource);
   if (!win) return;

   if (!(ev = calloc(1, sizeof(Ecore_Wl2_Event_Aux_Hint_Allowed)))) return;
   ev->win = win;
   ev->id = id;
   ev->display = ewd;
   ewd->refs++;
   ecore_event_add(ECORE_WL2_EVENT_AUX_HINT_ALLOWED, ev,
                   _display_event_free, ewd);
}

 static void
_cb_aux_message_free(void *data EINA_UNUSED, void *event)
{
   Ecore_Wl2_Event_Aux_Message *ev;
   char *str;

   ev = event;
   ecore_wl2_display_disconnect(ev->display);
   eina_stringshare_del(ev->key);
   eina_stringshare_del(ev->val);
   EINA_LIST_FREE(ev->options, str)
     eina_stringshare_del(str);
   free(ev);
}

 static void
_aux_hints_aux_message(void *data, struct efl_aux_hints *aux_hints EINA_UNUSED, struct wl_surface *surface_resource, const char *key, const char *val, struct wl_array *options)
{
   Ecore_Wl2_Window *win = NULL;
   Ecore_Wl2_Event_Aux_Message *ev;
   char *p = NULL, *str = NULL;
   Eina_List *opt_list = NULL;
   Ecore_Wl2_Display *ewd = data;

   if (!surface_resource) return;
   win = _ecore_wl2_display_window_surface_find(ewd, surface_resource);
   if (!win) return;

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

   if ((options) && (options->size))
     {
        p = options->data;
        while ((const char *)p < ((const char *)options->data + options->size))
          {
             str = (char *)eina_stringshare_add(p);
             opt_list = eina_list_append(opt_list, str);
             p += strlen(p) + 1;
          }
     }

   ev->win = win;
   ev->key = eina_stringshare_add(key);
   ev->val = eina_stringshare_add(val);
   ev->options = opt_list;
   ev->display = ewd;
   ewd->refs++;

   ecore_event_add(ECORE_WL2_EVENT_AUX_MESSAGE, ev, _cb_aux_message_free, NULL);
}

static const struct efl_aux_hints_listener _aux_hints_listener =
{
   _aux_hints_supported_aux_hints,
   _aux_hints_allowed_aux_hint,
   _aux_hints_aux_message,
};

static void
_cb_global_event_free(void *data EINA_UNUSED, void *event)
{
   Ecore_Wl2_Event_Global *ev;

   ev = event;
   eina_stringshare_del(ev->interface);
   ecore_wl2_display_disconnect(ev->display);
   free(ev);
}

static void
_cb_global_add(void *data, struct wl_registry *registry, unsigned int id, const char *interface, unsigned int version)
{
   Ecore_Wl2_Display *ewd;
   Ecore_Wl2_Event_Global *ev;

   ewd = data;

   /* test to see if we have already added this global to our hash */
   if (!eina_hash_find(ewd->globals, &id))
     {
        Ecore_Wl2_Global *global;

        /* allocate space for new global */
        global = calloc(1, sizeof(Ecore_Wl2_Global));
        if (!global) return;

        global->id = id;
        global->interface = eina_stringshare_add(interface);
        global->version = version;

        /* add this global to our hash */
        if (!eina_hash_add(ewd->globals, &global->id, global))
          {
             eina_stringshare_del(global->interface);
             free(global);
          }
     }
   else
     goto event;

   if (!strcmp(interface, "wl_compositor"))
     {
        Ecore_Wl2_Window *window;
        ewd->wl.compositor_version = MIN(version, 4);
        ewd->wl.compositor =
          wl_registry_bind(registry, id, &wl_compositor_interface,
                           ewd->wl.compositor_version);
        EINA_INLIST_FOREACH(ewd->windows, window)
          _ecore_wl2_window_surface_create(window);
     }
   else if (!strcmp(interface, "wl_subcompositor"))
     {
        ewd->wl.subcompositor =
          wl_registry_bind(registry, id, &wl_subcompositor_interface, 1);
     }
   else if (!strcmp(interface, "wl_shm"))
     {
        ewd->wl.shm =
          wl_registry_bind(registry, id, &wl_shm_interface, 1);
     }
   else if (!strcmp(interface, "zwp_linux_dmabuf_v1") && (version >= 2))
     {
        ewd->wl.dmabuf =
          wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 2);
        zwp_linux_dmabuf_v1_add_listener(ewd->wl.dmabuf, &_dmabuf_listener, ewd);
        _ecore_wl2_buffer_test(ewd);
        _ecore_wl2_display_sync_add(ewd);
     }
   else if (!strcmp(interface, "wl_data_device_manager"))
     {
        ewd->wl.data_device_manager_version = MIN(version, 3);
        ewd->wl.data_device_manager =
          wl_registry_bind(registry, id, &wl_data_device_manager_interface, ewd->wl.data_device_manager_version);
     }
   else if ((eina_streq(interface, "www")) &&
            (getenv("EFL_WAYLAND_ENABLE_WWW")))
     {
        Ecore_Wl2_Window *window;

        ewd->wl.www = wl_registry_bind(registry, id, &www_interface, 1);
        EINA_INLIST_FOREACH(ewd->windows, window)
          _ecore_wl2_window_www_surface_init(window);
     }
   else if ((!strcmp(interface, "zwp_e_session_recovery")) &&
            (!no_session_recovery))
     {
        ewd->wl.session_recovery =
          wl_registry_bind(registry, id,
                           &zwp_e_session_recovery_interface, 1);
        zwp_e_session_recovery_add_listener(ewd->wl.session_recovery,
                                            &_session_listener, ewd);
     }
   else if (!strcmp(interface, "efl_aux_hints"))
     {
        Ecore_Wl2_Window *window;
        ewd->wl.efl_aux_hints =
          wl_registry_bind(registry, id,
                           &efl_aux_hints_interface, 1);
        efl_aux_hints_add_listener(ewd->wl.efl_aux_hints, &_aux_hints_listener, ewd);
        EINA_INLIST_FOREACH(ewd->windows, window)
          if (window->surface) efl_aux_hints_get_supported_aux_hints(ewd->wl.efl_aux_hints, window->surface);
     }
   else if (!strcmp(interface, "zwp_teamwork"))
     {
        ewd->wl.teamwork =
          wl_registry_bind(registry, id,
                           &zwp_teamwork_interface, EFL_TEAMWORK_VERSION);
     }
   else if (!strcmp(interface, "wl_output"))
     _ecore_wl2_output_add(ewd, id);
   else if (!strcmp(interface, "wl_seat"))
     _ecore_wl2_input_add(ewd, id, version);
   else if (!strcmp(interface, "efl_hints"))
     {
        Ecore_Wl2_Window *window;

        ewd->wl.efl_hints = wl_registry_bind(registry, id, &efl_hints_interface, MIN(version, 2));
        EINA_INLIST_FOREACH(ewd->windows, window)
          {
             if (!window->xdg_surface) continue;
             if (window->aspect.set)
               efl_hints_set_aspect(window->display->wl.efl_hints, window->xdg_surface,
                 window->aspect.w, window->aspect.h, window->aspect.aspect);
             if (window->weight.set)
               efl_hints_set_weight(window->display->wl.efl_hints,
                 window->xdg_surface, window->weight.w, window->weight.h);
          }
     }

event:
   /* allocate space for event structure */
   ev = calloc(1, sizeof(Ecore_Wl2_Event_Global));
   if (!ev) return;

   ev->id = id;
   ev->display = ewd;
   ewd->refs++;
   ev->version = version;
   ev->interface = eina_stringshare_add(interface);

   /* raise an event saying a new global has been added */
   ecore_event_add(ECORE_WL2_EVENT_GLOBAL_ADDED, ev,
                   _cb_global_event_free, NULL);
}

static void
_cb_global_remove(void *data, struct wl_registry *registry EINA_UNUSED, unsigned int id)
{
   Ecore_Wl2_Display *ewd;
   Ecore_Wl2_Global *global;
   Ecore_Wl2_Event_Global *ev;

   ewd = data;

   /* try to find this global in our hash */
   global = eina_hash_find(ewd->globals, &id);
   if (!global) return;

   /* allocate space for event structure */
   ev = calloc(1, sizeof(Ecore_Wl2_Event_Global));
   if (!ev) return;

   ev->id = id;
   ev->display = ewd;
   ewd->refs++;
   ev->version = global->version;
   ev->interface = eina_stringshare_add(global->interface);

   /* raise an event saying a global has been removed */
   ecore_event_add(ECORE_WL2_EVENT_GLOBAL_REMOVED, ev,
                   _cb_global_event_free, NULL);

   /* delete this global from our hash */
   if (ewd->globals) eina_hash_del_by_key(ewd->globals, &id);
}

static const struct wl_registry_listener _registry_listener =
{
   _cb_global_add,
   _cb_global_remove
};

static Eina_Bool
_cb_create_data(void *data, Ecore_Fd_Handler *hdl EINA_UNUSED)
{
   Ecore_Wl2_Display *ewd = data;
   struct wl_event_loop *loop;

   loop = wl_display_get_event_loop(ewd->wl.display);
   wl_event_loop_dispatch(loop, 0);

   /* wl_display_flush_clients(ewd->wl.display); */

   return ECORE_CALLBACK_RENEW;
}

static void
_cb_create_prepare(void *data, Ecore_Fd_Handler *hdlr EINA_UNUSED)
{
   Ecore_Wl2_Display *ewd = data;

   wl_display_flush_clients(ewd->wl.display);
}

static Eina_Bool
_recovery_timer(Ecore_Wl2_Display *ewd)
{
   if (!_ecore_wl2_display_connect(ewd, 1))
     return EINA_TRUE;

   ewd->recovery_timer = NULL;
   return EINA_FALSE;
}

static void
_ecore_wl2_display_globals_cleanup(Ecore_Wl2_Display *ewd)
{
   if (ewd->wl.session_recovery)
     zwp_e_session_recovery_destroy(ewd->wl.session_recovery);
   if (ewd->wl.www) www_destroy(ewd->wl.www);
   if (ewd->wl.xdg_wm_base) xdg_wm_base_destroy(ewd->wl.xdg_wm_base);
   if (ewd->wl.zxdg_shell) zxdg_shell_v6_destroy(ewd->wl.zxdg_shell);
   if (ewd->wl.shm) wl_shm_destroy(ewd->wl.shm);
   if (ewd->wl.data_device_manager)
     wl_data_device_manager_destroy(ewd->wl.data_device_manager);
   if (ewd->wl.compositor) wl_compositor_destroy(ewd->wl.compositor);
   if (ewd->wl.subcompositor) wl_subcompositor_destroy(ewd->wl.subcompositor);
   if (ewd->wl.dmabuf) zwp_linux_dmabuf_v1_destroy(ewd->wl.dmabuf);
   if (ewd->wl.efl_aux_hints) efl_aux_hints_destroy(ewd->wl.efl_aux_hints);
   if (ewd->wl.efl_hints) efl_hints_destroy(ewd->wl.efl_hints);

   if (ewd->wl.registry) wl_registry_destroy(ewd->wl.registry);
}

static void
_recovery_timer_add(Ecore_Wl2_Display *ewd)
{
   Eina_Inlist *tmp, *tmp2;
   Ecore_Wl2_Output *output;
   Ecore_Wl2_Input *input;
   Ecore_Wl2_Window *window;

   eina_hash_free_buckets(ewd->globals);

   ecore_main_fd_handler_del(ewd->fd_hdl);
   ewd->fd_hdl = NULL;

   ewd->shell_done = EINA_FALSE;
   ewd->sync_done = EINA_FALSE;
   ewd->recovering = EINA_TRUE;

   _ecore_wl2_display_globals_cleanup(ewd);

   memset(&ewd->wl, 0, sizeof(ewd->wl));
   EINA_INLIST_FOREACH_SAFE(ewd->inputs, tmp, input)
     _ecore_wl2_input_del(input);

   EINA_INLIST_FOREACH_SAFE(ewd->outputs, tmp, output)
     _ecore_wl2_output_del(output);

   EINA_INLIST_FOREACH_SAFE(ewd->windows, tmp, window)
     {
        Ecore_Wl2_Subsurface *subsurf;

        EINA_INLIST_FOREACH_SAFE(window->subsurfs, tmp2, subsurf)
          _ecore_wl2_subsurf_unmap(subsurf);
        _ecore_wl2_window_semi_free(window);
        window->set_config.serial = 0;
        window->req_config.serial = 0;
        window->xdg_configure_ack = NULL;
        window->xdg_set_min_size = NULL;
        window->xdg_set_max_size = NULL;
        window->zxdg_configure_ack = NULL;
        window->zxdg_set_min_size = NULL;
        window->zxdg_set_max_size = NULL;
     }

   ewd->recovery_timer =
     ecore_timer_add(0.5, (Ecore_Task_Cb)_recovery_timer, ewd);
   _ecore_wl2_display_event(ewd, ECORE_WL2_EVENT_DISCONNECT);
}

static void
_begin_recovery_maybe(Ecore_Wl2_Display *ewd, int code)
{
   if ((_server_displays || (code != EPROTO)) && ewd->wl.session_recovery)// && (errno == EPIPE))
     _recovery_timer_add(ewd);
   else if (!_server_displays)
     {
        ERR("Wayland Socket Error: %s", eina_error_msg_get(errno));
        _ecore_wl2_display_signal_exit();
     }
}

static void
_cb_connect_pre(void *data, Ecore_Fd_Handler *hdl EINA_UNUSED)
{
   Ecore_Wl2_Display *ewd = data;
   int ret = 0, code;

   while ((wl_display_prepare_read(ewd->wl.display) != 0) && (ret >= 0))
     ret = wl_display_dispatch_pending(ewd->wl.display);

   if (ret < 0) goto err;

   ret = wl_display_get_error(ewd->wl.display);
   if (ret < 0) goto err;

   return;

err:
   code = errno;
   if ((ret < 0) && (code != EAGAIN))
     {
        _begin_recovery_maybe(ewd, code);
     }
}

static Eina_Bool
_cb_connect_data(void *data, Ecore_Fd_Handler *hdl)
{
   Ecore_Wl2_Display *ewd = data;
   int ret = 0, code;

   if (ecore_main_fd_handler_active_get(hdl, ECORE_FD_READ))
     {
        ret = wl_display_read_events(ewd->wl.display);
        code = errno;
        if ((ret < 0) && (code != EAGAIN)) goto err;
     }
   else
     wl_display_cancel_read(ewd->wl.display);

   wl_display_dispatch_pending(ewd->wl.display);
   if (ecore_main_fd_handler_active_get(hdl, ECORE_FD_WRITE))
     {
        ret = wl_display_flush(ewd->wl.display);
        code = errno;
        if (ret >= 0)
          ecore_main_fd_handler_active_set(hdl, ECORE_FD_READ | ECORE_FD_ALWAYS);

        if ((ret < 0) && (code != EAGAIN)) goto err;
     }

   return ECORE_CALLBACK_RENEW;

err:
   ewd->fd_hdl = NULL;
   _begin_recovery_maybe(ewd, code);

   return ECORE_CALLBACK_CANCEL;
}

static void
_cb_globals_hash_del(void *data)
{
   Ecore_Wl2_Global *global;

   global = data;

   eina_stringshare_del(global->interface);

   free(global);
}

static Ecore_Wl2_Global *
_ecore_wl2_global_find(Ecore_Wl2_Display *ewd, const char *interface)
{
   Eina_Iterator *itr;
   Ecore_Wl2_Global *global = NULL, *g = NULL;

   itr = eina_hash_iterator_data_new(ewd->globals);
   if (!itr) return NULL;

   EINA_ITERATOR_FOREACH(itr, g)
     {
        if (!strcmp(g->interface, interface))
          {
             global = g;
             break;
          }
     }

   eina_iterator_free(itr);
   return global;
}

static void
_ecore_wl2_shell_bind(Ecore_Wl2_Display *ewd)
{
   Ecore_Wl2_Global *global = NULL;
   const char **itr;
   const char *shells[] =
     {
        "xdg_wm_base",
        "zxdg_shell_v6",
        NULL
     };

   if (ewd->shell_done) return;

   for (itr = shells; *itr != NULL; itr++)
     {
        global = _ecore_wl2_global_find(ewd, *itr);
        if (!global) continue;
        break;
     }

   if (!global) return;

   if (!strcmp(global->interface, "xdg_wm_base"))
     {
        ewd->wl.xdg_wm_base =
          wl_registry_bind(ewd->wl.registry, global->id,
                           &xdg_wm_base_interface, 1);
        xdg_wm_base_add_listener(ewd->wl.xdg_wm_base,
                                   &_xdg_shell_listener, ewd);
        ewd->shell_done = EINA_TRUE;
     }
   else if (!strcmp(global->interface, "zxdg_shell_v6"))
     {
        ewd->wl.zxdg_shell =
          wl_registry_bind(ewd->wl.registry, global->id,
                           &zxdg_shell_v6_interface, 1);
        zxdg_shell_v6_add_listener(ewd->wl.zxdg_shell,
                                   &_zxdg_shell_listener, ewd);
        ewd->shell_done = EINA_TRUE;
     }
}

static void
_cb_sync_done(void *data, struct wl_callback *cb, uint32_t serial EINA_UNUSED)
{
   Ecore_Wl2_Event_Sync_Done *ev;
   Ecore_Wl2_Display *ewd;

   ewd = data;
   if (--ewd->syncs) return;
   if (ewd->sync_done) return;

   ewd->sync_done = EINA_TRUE;

   _ecore_wl2_shell_bind(ewd);

   wl_callback_destroy(cb);
   ecore_wl2_display_flush(ewd);

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

   ev->display = ewd;
   ewd->refs++;
   ecore_event_add(ECORE_WL2_EVENT_SYNC_DONE, ev, _display_event_free, ewd);
}

static const struct wl_callback_listener _sync_listener =
{
   _cb_sync_done
};

static void
_ecore_wl2_display_sync_add(Ecore_Wl2_Display *ewd)
{
   struct wl_callback *cb;

   ewd->syncs++;
   cb = wl_display_sync(ewd->wl.display);
   wl_callback_add_listener(cb, &_sync_listener, ewd);
}

static Eina_Bool
_ecore_wl2_display_connect(Ecore_Wl2_Display *ewd, Eina_Bool sync)
{
   /* try to connect to wayland display with this name */
   ewd->wl.display = wl_display_connect(ewd->name);
   if (!ewd->wl.display) return EINA_FALSE;

   ewd->recovering = EINA_FALSE;

   ewd->wl.registry = wl_display_get_registry(ewd->wl.display);
   wl_registry_add_listener(ewd->wl.registry, &_registry_listener, ewd);

   _ecore_wl2_display_sync_add(ewd);

   if (sync)
     {
        /* NB: If we are connecting (as a client), then we will need to setup
         * a callback for display_sync and wait for it to complete. There is no
         * other option here as we need the compositor, shell, etc, to be setup
         * before we can allow a user to make use of the API functions */
        while (!ewd->sync_done)
          {
             int ret;

             ret = wl_display_dispatch(ewd->wl.display);
             if ((ret < 0) && (errno != EAGAIN))
               {
                  ERR("Received Fatal Error on Wayland Display");

                  wl_registry_destroy(ewd->wl.registry);
                  return EINA_FALSE;
               }
          }
     }

   ewd->fd_hdl =
     ecore_main_fd_handler_add(wl_display_get_fd(ewd->wl.display),
                               ECORE_FD_READ | ECORE_FD_WRITE | ECORE_FD_ERROR | ECORE_FD_ALWAYS,
                               _cb_connect_data, ewd, NULL, ewd);

   ecore_main_fd_handler_prepare_callback_set
     (ewd->fd_hdl, _cb_connect_pre, ewd);

   _ecore_wl2_display_event(ewd, ECORE_WL2_EVENT_CONNECT);
   return EINA_TRUE;
}

static void
_ecore_wl2_display_cleanup(Ecore_Wl2_Display *ewd)
{
   Ecore_Wl2_Output *output;
   Ecore_Wl2_Input *input;
   Eina_Inlist *tmp;

   if (ewd->xkb_context) xkb_context_unref(ewd->xkb_context);

   /* free each input */
   EINA_INLIST_FOREACH_SAFE(ewd->inputs, tmp, input)
     _ecore_wl2_input_del(input);

   /* free each output */
   EINA_INLIST_FOREACH_SAFE(ewd->outputs, tmp, output)
     _ecore_wl2_output_del(output);

   if (ewd->fd_hdl) ecore_main_fd_handler_del(ewd->fd_hdl);

   eina_hash_free(ewd->globals);

   _ecore_wl2_display_globals_cleanup(ewd);
}

Ecore_Wl2_Window *
_ecore_wl2_display_window_surface_find(Ecore_Wl2_Display *display, struct wl_surface *wl_surface)
{
   Ecore_Wl2_Window *window;

   if ((!display) || (!wl_surface)) return NULL;

   EINA_INLIST_FOREACH(display->windows, window)
     {
        if ((window->surface) &&
            (window->surface == wl_surface))
          return window;
     }

   return NULL;
}

EAPI Ecore_Wl2_Display *
ecore_wl2_display_create(const char *name)
{
   Ecore_Wl2_Display *ewd;
   struct wl_event_loop *loop;

   if (!_server_displays)
     _server_displays = eina_hash_string_superfast_new(NULL);

   if (name)
     {
        /* someone wants to create a server with a specific display */

        /* check hash of cached server displays for this name */
        ewd = eina_hash_find(_server_displays, name);
        if (ewd) goto found;
     }

   /* allocate space for display structure */
   ewd = calloc(1, sizeof(Ecore_Wl2_Display));
   if (!ewd) return NULL;

   ewd->refs++;
   ewd->pid = getpid();

   /* try to create new wayland display */
   ewd->wl.display = wl_display_create();
   if (!ewd->wl.display)
     {
        ERR("Could not create wayland display");
        goto create_err;
     }

   if (!name)
     {
        const char *n;

        n = wl_display_add_socket_auto(ewd->wl.display);
        if (!n)
          {
             ERR("Failed to add display socket");
             goto socket_err;
          }

        ewd->name = strdup(n);
     }
   else
     {
        if (wl_display_add_socket(ewd->wl.display, name))
          {
             ERR("Failed to add display socket");
             goto socket_err;
          }

        ewd->name = strdup(name);
     }

   setenv("WAYLAND_DISPLAY", ewd->name, 1);
   DBG("WAYLAND_DISPLAY: %s", ewd->name);

   loop = wl_display_get_event_loop(ewd->wl.display);

   ewd->fd_hdl =
     ecore_main_fd_handler_add(wl_event_loop_get_fd(loop),
                               ECORE_FD_READ | ECORE_FD_ERROR,
                               _cb_create_data, ewd, NULL, NULL);

   ecore_main_fd_handler_prepare_callback_set(ewd->fd_hdl,
                                              _cb_create_prepare, ewd);

   /* add this new server display to hash */
   eina_hash_add(_server_displays, ewd->name, ewd);

   return ewd;

socket_err:
   wl_display_destroy(ewd->wl.display);

create_err:
   free(ewd);
   return NULL;

found:
   ewd->refs++;
   return ewd;
}

Eina_Bool
_ecore_wl2_display_sync_get(void)
{
   return !_server_displays || !eina_hash_population(_server_displays);
}

EAPI Ecore_Wl2_Display *
ecore_wl2_display_connect(const char *name)
{
   Ecore_Wl2_Display *ewd;
   const char *n;
   Eina_Bool hash_create = !_client_displays;

   if (!_client_displays)
     _client_displays = eina_hash_string_superfast_new(NULL);

   if (!name)
     {
        /* client wants to connect to default display */
        n = getenv("WAYLAND_DISPLAY");
        if (!n) n = "wayland-0";

        /* we have a default wayland display */

        /* check hash of cached client displays for this name */
        ewd = eina_hash_find(_client_displays, n);
        if (ewd) goto found;
     }
   else
     {
        /* client wants to connect to specific display */

        /* check hash of cached client displays for this name */
        ewd = eina_hash_find(_client_displays, name);
        if (ewd) goto found;
     }

   /* allocate space for display structure */
   ewd = calloc(1, sizeof(Ecore_Wl2_Display));
   if (!ewd) return NULL;

   ewd->refs++;

   if (name)
     ewd->name = strdup(name);
   else if (n)
     ewd->name = strdup(n);

   ewd->globals = eina_hash_int32_new(_cb_globals_hash_del);

   ewd->xkb_context = xkb_context_new(0);
   if (!ewd->xkb_context) goto context_err;

   /* check server display hash and match on pid. If match, skip sync */
   if (!_ecore_wl2_display_connect(ewd, _ecore_wl2_display_sync_get()))
     goto connect_err;

   /* add this new client display to hash */
   eina_hash_add(_client_displays, ewd->name, ewd);

   return ewd;

connect_err:
   xkb_context_unref(ewd->xkb_context);
   ewd->xkb_context = NULL;

context_err:
   eina_hash_free(ewd->globals);
   free(ewd->name);
   free(ewd);

   if (hash_create)
     {
        eina_hash_free(_client_displays);
        _client_displays = NULL;
     }
   return NULL;

found:
   ewd->refs++;
   return ewd;
}

EAPI void
ecore_wl2_display_disconnect(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN(display);
   int ret;

   do
     {
        ret = wl_display_dispatch_pending(display->wl.display);
     } while (ret > 0);

   --display->refs;
   if (display->refs == 0)
     {
        _ecore_wl2_display_cleanup(display);

        wl_display_disconnect(display->wl.display);

        /* remove this client display from hash */
        eina_hash_del_by_key(_client_displays, display->name);

        free(display->name);
        free(display);
     }
}

EAPI void
ecore_wl2_display_destroy(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN(display);

   --display->refs;
   if (display->refs == 0)
     {
        /* this ensures that things like wl_registry are destroyed
         * before we destroy the actual wl_display */
        _ecore_wl2_display_cleanup(display);

        wl_display_destroy(display->wl.display);

        /* remove this client display from hash */
        eina_hash_del_by_key(_server_displays, display->name);
        ecore_timer_del(display->recovery_timer);

        free(display->name);
        free(display);
     }
}

EAPI void
ecore_wl2_display_terminate(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN(display);
   wl_display_terminate(display->wl.display);
}

EAPI struct wl_display *
ecore_wl2_display_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   return display->wl.display;
}

EAPI struct wl_shm *
ecore_wl2_display_shm_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   return display->wl.shm;
}

EAPI void *
ecore_wl2_display_dmabuf_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   return display->wl.dmabuf;
}

EAPI Eina_Iterator *
ecore_wl2_display_globals_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   EINA_SAFETY_ON_NULL_RETURN_VAL(display->globals, NULL);

   return eina_hash_iterator_data_new(display->globals);
}

EAPI void
ecore_wl2_display_screen_size_get(Ecore_Wl2_Display *display, int *w, int *h)
{
   Ecore_Wl2_Output *output;
   int ow = 0, oh = 0;

   EINA_SAFETY_ON_NULL_RETURN(display);

   if (w) *w = 0;
   if (h) *h = 0;

   EINA_INLIST_FOREACH(display->outputs, output)
     {
        switch (output->transform)
          {
           case WL_OUTPUT_TRANSFORM_90:
           case WL_OUTPUT_TRANSFORM_270:
           case WL_OUTPUT_TRANSFORM_FLIPPED_90:
           case WL_OUTPUT_TRANSFORM_FLIPPED_270:
             ow += output->geometry.h;
             oh += output->geometry.w;
             break;
           default:
             ow += output->geometry.w;
             oh += output->geometry.h;
             break;
          }
     }

   if (w) *w = ow;
   if (h) *h = oh;
}

EAPI struct wl_registry *
ecore_wl2_display_registry_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);

   return display->wl.registry;
}

EAPI int
ecore_wl2_display_compositor_version_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, 0);

   return display->wl.compositor_version;
}

EAPI Eina_Iterator *
ecore_wl2_display_inputs_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(display->pid, NULL);
   return eina_inlist_iterator_new(display->inputs);
}

EAPI Ecore_Wl2_Input *
ecore_wl2_display_input_find(const Ecore_Wl2_Display *display, unsigned int id)
{
   Ecore_Wl2_Input *input;

   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(display->pid, NULL);
   EINA_INLIST_FOREACH(display->inputs, input)
     if (input->id == id) return input;
   return NULL;
}

EAPI Ecore_Wl2_Input *
ecore_wl2_display_input_find_by_name(const Ecore_Wl2_Display *display, const char *name)
{
   Ecore_Wl2_Input *input;

   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(display->pid, NULL);
   EINA_INLIST_FOREACH(display->inputs, input)
     if (eina_streq(input->name, name)) return input;
   return NULL;
}

EAPI Eina_Bool
ecore_wl2_display_sync_is_done(const Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, EINA_FALSE);
   return display->sync_done;
}

EAPI const char *
ecore_wl2_display_name_get(const Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   return display->name;
}

EAPI void
ecore_wl2_display_flush(Ecore_Wl2_Display *display)
{
   int ret, code;

   EINA_SAFETY_ON_NULL_RETURN(display);

   ret = wl_display_flush(display->wl.display);
   if (ret >= 0) return;

   code = errno;
   if (code == EAGAIN)
     {
        ecore_main_fd_handler_active_set(display->fd_hdl,
                                         (ECORE_FD_READ | ECORE_FD_WRITE | ECORE_FD_ALWAYS));
        return;
     }

   _begin_recovery_maybe(display, code);
}

EAPI Ecore_Wl2_Window *
ecore_wl2_display_window_find_by_surface(Ecore_Wl2_Display *display, struct wl_surface *surface)
{
   return _ecore_wl2_display_window_surface_find(display, surface);
}

EAPI Ecore_Wl2_Display *
ecore_wl2_connected_display_get(const char *name)
{
   Ecore_Wl2_Display *ewd;

   EINA_SAFETY_ON_NULL_RETURN_VAL(_client_displays, NULL);

   if (!name)
     {
        const char *n;

        /* client wants to connected to default display */
        n = getenv("WAYLAND_DISPLAY");
        if (!n) n = "wayland-0";

        /* we have a default wayland display */

        /* check hash of cached client displays for this name */
        ewd = eina_hash_find(_client_displays, n);
     }
   else
     {
        /* client wants to connect to specific display */

        /* check hash of cached client displays for this name */
        ewd = eina_hash_find(_client_displays, name);
     }

   return ewd;
}

EAPI struct wl_compositor *
ecore_wl2_display_compositor_get(Ecore_Wl2_Display *display)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(display, NULL);
   return display->wl.compositor;
}
