#define E_COMP_WL
#include "e.h"

/* handle include for printing uint64_t */
#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#include "www-server-protocol.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_adjust(int pid, int set, int adj, Eina_Bool use_adj, Eina_Bool adj_child, Eina_Bool do_child)
{
   Eina_List *files;
   char *file, buff[PATH_MAX];
   FILE *f;
   int pid2, ppid;
   int num_read;
   int n;

   if (use_adj)
     n = (getpriority(PRIO_PROCESS, pid) + adj);
   else
     n = set;

   setpriority(PRIO_PROCESS, pid, n);

   if (adj_child)
     use_adj = EINA_TRUE;

   if (!do_child) return;

   files = ecore_file_ls("/proc");
   EINA_LIST_FREE(files, file)
      {
         if (!isdigit(file[0]))
           continue;

         snprintf(buff, sizeof(buff), "/proc/%s/stat", file);
         if ((f = fopen(buff, "r")))
           {
              pid2 = -1;
              ppid = -1;
              num_read = fscanf(f, "%i %*s %*s %i %*s", &pid2, &ppid);
              fclose(f);
              if (num_read == 2 && ppid == pid)
                _e_comp_wl_client_priority_adjust(pid2, set,
                                                  adj, use_adj,
                                                  adj_child, do_child);
           }

         free(file);
      }
}

static void
_e_comp_wl_client_priority_raise(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   _e_comp_wl_client_priority_adjust(ec->netwm.pid,
                                     e_config->priority - 1, -1,
                                     EINA_FALSE, EINA_TRUE, EINA_FALSE);
}

static void
_e_comp_wl_client_priority_normal(E_Client *ec)
{
   if (ec->netwm.pid <= 0) return;
   if (ec->netwm.pid == getpid()) return;
   _e_comp_wl_client_priority_adjust(ec->netwm.pid, e_config->priority, 1,
                                     EINA_FALSE, EINA_TRUE, EINA_FALSE);
}

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;
   if (ec->comp_data->www.surface)
     {
        if (ec->comp_data->moved)
          {
             if ((!ec->maximized) && (!ec->fullscreen) && (!ec->comp_data->maximizing) && (!ec->maximize_override))
               www_surface_send_status(ec->comp_data->www.surface,
                 ec->x - ec->comp_data->www.x, ec->y - ec->comp_data->www.y, lround(ecore_loop_time_get()));
             else if (ec->comp_data->maximizing || ec->maximize_override)
               www_surface_send_status(ec->comp_data->www.surface,
                 ec->w / 3, ec->h / 3, lround(ecore_loop_time_get()));
          }
        ec->comp_data->www.x = ec->x;
        ec->comp_data->www.y = ec->y;
     }
   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;
}

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_screensaver_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);

   /* 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));

   if (ec->internal_elm_win)
     evas_object_hide(ec->frame);
}

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);
}

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->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;
     }
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"))
     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;
}
