#define EFL_INTERNAL_UNSTABLE
#define EFL_CANVAS_GROUP_PROTECTED

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

#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif

#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <dlfcn.h>

#include "Ecore_Evas.h"
#include "Ecore_Wl2.h"
#include "ecore_wl2_internal.h"
#include "Ecore_Input.h"
#include "Evas_GL.h"

/* We have to include the wayland server stuff after any wayland client stuff
 * like Ecore_Wl2.h or we'll get complaints about struct wl_buffer being
 * deprecated.
 * That's because its deprecated in server code - it's still the name of
 * the opaque struct client side.
 */
#include <wayland-server.h>
#include "xdg-shell-server-protocol.h"
#include "efl-hints-server-protocol.h"
#include "dmabuf.h"

# ifdef HAVE_ECORE_X
#include "Ecore_X.h"
#endif
#include "Evas_Internal.h"
#include "canvas/evas_canvas_eo.h"
#include "Efl_Canvas_Wl.h"

#undef COORDS_INSIDE
#define COORDS_INSIDE(x, y, xx, yy, ww, hh) \
  (((x) < ((xx) + (ww))) && ((y) < ((yy) + (hh))) && ((x) >= (xx)) && ((y) >= (yy)))

#ifdef __linux__
# include <linux/input.h>
#else
# define BTN_LEFT 0x110
# define BTN_RIGHT 0x111
# define BTN_MIDDLE 0x112
# define BTN_SIDE 0x113
# define BTN_EXTRA 0x114
# define BTN_FORWARD 0x115
# define BTN_BACK 0x116
#endif

#undef container_of
#  define container_of(ptr, type, member) \
   ({ \
      const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \
      (type *)(void *)( (char *)__mptr - offsetof(type,member) ); \
   })

#ifndef EGL_HEIGHT
# define EGL_HEIGHT			0x3056
#endif
#ifndef EGL_WIDTH
# define EGL_WIDTH			0x3057
#endif

#ifndef EGL_TEXTURE_FORMAT
# define EGL_TEXTURE_FORMAT		0x3080
#endif
#ifndef EGL_TEXTURE_RGBA
# define EGL_TEXTURE_RGBA		0x305E
#endif
#ifndef DRM_FORMAT_ARGB8888
# define DRM_FORMAT_ARGB8888           0x34325241
#endif
#ifndef DRM_FORMAT_XRGB8888
# define DRM_FORMAT_XRGB8888           0x34325258
#endif

#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
		     WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
		     WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)

#define MY_CLASS EFL_CANVAS_WL_CLASS

typedef struct Input_Sequence
{
   EINA_INLIST;
   uint32_t id;
   uint32_t down_serial;
   uint32_t down_time;
   uint32_t up_serial;
   uint32_t up_time;
   Eina_Bool pass : 1;
} Input_Sequence;

typedef struct Comp_Subsurface Comp_Subsurface;
typedef struct Comp_Surface Comp_Surface;

typedef struct Comp_Buffer
{
   struct wl_resource *res;
   Comp_Surface *cs;
   Eina_List *renders;
   Eina_List *post_renders;
   int x, y, w, h;
   struct wl_listener destroy_listener;
   struct wl_shm_buffer *shm_buffer;
   struct wl_shm_pool *pool;
   struct linux_dmabuf_buffer *dmabuf_buffer;
   Eina_Bool dbg : 1;
} Comp_Buffer;

typedef struct Comp
{
   Efl_Canvas_Wl_Rotation rotation;
   double scale;
   char *env;
   Efl_Exe_Flags flags;
   Ecore_Wl2_Display *disp;
   Ecore_Wl2_Display *parent_disp;
   Ecore_Wl2_Display *client_disp;
   struct wl_display *display;

   double wayland_time_base;
   Eo *obj;
   Eo *clip;
   Eo *events;

   Eina_Hash *exes;

   Eina_Inlist *surfaces;
   unsigned int surfaces_count;
   Eina_Hash *client_surfaces;
   Comp_Surface *active_surface;

   Eina_Inlist *shells;
   Eina_List *render_queue;
   Eina_List *post_render_queue;
   Evas *evas;
   Evas_GL *gl;
   Evas_GL_Config *glcfg;
   Evas_GL_Context *glctx;
   Evas_GL_Surface *glsfc;
   Evas_GL_API *glapi;
   Eina_List *output_resources;
   Eina_Inlist *seats;
   Eina_Bool rendering : 1;
   Eina_Bool data_device_proxy : 1;
   Eina_Bool x11_selection : 1;
   Eina_Bool rtl : 1;
   Eina_Bool aspect : 1;
   Eina_Bool minmax : 1;
} Comp;

typedef struct Comp_Data_Device_Source Comp_Data_Device_Source;

typedef struct Comp_Seat
{
   EINA_INLIST;
   Comp *c;
   Eina_Stringshare *name;
   struct wl_global *global;

   Ecore_Wl2_Input *seat;
   Ecore_Wl2_Input *client_seat;
   Ecore_Wl2_Offer *client_offer;
   uint32_t client_selection_serial;
   Eo *dev;
   Eina_List *resources;

   Eina_Hash *data_devices;
   struct
   {
      struct wl_resource *res;
      Comp_Data_Device_Source *source;
      Comp_Surface *surface;
      Comp_Surface *enter;
      uint32_t id;
      Ecore_Evas *proxy_win;
      Ecore_Window x11_owner;
      Eina_List *x11_types;
      Eina_Bool tch : 1;
   } drag;
   Comp_Data_Device_Source *selection_source;
   uint32_t selection_serial;
   Ecore_Window x11_selection_owner;

   struct wl_client *active_client;
   Comp_Surface *grab;

   struct
   {
      struct wl_array keys;
      struct wl_array *keys_external;
      struct
      {
         xkb_mod_mask_t depressed;
         xkb_mod_mask_t latched;
         xkb_mod_mask_t locked;
         xkb_layout_index_t group;
         Eina_Bool changed : 1;
      } mods;
      struct xkb_context *context;
      struct xkb_keymap *keymap;
      struct xkb_state *state;
      const char *keymap_str;
      int keymap_str_size;
      int repeat_rate;
      int repeat_delay;
      Eina_Hash *resources;
      Comp_Surface *enter;
      Eina_Bool external : 1;
   } kbd;

   struct
   {
      Eina_Hash *resources;
      uint32_t button_mask;
      uint32_t enter_serial;
      Eina_Inlist *events;
      Comp_Surface *enter;
      struct
      {
         Comp_Surface *surface;
         int x, y;
      } cursor;
      struct
      {
         Eo *obj;
         int layer;
         int x, y;
      } efl;
      Evas_Point pos;
      Eina_Bool in : 1;
   } ptr;

   struct
   {
      Eina_Hash *resources;
      Eina_Inlist *events;
      Comp_Surface *enter;
      Evas_Point pos;
   } tch;

   Eina_Bool pointer : 1;
   Eina_Bool keyboard : 1;
   Eina_Bool touch : 1;
   Eina_Bool focused : 1;
   Eina_Bool selection_changed : 1;
   Eina_Bool selection_exists : 1;
   Eina_Bool event_propagate : 1;
} Comp_Seat;

typedef struct Comp_Buffer_State
{
   Comp_Buffer *buffer;
   Eina_Tiler *opaque;
   Eina_Tiler *damages;
   Eina_Tiler *input;
   Eina_List *frames;
   Eina_Bool attach : 1;
   Eina_Bool set_opaque : 1;
   Eina_Bool set_input : 1;
} Comp_Buffer_State;

typedef struct Shell_Data
{
   EINA_INLIST;
   Comp *c;
   struct wl_resource *res;
   Eina_List *surfaces;
   Eina_Inlist *positioners;
   Eina_Bool ping : 1;
} Shell_Data;


typedef struct Shell_Positioner
{
   EINA_INLIST;
   Shell_Data *sd;
   struct wl_resource *res;
   Evas_Coord_Size size;
   Eina_Rectangle anchor_rect;
   enum xdg_positioner_anchor anchor;
   enum xdg_positioner_gravity gravity;
   enum xdg_positioner_constraint_adjustment constrain;
   Evas_Coord_Point offset;
} Shell_Positioner;

struct Comp_Surface
{
   EINA_INLIST;
   Comp *c;
   Eo *obj;
   Eo *clip;
   Eo *img;
   Eina_Array *input_rects;
   Eina_Array *opaque_rects;
   Eina_List *proxies;
   struct wl_resource *res;
   struct wl_resource *role;
   Comp_Seat *drag; //drag surface
   Comp_Buffer *buffer[2]; // new, prev
   /* subsurface stacking order */
   Eina_List *subsurfaces;
   Eina_List *pending_subsurfaces;
   /* any child surface (xdg or subsurface */
   Eina_Inlist *children;
   Comp_Surface *parent;

   Eina_Tiler *opaque;
   Eina_Tiler *input;
   Eina_List *frames;
   Comp_Subsurface *subsurface;
   Comp_Buffer_State pending;
   struct
   {
      struct wl_resource *surface;
      Eina_Rectangle geom;
      Shell_Data *data;
      Eina_Stringshare *title;
      Eina_Stringshare *app_id;
      Shell_Positioner *positioner;
      Eina_List *grabs;
      Eina_Bool popup : 1;
      Eina_Bool new : 1;
      Eina_Bool activated : 1;
   } shell;
   Eina_Bool mapped : 1;
   Eina_Bool cursor : 1;
   Eina_Bool render_queue : 1;
   Eina_Bool post_render_queue : 1;
   Eina_Bool dead : 1;
   Eina_Bool commit : 1;
   Eina_Bool extracted : 1;
   Eina_Bool hint_set_weight : 1;
};

struct Comp_Subsurface
{
   Comp_Surface *surface;
   Comp_Buffer_State cache;
   Evas_Point offset;
   Evas_Point pending_offset;
   Eina_Bool set_offset : 1;
   Eina_Bool sync : 1;
   Eina_Bool cached : 1;
};

typedef enum Comp_Data_Device_Offer_Type
{
   COMP_DATA_DEVICE_OFFER_TYPE_DND,
   COMP_DATA_DEVICE_OFFER_TYPE_CLIPBOARD,
} Comp_Data_Device_Offer_Type;

typedef struct Comp_Data_Device_Offer
{
   Comp_Data_Device_Offer_Type type;
   struct wl_resource *res;
   Comp_Data_Device_Source *source;
   Ecore_Wl2_Offer *proxy_offer;
   uint32_t dnd_actions;
   uint32_t preferred_dnd_action;
   Eina_Bool in_ask : 1;
   Eina_Bool proxy : 1;
} Comp_Data_Device_Offer;

typedef struct Comp_Data_Device_Source
{
   struct wl_resource *res;
   Comp_Seat *seat;
   Comp_Data_Device_Offer *offer;
   Ecore_Window x11_owner;
   Eina_Inlist *transfers;
   Eina_Binbuf *reader_data;
   Ecore_Fd_Handler *reader;
   Eina_List *mime_types;
   uint32_t dnd_actions;
   uint32_t current_dnd_action;
   uint32_t compositor_action;
   Ecore_Event_Handler *proxy_send_handler;
   uint32_t proxy_serial;
   Eina_Bool actions_set : 1;
   Eina_Bool accepted : 1;
   Eina_Bool proxy : 1;
} Comp_Data_Device_Source;

typedef struct Comp_Data_Device_Transfer
{
   EINA_INLIST;
   Comp_Data_Device_Offer_Type type;
   Ecore_Fd_Handler *fdh;
   size_t offset;
   Eina_Stringshare *mime_type;
   Comp_Data_Device_Source *source;
} Comp_Data_Device_Transfer;

static Eina_List *comps;
static Eina_List *handlers;

static inline Eina_Tiler *
tiler_new(void)
{
   Eina_Tiler *t;

   t = eina_tiler_new(65535, 65535);
   eina_tiler_tile_size_set(t, 1, 1);
   return t;
}

static inline void
fdh_del(Ecore_Fd_Handler *fdh)
{
   int fd;
   if (!fdh) return;
   fd = ecore_main_fd_handler_fd_get(fdh);
   if (fd >= 0)
     close(fd);
   ecore_main_fd_handler_del(fdh);
}

#define PTR_SWAP(A, B) ptr_swap((void**)A, (void**)B)

static inline void
ptr_swap(void **a, void **b)
{
   void *c;

   c = *a;
   *a = *b;
   *b = c;
}

static inline void
array_clear(Eina_Array **arr)
{
   Eina_Array *a = *arr;

   if (!a) return;
   while (eina_array_count(a))
     evas_object_del(eina_array_pop(a));
   eina_array_free(a);
   *arr = NULL;
}

static inline Eina_Bool
client_allowed_check(Comp *c, struct wl_client *client)
{
   pid_t p;
   int32_t pid;
   Eina_Bool err;

   wl_client_get_credentials(client, &p, NULL, NULL);
   if (p == getpid()) return EINA_TRUE;
   pid = p;
   err = (!c->exes) || !eina_hash_find(c->exes, &pid);
   if (err)
     wl_client_post_no_memory(client);
   return !err;
}

static inline void
comp_data_device_source_reader_clear(Comp_Data_Device_Source *ds)
{
   fdh_del(ds->reader);
   ds->reader = NULL;
   eina_binbuf_free(ds->reader_data);
   ds->reader_data = NULL;
}

static inline void
comp_surface_reparent(Comp_Surface *cs, Comp_Surface *pcs)
{
   if (cs->parent == pcs) return;
   if (!cs->extracted)
     evas_object_smart_member_del(cs->obj);
   if (cs->parent)
     cs->parent->children = eina_inlist_remove(cs->parent->children, EINA_INLIST_GET(cs));
   if (pcs)
     {
        cs->c->surfaces = eina_inlist_remove(cs->c->surfaces, EINA_INLIST_GET(cs));
        cs->c->surfaces_count--;
        if (!cs->extracted)
          evas_object_smart_member_add(cs->obj, pcs->obj);
        pcs->children = eina_inlist_append(pcs->children, EINA_INLIST_GET(cs));
     }
   else
     {
        if (!cs->extracted)
          evas_object_smart_member_add(cs->obj, cs->c->obj);
        cs->c->surfaces = eina_inlist_append(cs->c->surfaces, EINA_INLIST_GET(cs));
        cs->c->surfaces_count++;
     }
   cs->parent = pcs;
}

static inline struct wl_resource *
data_device_find(Comp_Seat *s, struct wl_resource *resource)
{
   struct wl_client *client = wl_resource_get_client(resource);
   return eina_hash_find(s->data_devices, &client);
}

static inline void
seat_drag_end(Comp_Seat *s)
{
   s->drag.tch = 0;
   s->drag.id = 0;
   s->drag.surface = NULL;
   s->drag.res = NULL;
   s->drag.enter = NULL;
}

static inline Comp_Seat *
seat_find(Comp_Surface *cs, const Eo *dev)
{
   const Eo *seat = dev;
   Comp_Seat *s;

   if (!cs->c->parent_disp) return EINA_INLIST_CONTAINER_GET(cs->c->seats, Comp_Seat);
   if (evas_device_class_get(seat) != EVAS_DEVICE_CLASS_SEAT)
     seat = evas_device_parent_get(seat);
   EINA_INLIST_FOREACH(cs->c->seats, s)
     if (s->dev == seat) return s;
   return NULL;
}

static inline Comp_Seat *
comp_seat_find(Comp *c, const Eo *dev)
{
   const Eo *seat = dev;
   Comp_Seat *s;

   if (!c->parent_disp) return EINA_INLIST_CONTAINER_GET(c->seats, Comp_Seat);
   if (evas_device_class_get(seat) != EVAS_DEVICE_CLASS_SEAT)
     seat = evas_device_parent_get(seat);
   EINA_INLIST_FOREACH(c->seats, s)
     if (s->dev == seat) return s;
   return NULL;
}

static inline Eina_List *
seat_kbd_active_resources_get(Comp_Seat *s)
{
   Eina_List *l, *lcs, *llcs;
   Comp_Surface *cs;

   if (!s->active_client) return NULL;
   if (!s->kbd.resources) return NULL;
   l = eina_hash_find(s->kbd.resources, &s->active_client);
   if (!l) return NULL;
   lcs = eina_hash_find(s->c->client_surfaces, &s->active_client);
   if (!lcs) return NULL;
   EINA_LIST_REVERSE_FOREACH(lcs, llcs, cs)
     if (cs->role && (!cs->shell.popup)) return l;
   return NULL;
}

static inline Eina_List *
seat_ptr_resources_get(Comp_Seat *s, struct wl_client *client)
{
   return s->ptr.resources ? eina_hash_find(s->ptr.resources, &client) : NULL;
}

static inline Eina_List *
seat_tch_resources_get(Comp_Seat *s, struct wl_client *client)
{
   return s->tch.resources ? eina_hash_find(s->tch.resources, &client) : NULL;
}

static void comp_render_pre_proxied(Eo *o, Evas *e, void *event_info);
static void comp_render_post_proxied(Comp_Surface *cs, Evas *e, void *event_info);
static void comp_surface_commit_image_state(Comp_Surface *cs, Comp_Buffer *buffer, Eo *o);

static void
comp_surface_proxy_del(void *data, Evas *e, Eo *obj, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs = data;
   int i;

   cs->proxies = eina_list_remove(cs->proxies, obj);
   for (i = 0; i < 2; i++)
     {
        Comp_Buffer *buffer = cs->buffer[i];
        if (!buffer) continue;
        if (buffer->renders) buffer->renders = eina_list_remove(buffer->renders, e);
        if (buffer->post_renders) buffer->post_renders = eina_list_remove(buffer->post_renders, e);
     }
   evas_event_callback_del_full(e, EVAS_CALLBACK_RENDER_PRE, (Evas_Event_Cb)comp_render_pre_proxied, obj);
   evas_event_callback_del_full(e, EVAS_CALLBACK_RENDER_POST, (Evas_Event_Cb)comp_render_post_proxied, cs);
#ifdef HAVE_ECORE_X
   if (ecore_x_display_get())
     ecore_x_dnd_callback_pos_update_set(NULL, NULL);
#endif
}

static void
comp_surface_proxy_resize(void *data, Evas *e EINA_UNUSED, Eo *obj, void *event_info EINA_UNUSED)
{
   int w, h;

   evas_object_geometry_get(obj, NULL, NULL, &w, &h);
   ecore_evas_resize(data, w, h);
}

static Eina_Bool
comp_surface_is_alpha(Comp_Surface *cs, Comp_Buffer *buffer)
{
   int format;
   if (buffer->shm_buffer)
     format = wl_shm_buffer_get_format(buffer->shm_buffer);
   else if (buffer->dmabuf_buffer)
     format = buffer->dmabuf_buffer->attributes.format;
   else if (cs->c->gl)
     cs->c->glapi->evasglQueryWaylandBuffer(cs->c->gl, buffer->res, EGL_TEXTURE_FORMAT, &format);
   else return EINA_FALSE; //not a real case

   switch (format)
     {
      case DRM_FORMAT_ARGB8888:
      case WL_SHM_FORMAT_ARGB8888:
      case EGL_TEXTURE_RGBA:
        return EINA_TRUE;
      default:
        break;
     }
   return EINA_FALSE;
}

static void
comp_surface_proxy_win_del(Ecore_Evas *ee)
{
   Comp_Seat *s = ecore_evas_data_get(ee, "comp_seat");
   if (!s) return;

   s->drag.proxy_win = NULL;
   //fprintf(stderr, "PROXY WIN DEL\n");
}

static void
seat_drag_proxy_win_add(Comp_Seat *s)
{
   Eo *o;

   if (s->drag.proxy_win) abort();
   evas_object_hide(s->drag.surface->obj);
   s->drag.proxy_win = ecore_evas_new(NULL, 0, 0, 1, 1, NULL);
   ecore_evas_data_set(s->drag.proxy_win, "comp_seat", s);
   ecore_evas_callback_destroy_set(s->drag.proxy_win, comp_surface_proxy_win_del);
   ecore_evas_alpha_set(s->drag.proxy_win, 1);
   ecore_evas_borderless_set(s->drag.proxy_win, 1);
   ecore_evas_override_set(s->drag.proxy_win, 1);
   ecore_evas_show(s->drag.proxy_win);
   o = evas_object_image_filled_add(ecore_evas_get(s->drag.proxy_win));
   evas_object_data_set(o, "comp_surface", s->drag.surface);
   evas_event_callback_add(ecore_evas_get(s->drag.proxy_win),
     EVAS_CALLBACK_RENDER_PRE, (Evas_Event_Cb)comp_render_pre_proxied, o);
   evas_event_callback_add(ecore_evas_get(s->drag.proxy_win),
     EVAS_CALLBACK_RENDER_POST, (Evas_Event_Cb)comp_render_post_proxied, s->drag.surface);
   evas_object_image_border_center_fill_set(o, EVAS_BORDER_FILL_SOLID);
   evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
   if (!s->drag.surface->render_queue)
     {
        comp_surface_commit_image_state(s->drag.surface, s->drag.surface->buffer[1], o);
        evas_object_image_alpha_set(o,
          comp_surface_is_alpha(s->drag.surface, s->drag.surface->buffer[1]));
        evas_object_image_data_update_add(o, 0, 0, 9999, 9999);
     }
   evas_object_show(o);
   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL,
     comp_surface_proxy_del, s->drag.surface);
   evas_object_event_callback_add(o, EVAS_CALLBACK_RESIZE,
     comp_surface_proxy_resize, s->drag.proxy_win);
   evas_object_resize(o,
     s->drag.surface->buffer[!s->drag.surface->render_queue]->w,
     s->drag.surface->buffer[!s->drag.surface->render_queue]->h);
   s->drag.surface->proxies = eina_list_append(s->drag.surface->proxies, o);
}

static void comp_surface_send_data_device_leave(Comp_Surface *cs, Comp_Seat *s);
static void comp_surface_send_data_device_enter(Comp_Surface *cs, Comp_Seat *s);

static void
dnd_motion(Comp_Seat *s, int ex, int ey)
{
   struct wl_resource *res;
   Eina_List *l, *lcs;
   Comp_Surface *cs;

   s->ptr.pos.x = ex;
   s->ptr.pos.y = ey;
   if (!s->active_client) return;
   l = eina_hash_find(s->c->client_surfaces, &s->active_client);
   EINA_LIST_REVERSE_FOREACH(l, lcs, cs)
     {
        int x, y, w, h;
        if ((!cs->mapped) || (!cs->role)) continue;
        evas_object_geometry_get(cs->obj, &x, &y, &w, &h);
        if (!COORDS_INSIDE(ex, ey, x, y, w, h)) continue;
        if (s->drag.enter != cs)
          {
             if (s->drag.enter)
               comp_surface_send_data_device_leave(s->drag.enter, s);
             s->drag.enter = cs;
             if (s->drag.source)
               comp_surface_send_data_device_enter(cs, s);
          }
        if (!s->drag.source) break;
        res = eina_hash_find(s->data_devices, &s->active_client);
        wl_data_device_send_motion(res,
          (unsigned int)((unsigned long long)(ecore_time_get() * 1000.0) & 0xffffffff),
          wl_fixed_from_int(ex - x), wl_fixed_from_int(ey - y));
        break;
     }
}
#ifdef HAVE_ECORE_X
static void x11_send_send(Comp_Data_Device_Source *source, const char* mime_type, int32_t fd, Comp_Data_Device_Offer_Type type);
#endif
#include "copiedfromweston.x"
#ifdef HAVE_ECORE_X
# include "x11.x"
#endif

static void
resource_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   wl_resource_destroy(resource);
}

#define CONTAINS(x, y, w, h, xx, yy, ww, hh) \
  (((xx) >= (x)) && (((x) + (w)) >= ((xx) + (ww))) && ((yy) >= (y)) && (((y) + (h)) >= ((yy) + (hh))))
#define CONSTRAINED(W, H, X, Y) \
   !CONTAINS(zx, zy, zw, zh, (X), (Y), (W), (H))

static int
_apply_positioner_x(int x, Shell_Positioner *sp, Eina_Bool invert)
{
   enum xdg_positioner_anchor an = XDG_POSITIONER_ANCHOR_NONE;
   enum xdg_positioner_gravity grav = XDG_POSITIONER_GRAVITY_NONE;

   if (invert)
     {
        if (sp->anchor == XDG_POSITIONER_ANCHOR_LEFT)
          an = XDG_POSITIONER_ANCHOR_RIGHT;
        else if (sp->anchor == XDG_POSITIONER_ANCHOR_RIGHT)
          an = XDG_POSITIONER_ANCHOR_LEFT;
        if (sp->gravity & XDG_POSITIONER_GRAVITY_LEFT)
          grav |= XDG_POSITIONER_GRAVITY_RIGHT;
        else if (sp->gravity & XDG_POSITIONER_GRAVITY_RIGHT)
          grav |= XDG_POSITIONER_GRAVITY_LEFT;
     }
   else
     {
        an = sp->anchor;
        grav = sp->gravity;
     }

   /* left edge */
   if (an == XDG_POSITIONER_ANCHOR_LEFT)
     x += sp->anchor_rect.x;
   /* right edge */
   else if (an == XDG_POSITIONER_ANCHOR_RIGHT)
     x += sp->anchor_rect.x + sp->anchor_rect.w;
   /* center */
   else
     x += sp->anchor_rect.x + (sp->anchor_rect.w / 2);

   /* flip left over anchor */
   if (grav & XDG_POSITIONER_GRAVITY_LEFT)
     x -= sp->size.w;
   /* center on anchor */
   else if (!(grav & XDG_POSITIONER_GRAVITY_RIGHT))
     x -= sp->size.w / 2;
   return x;
}

static int
_apply_positioner_y(int y, Shell_Positioner *sp, Eina_Bool invert)
{
   enum xdg_positioner_anchor an = XDG_POSITIONER_ANCHOR_NONE;
   enum xdg_positioner_gravity grav = XDG_POSITIONER_GRAVITY_NONE;

   if (invert)
     {
        if (sp->anchor == XDG_POSITIONER_ANCHOR_TOP)
          an = XDG_POSITIONER_ANCHOR_BOTTOM;
        else if (sp->anchor == XDG_POSITIONER_ANCHOR_BOTTOM)
          an = XDG_POSITIONER_ANCHOR_TOP;
        if (sp->gravity & XDG_POSITIONER_GRAVITY_TOP)
          grav |= XDG_POSITIONER_GRAVITY_BOTTOM;
        else if (sp->gravity & XDG_POSITIONER_GRAVITY_BOTTOM)
          grav |= XDG_POSITIONER_GRAVITY_TOP;
     }
   else
     {
        an = sp->anchor;
        grav = sp->gravity;
     }

   /* up edge */
   if (an == XDG_POSITIONER_ANCHOR_TOP)
     y += sp->anchor_rect.y;
   /* bottom edge */
   else if (an == XDG_POSITIONER_ANCHOR_BOTTOM)
     y += sp->anchor_rect.y + sp->anchor_rect.h;
   /* center */
   else
     y += sp->anchor_rect.y + (sp->anchor_rect.h / 2);

   /* flip up over anchor */
   if (grav & XDG_POSITIONER_GRAVITY_TOP)
     y -= sp->size.h;
   /* center on anchor */
   else if (!(grav & XDG_POSITIONER_GRAVITY_BOTTOM))
     y -= sp->size.h / 2;
   return y;
}

static Eina_Bool
_apply_positioner_slide(Comp_Surface *cs, Shell_Positioner *sp, int *x, int *y, int *w, int *h, int zx, int zy, int zw, int zh)
{
   int px, py, cx, cy;

   evas_object_geometry_get(cs->parent->obj, &px, &py, NULL, NULL);
   evas_object_geometry_get(cs->c->obj, &cx, &cy, NULL, NULL);
   px -= cx, py -= cy;
   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X) &&
       (!CONTAINS(zx, zy, zw, zh, *x, zy, *w, 1)))
     {
        int sx = *x;

        if (sp->gravity & XDG_POSITIONER_GRAVITY_LEFT)
          {
             if (*x + *w > zx + zw)
               sx = MAX(zx + zw - *w, px + sp->anchor_rect.x - *w);
             else if (*x < zx)
               sx = MIN(zx, px + sp->anchor_rect.x + sp->anchor_rect.w);
          }
        else if (sp->gravity & XDG_POSITIONER_GRAVITY_RIGHT)
          {
             if (*x < zx)
               sx = MIN(zx, *x + sp->anchor_rect.x + sp->anchor_rect.w);
             else if (*x + *w > zx + zw)
               sx = MAX(zx + zw - *w, px + sp->anchor_rect.x - *w);
          }
        if (CONTAINS(zx, zy, zw, zh, sx, zy, *w, 1))
          *x = sx;
     }
   if (!CONSTRAINED(*w, *h, *x, *y)) return EINA_TRUE;
   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y) &&
       (!CONTAINS(zx, zy, zw, zh, zx, *y, 1, *h)))
     {
        int sy = *y;

        if (sp->gravity & XDG_POSITIONER_GRAVITY_TOP)
          {
             if (*y + *h > zy + zh)
               sy = MAX(zy + zh - *h, py + sp->anchor_rect.y - *h);
             else if (*y < zy)
               sy = MIN(zy, py + sp->anchor_rect.y + sp->anchor_rect.h);
          }
        else if (sp->gravity & XDG_POSITIONER_GRAVITY_BOTTOM)
          {
             if (*y < zy)
               sy = MIN(zy, py + sp->anchor_rect.y + sp->anchor_rect.h);
             else if (*y + *h > zy + zh)
               sy = MAX(zy + zh - *h, py + sp->anchor_rect.y - *h);
          }
        if (CONTAINS(zx, zy, zw, zh, zx, sy, 1, *h))
          *y = sy;
     }
   return !CONSTRAINED(*w, *h, *x, *y);
}

static void
_apply_positioner(Comp_Surface *cs, Shell_Positioner *sp)
{
   int ix, iy, x, y, w = -1, h = -1, px, py, cx, cy;
   int zx, zy, zw, zh;
   /* apply base geometry:
    * coords are relative to parent
    */
   evas_object_geometry_get(cs->parent->obj, &px, &py, NULL, NULL);
   evas_object_geometry_get(cs->c->obj, &cx, &cy, NULL, NULL);
   px -= cx, py -= cy;
   ix = x = px + sp->offset.x;
   iy = y = py + sp->offset.y;

   if (sp->size.w && sp->size.h)
     {
        w = sp->size.w;
        h = sp->size.h;
     }

   /* apply policies in order:
    - anchor (add anchor_rect using anchor point)
    - gravity (perform flips if gravity is not right|bottom)
    - constrain (adjust if popup does not fit)
    */
   x = _apply_positioner_x(x, sp, 0);
   y = _apply_positioner_y(y, sp, 0);

   evas_object_geometry_get(cs->c->obj, &zx, &zy, &zw, &zh);

   if (!CONSTRAINED(w, h, x, y))
     {
        evas_object_move(cs->obj, zx + x, zy + y);
        if (w > 0) evas_object_resize(cs->obj, w, h);
        return;
     }

   /* assume smart placement:
    - flip
    - slide
    - resize
    */
   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X) &&
       (!CONTAINS(zx, zy, zw, zh, x, zy, w, 1)))
     {
        int fx;

        fx = _apply_positioner_x(ix, sp, 1);
        if (CONTAINS(zx, zy, zw, zh, fx, zy, w, 1))
          x = fx;
     }
   if (!CONSTRAINED(w, h, x, y))
     {
        evas_object_move(cs->obj, zx + x, zy + y);
        if (w > 0) evas_object_resize(cs->obj, w, h);
        return;
     }
   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y) &&
       (!CONTAINS(zx, zy, zw, zh, zx, y, 1, h)))
     {
        int fy;

        fy = _apply_positioner_y(iy, sp, 1);
        if (CONTAINS(zx, zy, zw, zh, zx, fy, 1, h))
          y = fy;
     }
   if (!CONSTRAINED(w, h, x, y))
     {
        evas_object_move(cs->obj, zx + x, zy + y);
        if (w > 0) evas_object_resize(cs->obj, w, h);
        return;
     }
   if (_apply_positioner_slide(cs, sp, &x, &y, &w, &h, zx, zy, zw, zh))
     {
        evas_object_move(cs->obj, zx + x, zy + y);
        if (w > 0) evas_object_resize(cs->obj, w, h);
        return;
     }

   if (!CONSTRAINED(w, h, x, y))
     {
        evas_object_move(cs->obj, zx + x, zy + y);
        if (w > 0) evas_object_resize(cs->obj, w, h);
        return;
     }

   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X) &&
       (!CONTAINS(zx, zy, zw, zh, x, zy, w, 1)))
     {
        w = zx + zw - x;
        if (!CONSTRAINED(w, h, x, y))
          {
             evas_object_move(cs->obj, zx + x, zy + y);
             if (w > 0) evas_object_resize(cs->obj, w, h);
             return;
          }
     }
   if ((sp->constrain & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y) &&
       (!CONTAINS(zx, zy, zw, zh, zx, y, 1, h)))
     {
        h = zy + zh - y;
     }
   evas_object_move(cs->obj, zx + x, zy + y);
   if (w > 0)
     {
        if (!cs->hint_set_weight)
          evas_object_size_hint_weight_set(cs->obj, 0, 0);
        evas_object_resize(cs->obj, w, h);
     }
}

static const struct wl_data_offer_interface data_device_offer_interface =
{
   data_device_offer_accept,
   data_device_offer_receive,
   resource_destroy,
   data_device_offer_finish,
   data_device_offer_set_actions,
};

static void
data_device_offer_create(Comp_Data_Device_Source *source, struct wl_resource *resource)
{
   Comp_Data_Device_Offer *off;
   Eina_List *l;
   Eina_Stringshare *type;

   off = calloc(1, sizeof(Comp_Data_Device_Offer));
   off->res = wl_resource_create(wl_resource_get_client(resource), &wl_data_offer_interface,
     wl_resource_get_version(resource), 0);
   wl_resource_set_implementation(off->res, &data_device_offer_interface, off, data_device_offer_impl_destroy);
   off->source = source;
   source->offer = off;
   off->proxy = source->proxy;
   wl_data_device_send_data_offer(resource, off->res);
   EINA_LIST_FOREACH(source->mime_types, l, type)
     wl_data_offer_send_offer(off->res, type);
}

static void
comp_buffer_state_alloc(Comp_Buffer_State *cbs)
{
   cbs->damages = tiler_new();
   cbs->opaque = tiler_new();
   cbs->input = tiler_new();
}

static void
comp_buffer_state_clear(Comp_Buffer_State *cbs)
{
   eina_tiler_free(cbs->damages);
   eina_tiler_free(cbs->opaque);
   eina_tiler_free(cbs->input);
   while (cbs->frames)
     wl_resource_destroy(eina_list_data_get(cbs->frames));
}

static void
comp_seat_send_modifiers(Comp_Seat *s, struct wl_resource *res, uint32_t serial)
{
   wl_keyboard_send_modifiers(res, serial, s->kbd.mods.depressed,
                              s->kbd.mods.latched,
                              s->kbd.mods.locked,
                              s->kbd.mods.group);
   s->kbd.mods.changed = 0;
}

static Eina_Bool
data_device_selection_read(void *d, Ecore_Fd_Handler *fdh)
{
   Comp_Data_Device_Source *ds = d;
   int len = -1;

   do
     {
        unsigned char buf[2048];
        int fd;

        fd = ecore_main_fd_handler_fd_get(fdh);
        if (fd < 0) break;
        len = read(fd, buf, sizeof(buf));
        if (len > 0)
          {
             if (!ds->reader_data)
               ds->reader_data = eina_binbuf_new();
             eina_binbuf_append_length(ds->reader_data, buf, len);
             return ECORE_CALLBACK_RENEW;
          }
     } while (0);
   fdh_del(fdh);
   ds->reader = NULL;
   if (len < 0)
     {
        eina_binbuf_free(ds->reader_data);
        ds->reader_data = NULL;
     }
   return ECORE_CALLBACK_RENEW;
}

static void
comp_seat_kbd_data_device_enter(Comp_Seat *s)
{
   struct wl_resource *res;
   Comp_Data_Device_Source *ds = s->selection_source;
   int fd[2];

   if (!s->kbd.enter) return;
   res = data_device_find(s, s->kbd.enter->res);
   if (!res) return;
   if (ds)
     {
        data_device_offer_create(ds, res);
        ds->offer->type = COMP_DATA_DEVICE_OFFER_TYPE_CLIPBOARD;
     }
   if (ds)
     wl_data_device_send_selection(res, ds->offer ? ds->offer->res : NULL);
   if ((!ds) || (!ds->mime_types)) return;
   EINA_SAFETY_ON_TRUE_RETURN(pipe2(fd, O_CLOEXEC) < 0);
   wl_data_source_send_send(ds->res, eina_list_data_get(ds->mime_types), fd[1]);
   close(fd[1]);
   ds->reader = ecore_main_fd_handler_file_add(fd[0], ECORE_FD_READ | ECORE_FD_ERROR,
     data_device_selection_read, ds, NULL, NULL);
}

static void
comp_seats_redo_enter(Comp *c, Comp_Surface *cs)
{
   Comp_Seat *s;
   uint32_t serial;
   struct wl_client *client = NULL;

   serial = wl_display_next_serial(c->display);
   if (cs)
     client = wl_resource_get_client(cs->res);
   EINA_INLIST_FOREACH(c->seats, s)
     {
        Eina_List *l, *ll;
        struct wl_resource *res;
        Eina_Bool same = s->kbd.enter == cs;

        if (c->active_surface && s->kbd.enter && (!same))
          {
             l = seat_kbd_active_resources_get(s);
             EINA_LIST_FOREACH(l, ll, res)
               wl_keyboard_send_leave(res, serial, s->kbd.enter->res);
          }
        s->active_client = client;
        if (cs)
          {
             l = seat_kbd_active_resources_get(s);
             EINA_LIST_FOREACH(l, ll, res)
               {
                  if (!same)
                    wl_keyboard_send_enter(res, serial, cs->res, &s->kbd.keys);
                  comp_seat_send_modifiers(s, res, serial);
               }
          }
        s->kbd.enter = cs;
        if (s->kbd.enter && s->selection_source && (!same))
          comp_seat_kbd_data_device_enter(s);
     }
   c->active_surface = cs;
}

static void shell_surface_send_configure(Comp_Surface *cs);

static void
shell_surface_deactivate_recurse(Comp_Surface *cs)
{
   Comp_Surface *ccs;

   EINA_INLIST_FOREACH(cs->children, ccs)
     {
        if (!ccs->shell.popup) continue;
        shell_surface_deactivate_recurse(ccs);
        if (ccs->shell.grabs) evas_object_hide(ccs->obj);
     }
}

static void
shell_surface_activate_recurse(Comp_Surface *cs)
{
   Comp_Surface *lcs, *parent = cs->parent;
   Eina_List *parents = NULL;
   Eina_Inlist *i;

   if (parent)
     {
        /* remove focus from parents */
        while (parent)
          {
             parents = eina_list_append(parents, parent);
             if (!parent->shell.popup) break;
             parent = parent->parent;
          }
     }
   EINA_INLIST_FOREACH_SAFE(cs->c->surfaces, i, lcs)
     if (lcs->shell.activated && (lcs != cs))
       {
          if ((!parents) || (!eina_list_data_find(parents, lcs)))
            {
               lcs->shell.activated = 0;
               shell_surface_send_configure(lcs);
               cs->c->surfaces = eina_inlist_promote(cs->c->surfaces, EINA_INLIST_GET(lcs));
            }
       }
   eina_list_free(parents);
}

static void
shell_surface_minmax_update(Comp_Surface *cs)
{
   Eina_Size2D sz;

   if (!cs) return;
   if (!cs->c->minmax) return;
   if (cs->extracted) return;
   if (cs->parent) return;
   sz = efl_gfx_hint_size_restricted_min_get(cs->obj);
   efl_gfx_hint_size_restricted_min_set(cs->c->obj, sz);
   sz = efl_gfx_hint_size_restricted_max_get(cs->obj);
   if (!sz.w) sz.w = -1;
   if (!sz.h) sz.h = -1;
   efl_gfx_hint_size_restricted_max_set(cs->c->obj, sz);
}

static void
shell_surface_aspect_update(Comp_Surface *cs)
{
   Evas_Aspect_Control aspect;
   int w, h;

   if (!cs) return;
   if (!cs->c->aspect) return;
   if (cs->extracted) return;
   if (cs->parent) return;
   evas_object_size_hint_aspect_get(cs->obj, &aspect, &w, &h);
   evas_object_size_hint_aspect_set(cs->c->obj, aspect, w, h);
}

static void
shell_surface_send_configure(Comp_Surface *cs)
{
   uint32_t serial, *s;
   struct wl_array states;
   int w, h;

   cs->shell.new = 0;
   if (cs->shell.popup)
     {
        int x, y, px, py;
        evas_object_geometry_get(cs->obj, &x, &y, &w, &h);
        evas_object_geometry_get(cs->parent->obj, &px, &py, NULL, NULL);
        serial = wl_display_next_serial(cs->c->display);
        xdg_popup_send_configure(cs->role, x - px, y - py, w, h);
        xdg_surface_send_configure(cs->shell.surface, serial);
        return;
     }
   wl_array_init(&states);
   s = wl_array_add(&states, sizeof(uint32_t));
   *s = XDG_TOPLEVEL_STATE_FULLSCREEN;
   if (cs->shell.activated)
     {
        s = wl_array_add(&states, sizeof(uint32_t));
        *s = XDG_TOPLEVEL_STATE_ACTIVATED;
        if (!cs->extracted)
          evas_object_raise(cs->obj);
        if (cs->parent)
          cs->parent->children = eina_inlist_demote(cs->parent->children, EINA_INLIST_GET(cs));
        else
          cs->c->surfaces = eina_inlist_demote(cs->c->surfaces, EINA_INLIST_GET(cs));
        shell_surface_activate_recurse(cs);
     }
   serial = wl_display_next_serial(cs->c->display);
   if (cs->extracted)
     evas_object_geometry_get(cs->obj, NULL, NULL, &w, &h);
   else
     evas_object_geometry_get(cs->c->clip, NULL, NULL, &w, &h);
   xdg_toplevel_send_configure(cs->role, w, h, &states);
   xdg_surface_send_configure(cs->shell.surface, serial);
   wl_array_release(&states);
   if (cs->shell.activated)
     {
        Comp_Surface *ccs;

        /* apply activation to already-mapped surface */
        if (cs->mapped)
          {
             comp_seats_redo_enter(cs->c, cs);
             shell_surface_aspect_update(cs);
             shell_surface_minmax_update(cs);
          }
        EINA_INLIST_FOREACH(cs->children, ccs)
          if (ccs->shell.surface && ccs->role && ccs->shell.popup)
            ccs->shell.activated = cs->shell.activated;
     }
   else
     shell_surface_deactivate_recurse(cs);
}

static void
shell_surface_init(Comp_Surface *cs)
{
   /* update activate status: newest window is always activated */
   Comp_Surface *parent = cs->parent;

   if (cs->c->active_surface && parent && (!parent->shell.activated))
     {
        /* child windows/popups cannot steal focus from toplevel */
        shell_surface_send_configure(cs);
        return;
     }

   cs->shell.activated = 1;
   shell_surface_send_configure(cs);
}

static void
comp_surface_output_leave(Comp_Surface *cs)
{
   Eina_List *l;
   struct wl_resource *res;

   EINA_LIST_FOREACH(cs->c->output_resources, l, res)
     if (wl_resource_get_client(res) == wl_resource_get_client(cs->res))
       wl_surface_send_leave(cs->res, res);
}

static void
comp_surface_output_enter(Comp_Surface *cs)
{
   Eina_List *l;
   struct wl_resource *res;

   EINA_LIST_FOREACH(cs->c->output_resources, l, res)
     if (wl_resource_get_client(res) == wl_resource_get_client(cs->res))
       wl_surface_send_enter(cs->res, res);
}

static void
comp_surface_buffer_detach(Comp_Buffer **pbuffer)
{
   Comp_Buffer *buffer;

   buffer = *pbuffer;
   if (!buffer) return;
   if (buffer->post_renders)
     fprintf(stderr, "CRASH %u\n", wl_resource_get_id(buffer->res));
   eina_list_free(buffer->renders);
   wl_list_remove(&buffer->destroy_listener.link);
   //if (buffer->dbg) fprintf(stderr, "BUFFER(%d) RELEASE\n", wl_resource_get_id(buffer->res));
   if (buffer->pool) wl_shm_pool_unref(buffer->pool);
   wl_buffer_send_release(buffer->res);
   free(buffer);
   *pbuffer = NULL;
}

static void
comp_surface_buffer_post_render(Comp_Surface *cs)
{
   double t = ecore_loop_time_get() - cs->c->wayland_time_base;

   //if (cs->subsurface)
     //fprintf(stderr, "FRAME(%d)\n", wl_resource_get_id(cs->res));
   while (cs->frames)
     {
        struct wl_resource *frame = eina_list_data_get(cs->frames);

        wl_callback_send_done(frame, t * 1000);
        wl_resource_destroy(frame);
     }
}

static void
comp_surface_pixels_get(void *data, Eo *obj)
{
   Comp_Surface *cs = data;
   Comp_Buffer *buffer;

   evas_object_image_pixels_dirty_set(obj, 0);
   evas_object_image_pixels_get_callback_set(obj, NULL, NULL);
   buffer = cs->buffer[!cs->render_queue];
   //if (cs->cursor || (buffer->w == 32)) fprintf(stderr, "RENDER(%d) %dx%d\n", wl_resource_get_id(buffer->res), buffer->w, buffer->h);
   evas_object_image_size_set(obj, buffer->w, buffer->h);
   evas_object_image_data_set(obj, wl_shm_buffer_get_data(buffer->shm_buffer));
}

static void
comp_surface_commit_image_state(Comp_Surface *cs, Comp_Buffer *buffer, Eo *o)
{
   if ((!buffer->renders) || (!eina_list_data_find(buffer->renders, evas_object_evas_get(o))))
     buffer->renders = eina_list_append(buffer->renders, evas_object_evas_get(o));
   evas_object_image_pixels_dirty_set(o, 1);

   if (buffer->shm_buffer)
     {
        //if (cs->subsurface)
          //fprintf(stderr, "SET CB\n");
        evas_object_image_pixels_get_callback_set(o, comp_surface_pixels_get, cs);
        buffer->pool = wl_shm_buffer_ref_pool(buffer->shm_buffer);
     }
   else
     {
        Evas_Native_Surface ns;

        evas_object_image_pixels_get_callback_set(o, NULL, NULL);
        if (buffer->dmabuf_buffer)
          {
             ns.type = EVAS_NATIVE_SURFACE_WL_DMABUF;
             ns.version = EVAS_NATIVE_SURFACE_VERSION;

             ns.data.wl_dmabuf.attr = &buffer->dmabuf_buffer->attributes;
             ns.data.wl_dmabuf.resource = buffer->res;
             ns.data.wl_dmabuf.scanout.handler = NULL;
             ns.data.wl_dmabuf.scanout.data = NULL;
          }
        else
          {
             ns.type = EVAS_NATIVE_SURFACE_WL;
             ns.version = EVAS_NATIVE_SURFACE_VERSION;
             ns.data.wl.legacy_buffer = buffer->res;
          }
        evas_object_image_native_surface_set(o, &ns);
     }
}

static void
shell_surface_reset(Comp_Surface *cs)
{
   EINA_RECTANGLE_SET(&cs->shell.geom, 0, 0, 0, 0);
   eina_stringshare_replace(&cs->shell.title, NULL);
   eina_stringshare_replace(&cs->shell.app_id, NULL);
   cs->shell.activated = 0;
}

static void
comp_surface_commit_state(Comp_Surface *cs, Comp_Buffer_State *state)
{
   int x, y;
   Eina_List *l;
   Eo *o;
   Comp_Buffer *buffer = NULL;
   Eina_Bool newly_new = EINA_FALSE;

   if (state->attach)
     {
        comp_surface_buffer_detach(&cs->buffer[0]);
        buffer = cs->buffer[0] = state->buffer;

        if (buffer)
          {
             //if (cs->subsurface)
               //fprintf(stderr, "BUFFER(%d) COMMIT %d\n", wl_resource_get_id(buffer->res), wl_resource_get_id(cs->res));
             if ((!cs->c->rendering) && (!cs->post_render_queue) &&
               ((!cs->buffer[1]) || (!cs->buffer[1]->post_renders)))
               comp_surface_buffer_detach(&cs->buffer[1]);
          }
        else
          {
             if (!cs->extracted)
               evas_object_hide(cs->obj);
             EINA_LIST_FOREACH(cs->proxies, l, o)
               evas_object_hide(o);
             if (cs->shell.surface)
               {
                  cs->shell.new = 1;
                  newly_new = EINA_TRUE;
                  shell_surface_reset(cs);
               }
          }
     }
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);

   if (buffer && (!cs->mapped))
     {
        if (cs->role && (!cs->extracted))
          evas_object_show(cs->obj);
        /* apply activation to activated surface on map */
        if (cs->role && cs->shell.surface && cs->shell.activated && (!cs->shell.popup))
          {
             comp_seats_redo_enter(cs->c, cs);
             shell_surface_aspect_update(cs);
             shell_surface_minmax_update(cs);
          }
     }

   if (state->attach && state->buffer)
     {
        evas_object_move(cs->img, x + buffer->x, y + buffer->y);
        evas_object_resize(cs->obj, buffer->w, buffer->h);
     }
   else if (cs->shell.new && (!newly_new))
     shell_surface_init(cs);

   state->attach = 0;
   state->buffer = NULL;

   cs->frames = eina_list_merge(cs->frames, state->frames);
   state->frames = NULL;

   if (eina_tiler_empty(state->damages))
     {
        comp_surface_buffer_detach(&buffer);
        comp_surface_buffer_post_render(cs);
        if (!cs->post_render_queue)
          {
             evas_object_image_pixels_dirty_set(cs->img, 0);
             EINA_LIST_FOREACH(cs->proxies, l, o)
               evas_object_image_pixels_dirty_set(o, 0);
          }
     }
   else if (buffer)
     {
        Eina_Iterator *it;
        Eina_Rectangle *rect;

        comp_surface_commit_image_state(cs, buffer, cs->img);
        EINA_LIST_FOREACH(cs->proxies, l, o)
          comp_surface_commit_image_state(cs, buffer, o);

        it = eina_tiler_iterator_new(state->damages);
        EINA_ITERATOR_FOREACH(it, rect)
          {
               //if (cs->subsurface) fprintf(stderr, "BUFFER(%d) DAMAGE %d\n", wl_resource_get_id(buffer->res), wl_resource_get_id(cs->res));
             evas_object_image_data_update_add(cs->img, rect->x, rect->y, rect->w, rect->h);
             EINA_LIST_FOREACH(cs->proxies, l, o)
               evas_object_image_data_update_add(o, rect->x, rect->y, rect->w, rect->h);
          }
        eina_iterator_free(it);
        if (!cs->render_queue)
          cs->c->render_queue = eina_list_append(cs->c->render_queue, cs);
        cs->render_queue = 1;
     }
   eina_tiler_clear(state->damages);

   if (state->set_opaque && (!eina_tiler_equal(cs->opaque, state->opaque)))
     {
        array_clear(&cs->opaque_rects);
        if (!eina_tiler_empty(state->opaque))
          /* FIXME: proxied opaque regions */
          {
             Eina_Iterator *it;
             Eina_Rectangle *rect;
             Eo *r;

             it = eina_tiler_iterator_new(state->opaque);
             cs->opaque_rects  = eina_array_new(1);
             EINA_ITERATOR_FOREACH(it, rect)
               {
                  r = evas_object_rectangle_add(cs->c->evas);
                  evas_object_name_set(r, "opaque_rect");
                  evas_object_pass_events_set(r, 1);
                  evas_object_color_set(r, 0, 0, 0, 255);
                  evas_object_smart_member_add(r, cs->obj);
                  evas_object_geometry_set(r, x + rect->x, y + rect->y, rect->w, rect->h);
                  evas_object_stack_below(r, cs->img);
                  evas_object_show(r);
                  eina_array_push(cs->opaque_rects, r);
               }
             /* FIXME: maybe use image border here */

             eina_iterator_free(it);
          }
        PTR_SWAP(&cs->opaque, &state->opaque);
     }
   eina_tiler_clear(state->opaque);
   state->set_opaque = 0;

   if (state->set_input)
     {
        if (eina_tiler_empty(state->input))
          {
             array_clear(&cs->input_rects);
             evas_object_pass_events_set(cs->img, 0);
             evas_object_pointer_mode_set(cs->img, EVAS_OBJECT_POINTER_MODE_NOGRAB);
          }
        else if (!eina_tiler_equal(cs->input, state->input))
          {
             Eina_Iterator *it;
             Eina_Rectangle *rect;
             Eo *r;

             array_clear(&cs->input_rects);
             it = eina_tiler_iterator_new(state->input);
             cs->input_rects  = eina_array_new(1);
             EINA_ITERATOR_FOREACH(it, rect)
               {
                  r = evas_object_rectangle_add(cs->c->evas);
                  evas_object_name_set(r, "input_rect");
                  evas_object_color_set(r, 0, 0, 0, 0);
                  evas_object_smart_member_add(r, cs->obj);
                  evas_object_geometry_set(r, x + rect->x, y + rect->y, rect->w, rect->h);
                  evas_object_stack_above(r, cs->img);
                  evas_object_pointer_mode_set(r, EVAS_OBJECT_POINTER_MODE_NOGRAB);
                  evas_object_show(r);
                  evas_object_show(r);
                  eina_array_push(cs->input_rects, r);
               }
             evas_object_pass_events_set(cs->img, 1);
             eina_iterator_free(it);
          }
        PTR_SWAP(&cs->input, &state->input);
     }
   eina_tiler_clear(state->input);
   state->set_input = 0;

   if (cs->pending_subsurfaces)
     {
        cs->subsurfaces = eina_list_free(cs->subsurfaces);
        PTR_SWAP(&cs->subsurfaces, &cs->pending_subsurfaces);
     }
   if (cs->subsurface)
     {
        if (cs->subsurface->set_offset)
          {
             cs->subsurface->offset.x = cs->subsurface->pending_offset.x;
             cs->subsurface->offset.y = cs->subsurface->pending_offset.y;
             cs->subsurface->pending_offset.x = cs->subsurface->pending_offset.y = 0;
             cs->subsurface->set_offset = 0;
             evas_object_geometry_get(cs->parent->obj, &x, &y, NULL, NULL);
             evas_object_move(cs->obj, x + cs->subsurface->offset.x, y + cs->subsurface->offset.y);
          }
     }
}

static void
comp_surface_destroy(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   if (cs)
     {
        if (cs->post_render_queue)
          {
             cs->dead = 1;
             evas_object_pass_events_set(cs->obj, 1);
             return;
          }
        cs->res = NULL;
     }
   wl_resource_destroy(resource);
}

static void
comp_surface_buffer_destroy(struct wl_listener *listener, void *data EINA_UNUSED)
{
   Comp_Buffer *buffer;
   Comp_Surface *cs;

   buffer = container_of(listener, Comp_Buffer, destroy_listener);
   cs = buffer->cs;
   if (cs)
     {
        if (cs->buffer[0] == buffer) cs->buffer[0] = NULL;
        else if (cs->buffer[1] == buffer) cs->buffer[1] = NULL;
        else if (cs->pending.buffer == buffer) cs->pending.buffer = NULL;
        else if (cs->subsurface)
          {
             if (cs->subsurface->cache.buffer == buffer) cs->subsurface->cache.buffer = NULL;
          }
     }
   free(buffer);
}

static void
comp_surface_attach(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *buffer_resource, int32_t x, int32_t y)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Comp_Buffer *buffer;
   struct wl_shm_buffer *shmbuff;
   struct linux_dmabuf_buffer *dmabuf;

   if (cs->shell.new)
     {
        wl_resource_post_error(cs->shell.surface, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
                               "buffer attached/committed before configure");
        return;
     }

   comp_surface_buffer_detach(&cs->pending.buffer);
   cs->pending.attach = 1;
   if (!buffer_resource) return;

   buffer = calloc(1, sizeof(Comp_Buffer));
   if (cs->subsurface)
     {
        //fprintf(stderr, "BUFFER(%d) HELD BY %d\n", wl_resource_get_id(buffer_resource), wl_resource_get_id(resource));
        buffer->dbg = 1;
     }
   buffer->cs = cs;
   buffer->x = x;
   buffer->y = y;
   shmbuff = wl_shm_buffer_get(buffer_resource);
   dmabuf = linux_dmabuf_buffer_get(buffer_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 (cs->c->gl)
     {
        cs->c->glapi->evasglQueryWaylandBuffer(cs->c->gl, buffer_resource, EGL_WIDTH, &buffer->w);
        cs->c->glapi->evasglQueryWaylandBuffer(cs->c->gl, buffer_resource, EGL_HEIGHT, &buffer->h);
     }
   buffer->shm_buffer = shmbuff;
   buffer->dmabuf_buffer = dmabuf;

   buffer->res = buffer_resource;
   buffer->destroy_listener.notify = comp_surface_buffer_destroy;
   wl_resource_add_destroy_listener(buffer_resource, &buffer->destroy_listener);
   cs->pending.buffer = buffer;
}

static void
comp_surface_damage_buffer(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   eina_tiler_rect_add(cs->pending.damages, &(Eina_Rectangle){x, y, w, h});
}

/*
 * 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
comp_surface_damage(struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   comp_surface_damage_buffer(client, resource, x, y, w, h);
}

static void
comp_surface_frame_impl_destroy(struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   if (!cs) return;
   if (cs->frames)
     cs->frames = eina_list_remove(cs->frames, resource);
   if (cs->pending.frames)
     cs->pending.frames = eina_list_remove(cs->pending.frames, resource);

   if (cs->subsurface && cs->subsurface->cached)
     cs->subsurface->cache.frames = eina_list_remove(cs->subsurface->cache.frames, resource);
}

static void
comp_surface_frame(struct wl_client *client, struct wl_resource *resource, uint32_t callback)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   struct wl_resource *res;

   res = wl_resource_create(client, &wl_callback_interface, 1, callback);
   wl_resource_set_implementation(res, NULL, cs, comp_surface_frame_impl_destroy);
   cs->pending.frames = eina_list_append(cs->pending.frames, res);
}

static void
comp_surface_set_opaque_region(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region_resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   cs->pending.set_opaque = 1;
   eina_tiler_clear(cs->pending.opaque);
   if (region_resource)
     eina_tiler_union(cs->pending.opaque, wl_resource_get_user_data(region_resource));
}

static void
comp_surface_set_input_region(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *region_resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   if (cs->cursor) return;

   cs->pending.set_input = 1;
   eina_tiler_clear(cs->pending.input);
   if (region_resource)
     eina_tiler_union(cs->pending.input, wl_resource_get_user_data(region_resource));
}

static void
subcomp_subsurface_cache_merge(Comp_Subsurface *css)
{
   //fprintf(stderr, "CACHE MERGE\n");
   css->cached = 1;
   if (css->cache.frames || css->surface->pending.frames)
     css->cache.frames = eina_list_merge(css->cache.frames, css->surface->pending.frames);
   css->surface->pending.frames = NULL;
   eina_tiler_union(css->cache.damages, css->surface->pending.damages);
   eina_tiler_clear(css->surface->pending.damages);
   css->cache.set_input = css->surface->pending.set_input;
   if (css->surface->pending.set_input)
     {
        eina_tiler_clear(css->cache.input);
        PTR_SWAP(&css->cache.input, &css->surface->pending.input);
     }
   css->cache.set_opaque = css->surface->pending.set_opaque;
   if (css->surface->pending.set_opaque)
     {
        eina_tiler_clear(css->cache.opaque);
        PTR_SWAP(&css->cache.opaque, &css->surface->pending.opaque);
     }
   css->surface->pending.set_input = 0;
   css->surface->pending.set_opaque = 0;
   if (!css->surface->pending.attach) return;
   css->cache.attach = 1;
   comp_surface_buffer_detach(&css->cache.buffer);
   PTR_SWAP(&css->cache.buffer, &css->surface->pending.buffer);
   css->surface->pending.attach = 0;
}

static void
comp_surface_commit(struct wl_client *client, struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Comp_Subsurface *css;
   Eina_List *l;
   Comp_Buffer_State *cbs = &cs->pending;

   if (cs->shell.popup && (!cs->parent))
     {
        wl_resource_post_error(cs->shell.surface, XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT,
                               "popup surface has no parent");
        return;
     }

   cs->commit = 1;
   if (cs->subsurface)
     {
        Comp_Surface *parent;

        css = cs->subsurface;
        parent = cs->parent;
        if ((!parent->commit) && css->sync)
          {
             subcomp_subsurface_cache_merge(css);
             return;
          }
        while (parent && (!parent->commit) && parent->subsurface)
          {
             Comp_Subsurface *pss = parent->subsurface;

             if (pss->sync)
               {
                  subcomp_subsurface_cache_merge(css);
                  return;
               }
             parent = parent->parent;
          }


        subcomp_subsurface_cache_merge(css);
        cbs = &css->cache;
     }

   comp_surface_commit_state(cs, cbs);
   EINA_LIST_FOREACH(cs->subsurfaces, l, css)
     if (css->sync) comp_surface_commit(client, css->surface->res);
   cs->commit = 0;
}

static void
comp_surface_set_buffer_transform(struct wl_client *client, struct wl_resource *resource, int32_t transform)
{
   //Comp_Surface *cs = wl_resource_get_user_data(resource);
}

static void
comp_surface_set_buffer_scale(struct wl_client *client, struct wl_resource *resource, int32_t scale)
{
   //Comp_Surface *cs = wl_resource_get_user_data(resource);
}

static const struct wl_surface_interface comp_surface_interface =
{
   comp_surface_destroy,
   comp_surface_attach,
   comp_surface_damage,
   comp_surface_frame,
   comp_surface_set_opaque_region,
   comp_surface_set_input_region,
   comp_surface_commit,
   comp_surface_set_buffer_transform,
   comp_surface_set_buffer_scale,
   comp_surface_damage_buffer
};

static void
comp_surface_impl_destroy(struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Eina_List *subsurfaces;
   Comp_Subsurface *css;
   Comp_Seat *s;
   struct wl_client *client = wl_resource_get_client(resource);

   EINA_INLIST_FOREACH(cs->c->seats, s)
     {
        if (s->kbd.enter == cs) s->kbd.enter = NULL;
        if (s->ptr.enter == cs) s->ptr.enter = NULL;
        if (s->ptr.cursor.surface == cs)
          {
             if (s->ptr.in)
               {
                  const Eina_List *l;
                  Eo *dev;
                  Ecore_Evas *ee = ecore_evas_ecore_evas_get(s->c->evas);
                  EINA_LIST_FOREACH(evas_device_list(s->c->evas, s->dev), l, dev)
                    if (evas_device_class_get(dev) == EVAS_DEVICE_CLASS_MOUSE)
                      ecore_evas_cursor_device_unset(ee, dev);
               }
             s->ptr.cursor.surface = NULL;
          }
        if (s->drag.surface == cs) s->drag.surface = NULL;
     }
   eina_hash_list_remove(cs->c->client_surfaces, &client, cs);
   if (cs->render_queue)
     cs->c->render_queue = eina_list_remove(cs->c->render_queue, cs);
   subsurfaces = cs->pending_subsurfaces ?: cs->subsurfaces;
   EINA_LIST_FREE(subsurfaces, css)
     {
        evas_object_hide(css->surface->obj);
        comp_surface_reparent(css->surface, NULL);
     }
   if (cs->pending_subsurfaces) eina_list_free(cs->subsurfaces);
   cs->pending_subsurfaces = cs->subsurfaces = NULL;
   comp_surface_buffer_detach(&cs->buffer[0]);
   cs->res = NULL;
   if (cs->post_render_queue && (!cs->dead))
     {
        Eina_List *l;
        Eo *o;

        cs->dead = 1;
        evas_object_hide(cs->obj);
        EINA_LIST_FOREACH(cs->proxies, l, o)
          evas_object_hide(o);
     }
   else
     {
        comp_surface_buffer_detach(&cs->buffer[1]);
        if (!cs->dead) evas_object_del(cs->obj);
     }
}


static inline Eina_Bool
comp_surface_check_grab(Comp_Surface *cs, Comp_Seat *s)
{
   Comp_Surface *parent;
   if (!s->grab) return EINA_TRUE;
   if (cs == s->grab) return EINA_TRUE;
   parent = s->grab->parent;
   while (parent)
     {
        if (cs == parent) return EINA_TRUE;
        parent = parent->parent;
     }
   return EINA_FALSE;
}

static void
comp_surface_input_event(Eina_Inlist **list, uint32_t id, uint32_t serial, uint32_t time, Eina_Bool up)
{
   Input_Sequence *ev;

   if (up)
     {
        EINA_INLIST_FOREACH(*list, ev)
          if (ev->id == id)
            {
               ev->up_serial = serial;
               ev->up_time = time;
               return;
            }
        return;
     }
   ev = calloc(1, sizeof(Input_Sequence));
   ev->id = id;
   ev->down_serial = serial;
   ev->down_time = time;
   *list = eina_inlist_append(*list, EINA_INLIST_GET(ev));
}

static void
comp_surface_send_data_device_enter(Comp_Surface *cs, Comp_Seat *s)
{
   struct wl_resource *offer = NULL;
   int x, y, cx, cy;
   uint32_t serial;
   struct wl_resource *res = data_device_find(s, cs->res);


   if (!res) return;
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);
   if (s->drag.tch)
     cx = s->tch.pos.x, cy = s->tch.pos.y;
   else
     cx = s->ptr.pos.x, cy = s->ptr.pos.y;
   if (s->drag.source)
     {
        data_device_offer_create(s->drag.source, res);
        s->drag.source->offer->type = COMP_DATA_DEVICE_OFFER_TYPE_DND;
        offer = s->drag.source->offer->res;
     }
   s->drag.enter = cs;
   s->ptr.enter_serial = serial = wl_display_next_serial(cs->c->display);
   wl_data_device_send_enter(res, serial, cs->res,
       wl_fixed_from_int(cx - x), wl_fixed_from_int(cy - y), offer);
}

static Eina_Bool
comp_surface_send_pointer_enter(Comp_Surface *cs, Comp_Seat *s, int cx, int cy)
{
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t serial;
   int x, y;

   if (s->ptr.enter && (cs != s->grab)) return EINA_FALSE;
   if (!comp_surface_check_grab(cs, s)) return EINA_FALSE;
   s->ptr.enter = cs;
   if (cs->dead) return EINA_FALSE;
   if (s->drag.res && (!s->drag.tch))
     {
        comp_surface_send_data_device_enter(cs, s);
        return EINA_TRUE;
     }
   l = seat_ptr_resources_get(s, wl_resource_get_client(cs->res));
   if (!l) return EINA_FALSE;
   s->ptr.enter_serial = serial = wl_display_next_serial(cs->c->display);
   //fprintf(stderr, "ENTER %s\n", cs->shell.popup ? "POPUP" : "TOPLEVEL");
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);
   EINA_LIST_FOREACH(l, ll, res)
     wl_pointer_send_enter(res, serial, cs->res,
       wl_fixed_from_int(cx - x), wl_fixed_from_int(cy - y));
   return EINA_TRUE;
}

static void
comp_surface_mouse_in(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_In *ev = event_info;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   s = seat_find(data, ev->dev);
   if (comp_surface_send_pointer_enter(data, s, ev->canvas.x, ev->canvas.y))
     {
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
     }
}

static void
comp_surface_send_data_device_leave(Comp_Surface *cs, Comp_Seat *s)
{
   struct wl_resource *res = data_device_find(s, cs->res);
   if (!res) return;
   if (s->drag.source)
     {
        s->drag.source->offer->source = NULL;
        s->drag.source->offer = NULL;
     }
   s->drag.enter = NULL;
   wl_data_device_send_leave(res);
}

static Eina_Bool
comp_surface_send_pointer_leave(Comp_Surface *cs, Comp_Seat *s)
{
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t serial;

   if (s->ptr.enter != cs) return EINA_FALSE;
   if (!comp_surface_check_grab(cs, s)) return EINA_FALSE;
   s->ptr.enter = NULL;
   if (cs->dead) return EINA_FALSE;
   if (s->drag.res)
     {
        comp_surface_send_data_device_leave(cs, s);
        return EINA_TRUE;
     }
   l = seat_ptr_resources_get(s, wl_resource_get_client(cs->res));
   if (!l) return EINA_FALSE;
   serial = wl_display_next_serial(cs->c->display);
   //fprintf(stderr, "LEAVE %s\n", cs->shell.popup ? "POPUP" : "TOPLEVEL");
   EINA_LIST_FOREACH(l, ll, res)
     wl_pointer_send_leave(res, serial, cs->res);
   return EINA_TRUE;
}

static void
comp_surface_mouse_out(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Out *ev = event_info;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   s = seat_find(data, ev->dev);
   if (comp_surface_send_pointer_leave(data, s))
     {
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
     }
}

static Eina_Bool
comp_surface_mouse_button(Comp_Surface *cs, Comp_Seat *s, uint32_t timestamp, uint32_t button_id, uint32_t state)
{
   uint32_t serial, btn;
   Eina_List *l, *ll;
   struct wl_resource *res;

   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 (s->ptr.enter != cs) return EINA_FALSE;
   if (!comp_surface_check_grab(cs, s)) return EINA_FALSE;
   if (state == WL_POINTER_BUTTON_STATE_PRESSED)
     s->ptr.button_mask |= 1 << button_id;
   else
     {
        if (!(s->ptr.button_mask & (1 << button_id))) return EINA_FALSE;
        s->ptr.button_mask &= ~(1 << button_id);
        if (s->drag.res && (!s->drag.tch))
          {
             drag_grab_button(s, timestamp, button_id, WL_POINTER_BUTTON_STATE_RELEASED);
             comp_surface_input_event(&s->ptr.events, button_id, 0, timestamp, state == WL_POINTER_BUTTON_STATE_RELEASED);
             s->ptr.enter = NULL;
             comp_surface_send_pointer_enter(cs, s, s->ptr.pos.x, s->ptr.pos.y);
             return EINA_TRUE;
          }
     }

   if (cs->dead)
     {
        comp_surface_input_event(&s->ptr.events, button_id, 0, timestamp, state == WL_POINTER_BUTTON_STATE_RELEASED);
        return EINA_TRUE;
     }

   l = seat_ptr_resources_get(s, wl_resource_get_client(cs->res));
   if (!l) return EINA_FALSE;
   serial = wl_display_next_serial(s->c->display);
   comp_surface_input_event(&s->ptr.events, button_id, serial, timestamp, state == WL_POINTER_BUTTON_STATE_RELEASED);

   EINA_LIST_FOREACH(l, ll, res)
     wl_pointer_send_button(res, serial, timestamp, btn, state);
   return EINA_TRUE;
}

static void
comp_surface_mouse_down(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   s = seat_find(data, ev->dev);
   if (comp_surface_mouse_button(data, s, ev->timestamp, ev->button, WL_POINTER_BUTTON_STATE_PRESSED))
     {
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
     }
}

static void
comp_surface_mouse_up(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Down *ev = event_info;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   s = seat_find(data, ev->dev);
   if (comp_surface_mouse_button(data, s, ev->timestamp, ev->button, WL_POINTER_BUTTON_STATE_RELEASED))
     {
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
     }
}

static void
comp_surface_mouse_move(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Mouse_Move *ev = event_info;
   Comp_Surface *cs = data;
   Eina_List *l = NULL, *ll;
   struct wl_resource *res;
   int x, y;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (cs->dead) return;
   s = seat_find(data, ev->dev);
   if (s->ptr.enter != cs) return;
   if (!comp_surface_check_grab(cs, s)) return;
   if (s->drag.res)
     {
        if (s->drag.enter != cs) return;
        res = data_device_find(s, cs->res);
        if (!res) return;
     }
   else
     {
        l = seat_ptr_resources_get(s, wl_resource_get_client(cs->res));
        if (!l) return;
     }
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);
   //fprintf(stderr, "MOTION %s\n", cs->shell.popup ? "POPUP" : "TOPLEVEL");
   if (s->drag.res)
     wl_data_device_send_motion(res, ev->timestamp,
            wl_fixed_from_int(ev->cur.canvas.x - x), wl_fixed_from_int(ev->cur.canvas.y - y));
   else
     {
      //int n = 0;
        EINA_LIST_FOREACH(l, ll, res)
          {
           //fprintf(stderr, "motion %d\n", n++);
             wl_pointer_send_motion(res, ev->timestamp,
               wl_fixed_from_int(ev->cur.canvas.x - x), wl_fixed_from_int(ev->cur.canvas.y - y));
          }
     }
   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
   s->event_propagate = 1;
}

static void
comp_surface_mouse_wheel(void *data, Evas *evas EINA_UNUSED, Eo *obj EINA_UNUSED, void *event)
{
   Evas_Event_Mouse_Wheel *ev = event;
   Comp_Surface *cs = data;
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t axis, dir;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) 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 (cs->dead) return;
   s = seat_find(data, ev->dev);
   if (s->ptr.enter != cs) return;
   if (!comp_surface_check_grab(cs, s)) return;
   l = seat_ptr_resources_get(s, wl_resource_get_client(cs->res));
   if (!l) return;
   EINA_LIST_FOREACH(l, ll, res)
     wl_pointer_send_axis(res, ev->timestamp, axis, dir);
   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
   s->event_propagate = 1;
}

static void
comp_surface_multi_down(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Multi_Down *ev = event_info;
   Comp_Surface *cs = data;
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t serial;
   int x, y;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (cs->dead) return;
   s = seat_find(data, ev->dev);
   if (!comp_surface_check_grab(cs, s)) return;
   s->tch.enter = cs;
   l = seat_tch_resources_get(s, wl_resource_get_client(cs->res));
   if (!l)
     {
        comp_surface_input_event(&s->tch.events, ev->device, 0, ev->timestamp, 0);
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
        return;
     }
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);
   serial = wl_display_next_serial(cs->c->display);
   comp_surface_input_event(&s->tch.events, ev->device, serial, ev->timestamp, 0);
   EINA_LIST_FOREACH(l, ll, res)
     wl_touch_send_down(res, serial, ev->timestamp, cs->res, ev->device,
       wl_fixed_from_int(ev->canvas.x - x), wl_fixed_from_int(ev->canvas.y - y));
   s->event_propagate = 1;
   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
comp_surface_multi_up(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Multi_Up *ev = event_info;
   Comp_Surface *cs = data;
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t serial;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (cs->dead) return;
   s = seat_find(data, ev->dev);
   if (!comp_surface_check_grab(cs, s)) return;
   if (!ev->device)
     s->tch.enter = NULL;
   l = seat_tch_resources_get(s, wl_resource_get_client(cs->res));
   if ((!l) || (s->drag.tch && ((uint32_t)ev->device == s->drag.id)))
     {
        if (s->drag.tch)
          {
             res = data_device_find(s, cs->res);
             if (!res) return;

             wl_data_device_send_drop(res);
          }
        comp_surface_input_event(&s->tch.events, ev->device, 0, ev->timestamp, 1);
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
        return;
     }
   serial = wl_display_next_serial(cs->c->display);
   comp_surface_input_event(&s->tch.events, ev->device, serial, ev->timestamp, 1);
   EINA_LIST_FOREACH(l, ll, res)
     wl_touch_send_up(res, serial, ev->timestamp, ev->device);
   s->event_propagate = 1;
   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
}

static void
comp_surface_multi_move(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Evas_Event_Multi_Move *ev = event_info;
   Comp_Surface *cs = data;
   struct wl_resource *res;
   int x, y;
   Comp_Seat *s;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   if (cs->dead) return;
   s = seat_find(data, ev->dev);
   if (!comp_surface_check_grab(cs, s)) return;
   evas_object_geometry_get(cs->obj, &x, &y, NULL, NULL);
   if (s->drag.tch)
     {
        if (s->drag.enter != cs) return;
        res = data_device_find(s, cs->res);
        if (res)
          {
             wl_data_device_send_motion(res, ev->timestamp,
               wl_fixed_from_int(ev->cur.canvas.x - x), wl_fixed_from_int(ev->cur.canvas.y - y));
             s->event_propagate = 1;
             ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
          }
        return;
     }
   else
     {
        Eina_List *l, *ll;

        l = seat_tch_resources_get(s, wl_resource_get_client(cs->res));
        if (!l) return;
        EINA_LIST_FOREACH(l, ll, res)
          wl_touch_send_motion(res, ev->timestamp, ev->device,
            wl_fixed_from_int(ev->cur.canvas.x - x), wl_fixed_from_int(ev->cur.canvas.y - y));
        s->event_propagate = 1;
        ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
     }
}

EOLIAN static Eo *
_efl_canvas_wl_surface_efl_object_constructor(Eo *obj, Comp_Surface *cs EINA_UNUSED)
{
   efl_canvas_group_clipped_set(obj, EINA_TRUE);
   return efl_constructor(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS));
}

static void
_efl_canvas_wl_surface_efl_canvas_group_group_add(Eo *obj, Comp_Surface *cs)
{
   Evas *e;

   efl_canvas_group_add(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS));
   cs->obj = obj;
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_DOWN, comp_surface_mouse_down, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_UP, comp_surface_mouse_up, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_IN, comp_surface_mouse_in, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_OUT, comp_surface_mouse_out, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_MOVE, comp_surface_mouse_move, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MOUSE_WHEEL, comp_surface_mouse_wheel, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MULTI_DOWN, comp_surface_multi_down, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MULTI_UP, comp_surface_multi_up, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_MULTI_MOVE, comp_surface_multi_move, cs);
   e = evas_object_evas_get(obj);
   cs->img = evas_object_image_filled_add(e);
   evas_object_show(cs->img);
   cs->clip = (Eo*)efl_canvas_group_clipper_get(obj);
   efl_gfx_entity_geometry_set(cs->clip, efl_gfx_entity_geometry_get(obj));

   evas_object_smart_member_add(cs->img, cs->obj);
   evas_object_image_border_center_fill_set(cs->img, EVAS_BORDER_FILL_SOLID);
   evas_object_image_colorspace_set(cs->img, EVAS_COLORSPACE_ARGB8888);
}

static void
_efl_canvas_wl_surface_efl_canvas_group_group_del(Eo *obj, Comp_Surface *cs)
{
   array_clear(&cs->input_rects);
   array_clear(&cs->opaque_rects);
   eina_tiler_free(cs->opaque);
   eina_tiler_free(cs->input);
   comp_buffer_state_clear(&cs->pending);
   while (cs->proxies)
     evas_object_del(eina_list_data_get(cs->proxies));
   if (cs->res)
     {
        cs->dead = 1;
        wl_resource_destroy(cs->res);
     }
   evas_object_del(cs->img);
   if (cs->shell.surface)
     {
        if (cs->role)
          wl_resource_destroy(cs->role);
        wl_resource_destroy(cs->shell.surface);
     }
   cs->c->surfaces = eina_inlist_remove(cs->c->surfaces, EINA_INLIST_GET(cs));
   cs->c->surfaces_count--;
   efl_canvas_group_del(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS));
}

static void
_efl_canvas_wl_surface_efl_gfx_entity_position_set(Eo *obj, Comp_Surface *cs, Eina_Position2D pos)
{
   efl_gfx_entity_position_set(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS), pos);
   efl_gfx_entity_position_set(cs->clip, pos);

   //{
      //if (cs->cursor)
      //fprintf(stderr, "COMP %sSURFACE(%p) %d,%d\n", cs->subsurface ? "SUB" : "", cs, x, y);
   //}
}

static void
_efl_canvas_wl_surface_efl_gfx_entity_size_set(Eo *obj, Comp_Surface *cs, Eina_Size2D sz)
{
   efl_gfx_entity_size_set(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS), sz);
   evas_object_resize(cs->clip, sz.w, sz.h);
   //if (cs->cursor) fprintf(stderr, "COMP %sSURFACE(%p) %dx%d\n", cs->subsurface ? "SUB" : "", cs, w, h);
   if (cs->drag)
     evas_object_move(cs->obj, cs->drag->ptr.pos.x, cs->drag->ptr.pos.y);
}

static void
_efl_canvas_wl_surface_efl_gfx_entity_visible_set(Eo *obj, Comp_Surface *cs, Eina_Bool vis)
{
   Comp_Surface *pcs = NULL, *lcs;
   efl_gfx_entity_visible_set(efl_super(obj, EFL_CANVAS_WL_SURFACE_CLASS), vis);
   if (vis)
     {
        evas_object_show(cs->clip);
        cs->mapped = 1;
        return;
     }
   evas_object_hide(cs->clip);
   cs->mapped = 0;

   if (!cs->shell.activated) return;
   cs->shell.activated = 0;
   if (cs->shell.popup && cs->role)
     xdg_popup_send_popup_done(cs->role);
   if (cs->parent && cs->shell.popup) return;
   /* attempt to revert focus based on stacking order */
   if (cs->parent)
     {
        EINA_INLIST_REVERSE_FOREACH(cs->parent->children, lcs)
          {
             if (lcs == cs) continue;
             if (!evas_object_visible_get(lcs->obj)) continue;
             if ((!lcs->shell.surface) || (!lcs->role)) continue;
             lcs->shell.activated = 1;
             if (lcs->shell.popup)
               {
                  if (!lcs->extracted)
                    evas_object_raise(lcs->obj);
               }
             else
               shell_surface_send_configure(lcs);
             return;
          }
        if (!cs->parent->shell.popup)
          {
             pcs = cs->parent;
             if (!pcs->mapped) pcs = NULL;
          }
     }
   if (cs->c->seats)
     comp_seats_redo_enter(cs->c, pcs);
}

static void
comp_surface_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   struct wl_resource *res;
   Comp_Surface *cs;
   Comp *c = wl_resource_get_user_data(resource);
   Eo *obj;
   int x, y;

   res = wl_resource_create(client, &wl_surface_interface, wl_resource_get_version(resource), id);
   obj = efl_add(EFL_CANVAS_WL_SURFACE_CLASS, c->obj);
   efl_gfx_entity_visible_set(obj, EINA_FALSE);
   cs = efl_data_scope_get(obj, EFL_CANVAS_WL_SURFACE_CLASS);
   cs->res = res;
   evas_object_smart_member_add(cs->obj, c->obj);
   cs->c = c;
   evas_object_geometry_get(c->obj, &x, &y, NULL, NULL);
   evas_object_move(cs->obj, x, y);

   c->surfaces = eina_inlist_prepend(c->surfaces, EINA_INLIST_GET(cs));
   c->surfaces_count++;
   eina_hash_list_append(c->client_surfaces, &client, cs);
   if (evas_object_visible_get(cs->c->clip))
     comp_surface_output_enter(cs);
   else
     comp_surface_output_leave(cs);

   cs->opaque = tiler_new();
   cs->input = tiler_new();
   comp_buffer_state_alloc(&cs->pending);
   cs->pending.set_input = 1;
   eina_tiler_rect_add(cs->pending.input, &(Eina_Rectangle){0, 0, 65535, 65535});

   wl_resource_set_implementation(res, &comp_surface_interface, cs, comp_surface_impl_destroy);
}

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

static void
comp_region_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 = wl_resource_get_user_data(resource);
   eina_tiler_rect_add(tiler, &(Eina_Rectangle){x, y, w, h});
}

static void
comp_region_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 = wl_resource_get_user_data(resource);
   eina_tiler_rect_del(tiler, &(Eina_Rectangle){x, y, w, h});
}

static const struct wl_region_interface comp_region_interface =
{
   resource_destroy,
   comp_region_add,
   comp_region_subtract
};

static void
comp_region_impl_destroy(struct wl_resource *resource)
{
   eina_tiler_free(wl_resource_get_user_data(resource));
}

static void
comp_region_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Eina_Tiler *tiler;
   struct wl_resource *res;

   tiler = tiler_new();
   res = wl_resource_create(client, &wl_region_interface, 1, id);
   wl_resource_set_implementation(res, &comp_region_interface, tiler, comp_region_impl_destroy);
}

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

static const struct wl_compositor_interface comp_interface =
{
   comp_surface_create,
   comp_region_create,
};

static void
comp_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!client_allowed_check(data, client)) return;

   res = wl_resource_create(client, &wl_compositor_interface, version, id);
   wl_resource_set_implementation(res, &comp_interface, data, NULL);
}

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


static void
subcomp_subsurface_impl_destroy(struct wl_resource *resource)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);

   evas_object_hide(css->surface->obj);
   if (css->surface->parent)
     {
        css->surface->parent->subsurfaces = eina_list_remove(css->surface->parent->subsurfaces, css);
        if (css->surface->parent->pending_subsurfaces)
          css->surface->parent->pending_subsurfaces = eina_list_remove(css->surface->parent->pending_subsurfaces, css);
     }
   comp_surface_reparent(css->surface, NULL);
   comp_buffer_state_clear(&css->cache);
   comp_surface_buffer_detach(&css->cache.buffer);
   css->surface->subsurface = NULL;
   css->surface->role = NULL;
   free(css);
}

static void
subcomp_subsurface_set_position(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);

   css->pending_offset.x = x;
   css->pending_offset.y = y;
   css->set_offset = 1;
}

static void
subcomp_subsurface_place_above(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *sibling_resource)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);
   Comp_Subsurface *css2 = wl_resource_get_user_data(sibling_resource);

   if ((!css) || (!css2))
     {
        wl_resource_post_error( (!css) ? resource : sibling_resource,
                               WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "surface is not a valid subsurface");
        return;
     }

   if (!css->surface->parent->pending_subsurfaces)
     css->surface->parent->pending_subsurfaces = eina_list_clone(css->surface->parent->subsurfaces);
   css->surface->parent->pending_subsurfaces = eina_list_remove(css->surface->parent->pending_subsurfaces, css);
   css->surface->parent->pending_subsurfaces = eina_list_append_relative(css->surface->parent->pending_subsurfaces, css, css2);
}

static void
subcomp_subsurface_place_below(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *sibling_resource)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);
   Comp_Subsurface *css2 = wl_resource_get_user_data(sibling_resource);

   if ((!css) || (!css2))
     {
        wl_resource_post_error( (!css) ? resource : sibling_resource,
                               WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "surface is not a valid subsurface");
        return;
     }

   if (!css->surface->parent->pending_subsurfaces)
     css->surface->parent->pending_subsurfaces = eina_list_clone(css->surface->parent->subsurfaces);
   css->surface->parent->pending_subsurfaces = eina_list_remove(css->surface->parent->pending_subsurfaces, css);
   css->surface->parent->pending_subsurfaces = eina_list_prepend_relative(css->surface->parent->pending_subsurfaces, css, css2);
}

static void
subcomp_subsurface_set_sync(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);
   css->sync = 1;
}

static void
subcomp_subsurface_set_desync(struct wl_client *client EINA_UNUSED, struct wl_resource *resource)
{
   Comp_Subsurface *css = wl_resource_get_user_data(resource);
   css->sync = 0;
}

static const struct wl_subsurface_interface subcomp_subsurface_interface =
{
   resource_destroy,
   subcomp_subsurface_set_position,
   subcomp_subsurface_place_above,
   subcomp_subsurface_place_below,
   subcomp_subsurface_set_sync,
   subcomp_subsurface_set_desync
};

static void
subcomp_subsurface_create(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource, struct wl_resource *parent_resource)
{
   Comp_Subsurface *css;
   Comp_Surface *cs = wl_resource_get_user_data(surface_resource);
   Comp_Surface *pcs = wl_resource_get_user_data(parent_resource);

   if (surface_resource == parent_resource)
     {
        wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
          "cannot create subsurface as its own child");
        return;
     }

   if (cs->role)
     {
        wl_resource_post_error(resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
                               "surface already has a role");
        return;
     }

   css = cs->subsurface = calloc(1, sizeof(Comp_Subsurface));
   comp_buffer_state_alloc(&css->cache);
   css->sync = 1;
   evas_object_name_set(cs->img, "subsurface");
   css->surface = cs;
   if (!pcs->pending_subsurfaces)
     pcs->pending_subsurfaces = eina_list_clone(pcs->subsurfaces);
   pcs->pending_subsurfaces = eina_list_append(pcs->pending_subsurfaces, css);
   comp_surface_reparent(cs, pcs);

   cs->role = wl_resource_create(client, &wl_subsurface_interface, 1, id);
   wl_resource_set_implementation(cs->role, &subcomp_subsurface_interface, css, subcomp_subsurface_impl_destroy);
}

static const struct wl_subcompositor_interface subcomp_interface =
{
   resource_destroy,
   subcomp_subsurface_create,
};

static void
subcomp_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!client_allowed_check(data, client)) return;
   res = wl_resource_create(client, &wl_subcompositor_interface, version, id);
   wl_resource_set_implementation(res, &subcomp_interface, data, NULL);
}

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

static void
data_device_source_offer(struct wl_client *client, struct wl_resource *resource, const char *type)
{
   Comp_Data_Device_Source *ds = wl_resource_get_user_data(resource);

   if ((!ds->proxy) || (!ds->x11_owner))
     ds->mime_types = eina_list_append(ds->mime_types, eina_stringshare_add(type));
}

static void
data_device_source_set_actions(struct wl_client *client, struct wl_resource *resource, uint32_t dnd_actions)
{
   Comp_Data_Device_Source *ds = wl_resource_get_user_data(resource);

   if (ds->actions_set)
     {
        wl_resource_post_error(ds->res, WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
          "dnd_actions already set");
        return;
     }

    if (dnd_actions & ~ALL_ACTIONS)
      {
         wl_resource_post_error(ds->res, WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
           "invalid dnd_actions");
         return;
      }

   if (ds->seat && (!ds->proxy))
     {
        wl_resource_post_error(ds->res, WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
        "drag already begun");
        return;
     }

   ds->dnd_actions = dnd_actions;
   ds->actions_set = 1;
}

static void
data_device_source_impl_destroy(struct wl_resource *resource)
{
   Comp_Data_Device_Source *ds = wl_resource_get_user_data(resource);
   Eina_Stringshare *type;

   EINA_LIST_FREE(ds->mime_types, type) eina_stringshare_del(type);
   while (ds->transfers)
     {
        Comp_Data_Device_Transfer *dt = EINA_INLIST_CONTAINER_GET(ds->transfers, Comp_Data_Device_Transfer);
        fdh_del(dt->fdh);
        ds->transfers = eina_inlist_remove(ds->transfers, EINA_INLIST_GET(dt));
        free(dt);
     }
   if (ds->seat && (ds->seat->selection_source == ds)) ds->seat->selection_source = NULL;
   if (ds->seat && (ds->seat->drag.source == ds)) ds->seat->drag.source = NULL;
   comp_data_device_source_reader_clear(ds);
   ecore_event_handler_del(ds->proxy_send_handler);
   if (ds->offer) ds->offer->source = NULL;
   free(ds);
}

static const struct wl_data_source_interface data_device_source_interface =
{
   data_device_source_offer,
   resource_destroy,
   data_device_source_set_actions,
};

static void
data_device_manager_source_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Comp_Data_Device_Source *ds;
   pid_t pid;
   Comp_Seat *s;
   Comp *c;

   ds = calloc(1, sizeof(Comp_Data_Device_Source));
   c = wl_resource_get_user_data(resource);
   wl_client_get_credentials(client, &pid, NULL, NULL);
   ds->proxy = c->client_disp && (pid == getpid());
   ds->res = wl_resource_create(client, &wl_data_source_interface, MIN(wl_resource_get_version(resource), 3), id);
   wl_resource_set_implementation(ds->res, &data_device_source_interface, ds, data_device_source_impl_destroy);
   if (!ds->proxy) return;
   EINA_INLIST_FOREACH(c->seats, s)
     {
        int x, y;
        struct wl_resource *res;
        Eina_Array *arr;
        Eina_Array_Iterator it;
        unsigned int i;
        char *type;
        if (((!s->client_offer) && (!s->drag.x11_owner)) || s->drag.res) continue;

        //proxied drag
        s->drag.res = resource;
        s->drag.source = ds;
        ds->seat = s;
        if (!s->drag.enter) return;
        evas_object_geometry_get(s->drag.enter->obj, &x, &y, NULL, NULL);
        if (s->client_offer)
          {
             arr = ecore_wl2_offer_mimes_get(s->client_offer);
             EINA_ARRAY_ITER_NEXT(arr, i, type, it)
               ds->mime_types = eina_list_append(ds->mime_types, eina_stringshare_add(type));
          }
        else if (s->drag.x11_owner)
          {
             PTR_SWAP(&s->drag.x11_types, &ds->mime_types);
             ds->x11_owner = s->drag.x11_owner;
             s->drag.x11_owner = 0;
          }
        comp_surface_send_data_device_enter(s->drag.enter, s);
        res = data_device_find(s, s->drag.enter->res);
        if (!res) return;
        wl_data_device_send_motion(res,
          (unsigned int)((unsigned long long)(ecore_time_get() * 1000.0) & 0xffffffff),
          wl_fixed_from_int(s->ptr.pos.x - x), wl_fixed_from_int(s->ptr.pos.y - y));
     }
}

static void
data_device_manager_device_impl_destroy(struct wl_resource *resource)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_client *client = wl_resource_get_client(resource);
   eina_hash_del_by_key(s->data_devices, &client);
}

static void
data_device_start_drag(struct wl_client *client, struct wl_resource *resource, struct wl_resource *source_resource, struct wl_resource *origin_resource, struct wl_resource *icon_resource, uint32_t serial)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   Comp_Data_Device_Source *ds = NULL;
   Comp_Surface *cs = wl_resource_get_user_data(origin_resource);
   Comp_Surface *ics = wl_resource_get_user_data(icon_resource);
   Comp_Surface *enter;
   Input_Sequence *ev;
   Eina_Bool found = EINA_FALSE, up = EINA_FALSE;
   int cx, cy;

   if (source_resource) ds = wl_resource_get_user_data(source_resource);
   if ((cs != s->ptr.enter) && (cs != s->tch.enter)) return;
   enter = s->ptr.enter;
   if (enter) comp_surface_send_pointer_leave(enter, s);
   EINA_INLIST_FOREACH(s->tch.events, ev)
     {
        if (ev->down_serial == serial)
          {
             s->drag.tch = 1;
             up = !!ev->up_time;
             found = 1;
             s->drag.id = ev->id;
             break;
          }
     }
   if (!found)
     {
        EINA_INLIST_FOREACH(s->ptr.events, ev)
          {
             if (ev->down_serial == serial)
               {
                  up = !!ev->up_time;
                  found = 1;
                  s->drag.id = ev->id;
                  break;
               }
          }
     }
   if (!found) return;
   if (s->ptr.enter && (!s->drag.tch))
     comp_surface_send_pointer_leave(s->ptr.enter, s);
   s->drag.res = resource;
   s->drag.source = ds;
   if (ds) ds->seat = s;
   s->drag.surface = ics;
   if (ds && ds->proxy && s->drag.x11_owner)
     ds->x11_owner = s->drag.x11_owner;
   s->drag.x11_owner = 0;

   if (s->drag.tch)
     {
        cx = s->tch.pos.x;
        cy = s->tch.pos.y;
     }
   else
     {
        cx = s->ptr.pos.x;
        cy = s->ptr.pos.y;
     }
   if (ics)
     {
        int w, h;

        ics->cursor = 1;
        ics->drag = s;
        ics->pending.set_input = 1;
        eina_tiler_clear(ics->pending.input);
        evas_object_smart_member_del(ics->obj);
        evas_object_pass_events_set(ics->obj, 1);
        evas_object_layer_set(ics->obj, EVAS_LAYER_MAX - 1);
        evas_object_geometry_get(ics->obj, NULL, NULL, &w, &h);
        evas_object_move(ics->obj, cx, cy);
        evas_object_show(ics->obj);
     }
   if (s->drag.tch)
     {
        if (s->tch.enter)
          comp_surface_send_data_device_enter(s->tch.enter, s);
     }
   else if (enter)
     comp_surface_send_pointer_enter(enter, s, cx, cy);
#ifdef HAVE_ECORE_X
   if (ecore_x_display_get())
     ecore_x_pointer_grab(ecore_evas_window_get(ecore_evas_ecore_evas_get(s->c->evas)));
#endif
   if (up) drag_grab_button(s, ev->up_time, ev->id, WL_POINTER_BUTTON_STATE_RELEASED);
}

static Eina_Bool
data_device_proxy_send_send(void *d, int t EINA_UNUSED, void *event)
{
   Comp_Data_Device_Source *ds = d;
   Ecore_Wl2_Event_Data_Source_Send *ev = event;

   if (ds->proxy_serial != ev->serial) return ECORE_CALLBACK_RENEW;
   if (ev->display == ds->seat->c->parent_disp)
     {
        if (ecore_wl2_input_seat_id_get(ds->seat->seat) != ev->seat) return ECORE_CALLBACK_RENEW;
        wl_data_source_send_send(ds->res, ev->type, ev->fd);
        close(ev->fd);
     }
   return ECORE_CALLBACK_RENEW;
}

static void
data_device_set_selection(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *source_resource, uint32_t serial)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   Comp_Data_Device_Source *ds = NULL;

   if (source_resource)
     ds = wl_resource_get_user_data(source_resource);

   if (ds && ds->actions_set)
     {
        wl_resource_post_error(source_resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE, "invalid source");
        return;
     }
   if (s->selection_source && (s->selection_serial - serial < UINT32_MAX / 2))
     return;
   if (s->selection_source)
     {
        if (!s->selection_source->transfers)
          comp_data_device_source_reader_clear(s->selection_source);
        ecore_event_handler_del(s->selection_source->proxy_send_handler);
        s->selection_source->proxy_send_handler = NULL;
     }
   if (ds) ds->seat = s;
   s->selection_source = ds;
   s->selection_serial = serial;
   comp_seat_kbd_data_device_enter(s);
   if (ds && ds->proxy && s->x11_selection_owner)
     ds->x11_owner = s->x11_selection_owner;
   s->x11_selection_owner = 0;
   if (1)
   //if (s->c->data_device_proxy)
     {
        if (s->c->parent_disp) //wayland
          {
             if (ds && ds->mime_types)
               {
                  char *t, *types[eina_list_count(ds->mime_types) + 1];
                  Eina_List *l;
                  int i = 0;

                  EINA_LIST_FOREACH(ds->mime_types, l, t)
                    types[i++] = t;
                  types[i] = NULL;
                  ds->proxy_serial = ecore_wl2_dnd_selection_set(s->seat, (const char**)types);
                  ds->proxy_send_handler =
                    ecore_event_handler_add(ECORE_WL2_EVENT_DATA_SOURCE_SEND,
                      data_device_proxy_send_send, ds);
               }
             else if (ds)
               ds->proxy_serial = ecore_wl2_dnd_selection_clear(s->seat);
          }
#ifdef HAVE_ECORE_X
        else if (ds && (!ds->proxy) && ecore_x_display_get())
          {
             Eina_List *l;
             Comp *c;
             Ecore_X_Time t = ecore_x_current_time_get();

             EINA_LIST_FOREACH(comps, l, c)
               c->x11_selection = 0;
             s->c->x11_selection = 1;
             ecore_x_selection_owner_set(ecore_evas_window_get(ecore_evas_ecore_evas_get(s->c->evas)),
               ECORE_X_ATOM_SELECTION_CLIPBOARD, t);
          }
#endif
     }
   s->client_selection_serial = 0;
}

static const struct wl_data_device_interface data_device_interface =
{
   data_device_start_drag,
   data_device_set_selection,
   resource_destroy,
};

static void
data_device_manager_device_create(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id, struct wl_resource *seat_resource)
{
   Comp_Seat *s = wl_resource_get_user_data(seat_resource);
   struct wl_resource *res;

   res = wl_resource_create(client, &wl_data_device_interface, wl_resource_get_version(manager_resource), id);
   wl_resource_set_implementation(res, &data_device_interface, s, data_device_manager_device_impl_destroy);
   eina_hash_add(s->data_devices, &client, res);
}

static const struct wl_data_device_manager_interface data_device_manager_interface =
{
   data_device_manager_source_create,
   data_device_manager_device_create
};

static void
data_device_manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!client_allowed_check(data, client)) return;
   res = wl_resource_create(client, &wl_data_device_manager_interface, MIN(3, version), id);
   wl_resource_set_implementation(res, &data_device_manager_interface, data, NULL);
}

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

static void
output_resize(Comp *c, struct wl_resource *res)
{
   int w, h;
   int rot[][4] =
   {
    {
     [EFL_CANVAS_WL_ROTATION_ROTATE_0] = WL_OUTPUT_TRANSFORM_NORMAL,
     [EFL_CANVAS_WL_ROTATION_ROTATE_90] = WL_OUTPUT_TRANSFORM_90,
     [EFL_CANVAS_WL_ROTATION_ROTATE_180] = WL_OUTPUT_TRANSFORM_180,
     [EFL_CANVAS_WL_ROTATION_ROTATE_270] = WL_OUTPUT_TRANSFORM_270,
    },
    {
     [EFL_CANVAS_WL_ROTATION_ROTATE_0] = WL_OUTPUT_TRANSFORM_FLIPPED,
     [EFL_CANVAS_WL_ROTATION_ROTATE_90] = WL_OUTPUT_TRANSFORM_FLIPPED_90,
     [EFL_CANVAS_WL_ROTATION_ROTATE_180] = WL_OUTPUT_TRANSFORM_FLIPPED_180,
     [EFL_CANVAS_WL_ROTATION_ROTATE_270] = WL_OUTPUT_TRANSFORM_FLIPPED_270,
    },
   };

   evas_object_geometry_get(c->clip, NULL, NULL, &w, &h);
   /* FIXME: transform */
   wl_output_send_geometry(res, 0, 0, w, h, 0, "", "", rot[c->rtl][c->rotation]);
   wl_output_send_mode(res, WL_OUTPUT_MODE_CURRENT, w, h, 60 * 1000);
   if (wl_resource_get_version(res) >= WL_OUTPUT_DONE_SINCE_VERSION)
     wl_output_send_done(res);
}

static void
output_unbind(struct wl_resource *resource)
{
   Comp *c = wl_resource_get_user_data(resource);
   c->output_resources = eina_list_remove(c->output_resources, resource);
}

static void
output_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   Comp *c = data;
   Comp_Surface *cs;
   struct wl_resource *res;

   if (!client_allowed_check(data, client)) return;
   res = wl_resource_create(client, &wl_output_interface, version, id);
   c->output_resources = eina_list_append(c->output_resources, res);
   wl_resource_set_implementation(res, NULL, data, output_unbind);
   if (wl_resource_get_version(res) >= WL_OUTPUT_SCALE_SINCE_VERSION)
     wl_output_send_scale(res, lround(c->scale));
   output_resize(c, res);
   EINA_INLIST_FOREACH(c->surfaces, cs)
     if (wl_resource_get_client(cs->res) == client)
       {
          if (evas_object_visible_get(c->clip))
            comp_surface_output_enter(cs);
          else
            comp_surface_output_leave(cs);
       }
}

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

static void
shell_surface_toplevel_impl_destroy(struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   cs->role = NULL;
   evas_object_hide(cs->obj);
   shell_surface_reset(cs);
   if (!cs->parent) return;
   comp_surface_reparent(cs, NULL);
}

static void
shell_surface_toplevel_set_parent(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *parent_resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Comp_Surface *pcs = NULL;

   if (parent_resource) pcs = wl_resource_get_user_data(parent_resource);

   comp_surface_reparent(cs, pcs);
   if (parent_resource)
     efl_event_callback_call(cs->c->obj, EFL_CANVAS_WL_EVENT_CHILD_ADDED, cs->obj);
}

static void
shell_surface_toplevel_set_title(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *title)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   eina_stringshare_replace(&cs->shell.title, title);
}

static void
shell_surface_toplevel_set_app_id(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, const char *id)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   eina_stringshare_replace(&cs->shell.app_id, id);
}

static void
shell_surface_toplevel_show_window_menu(){}
static void
shell_surface_toplevel_move(){}
static void
shell_surface_toplevel_resize(){}

static void
shell_surface_toplevel_set_max_size(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t w, int32_t h)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   efl_gfx_hint_size_restricted_max_set(cs->obj, EINA_SIZE2D(w, h));
   if (cs == cs->c->active_surface)
     shell_surface_minmax_update(cs);
}

static void
shell_surface_toplevel_set_min_size(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t w, int32_t h)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   efl_gfx_hint_size_restricted_min_set(cs->obj, EINA_SIZE2D(w, h));
   if (cs == cs->c->active_surface)
     shell_surface_minmax_update(cs);
}

static void
shell_surface_toplevel_set_maximized(){}
static void
shell_surface_toplevel_unset_maximized(){}
static void
shell_surface_toplevel_set_fullscreen(){}
static void
shell_surface_toplevel_unset_fullscreen(){}
static void
shell_surface_toplevel_set_minimized(){}

static const struct xdg_toplevel_interface shell_surface_toplevel_interface =
{
   resource_destroy,
   shell_surface_toplevel_set_parent,
   shell_surface_toplevel_set_title,
   shell_surface_toplevel_set_app_id,
   shell_surface_toplevel_show_window_menu,
   shell_surface_toplevel_move,
   shell_surface_toplevel_resize,
   shell_surface_toplevel_set_max_size,
   shell_surface_toplevel_set_min_size,
   shell_surface_toplevel_set_maximized,
   shell_surface_toplevel_unset_maximized,
   shell_surface_toplevel_set_fullscreen,
   shell_surface_toplevel_unset_fullscreen,
   shell_surface_toplevel_set_minimized,
};

static void
shell_surface_toplevel_create(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t id)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   if (cs->buffer[0] || cs->pending.buffer)
     {
        wl_resource_post_error(resource, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
                               "buffer attached/committed before configure");
        return;
     }
   if (cs->role)
     {
        wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE,
                               "surface already has assigned role");
        return;
     }

   cs->role = wl_resource_create(client, &xdg_toplevel_interface, 1, id);
   wl_resource_set_implementation(cs->role, &shell_surface_toplevel_interface, cs, shell_surface_toplevel_impl_destroy);
   cs->shell.new = 1;
   efl_event_callback_call(cs->c->obj, EFL_CANVAS_WL_EVENT_TOPLEVEL_ADDED, cs->obj);
}

static void
shell_surface_popup_grab(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, struct wl_resource *seat, uint32_t serial EINA_UNUSED)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Comp_Seat *s = wl_resource_get_user_data(seat);

   if (cs->dead || (!cs->role) || (!cs->shell.surface))
     {
        wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "can't grab for this resource");
        return;
     }
   if (cs->mapped)
     {
        wl_resource_post_error(resource, XDG_POPUP_ERROR_INVALID_GRAB,
                                "grab requested on mapped popup");
        return;
     }

   if (cs->parent->shell.popup && (s->grab != cs->parent))
     {
        wl_resource_post_error(resource, XDG_POPUP_ERROR_INVALID_GRAB,
                                "grab requested on ungrabbed nested popup");
        return;
     }
   s->grab = cs;
   cs->shell.grabs = eina_list_append(cs->shell.grabs, s);
}

static const struct xdg_popup_interface shell_surface_popup_interface =
{
   resource_destroy,
   shell_surface_popup_grab,
};

static void
shell_surface_popup_impl_destroy(struct wl_resource *resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);
   Comp_Seat *s;

   cs->role = NULL;
   evas_object_hide(cs->obj);
   cs->shell.popup = 0;
   shell_surface_reset(cs);
   EINA_LIST_FREE(cs->shell.grabs, s)
     if (s->grab == cs)
       {
          if (cs->parent->shell.grabs &&
              eina_list_data_find(cs->parent->shell.grabs, s))
            s->grab = cs->parent;
          else
            s->grab = NULL;
       }
   if (cs->children)
     wl_resource_post_error(cs->shell.surface, XDG_WM_BASE_ERROR_DEFUNCT_SURFACES,
                            "popups dismissed out of order");
   if (cs->parent)
     comp_surface_reparent(cs, NULL);
}

static void
shell_surface_popup_create(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *parent_resource, struct wl_resource *positioner_resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   if (cs->buffer[0] || cs->pending.buffer)
     {
        wl_resource_post_error(resource, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
                               "buffer attached/committed before configure");
        return;
     }
   if (cs->role)
     {
        wl_resource_post_error(resource, XDG_WM_BASE_ERROR_ROLE,
                               "surface already has assigned role");
        return;
     }

   cs->role = wl_resource_create(client, &xdg_popup_interface, 1, id);
   wl_resource_set_implementation(cs->role, &shell_surface_popup_interface, cs, shell_surface_popup_impl_destroy);
   cs->shell.new = 1;
   cs->shell.popup = 1;
   if(parent_resource)
     comp_surface_reparent(cs, wl_resource_get_user_data(parent_resource));
   cs->shell.positioner = wl_resource_get_user_data(positioner_resource);
   _apply_positioner(cs, cs->shell.positioner);
   efl_event_callback_call(cs->c->obj, EFL_CANVAS_WL_EVENT_POPUP_ADDED, cs->obj);
}

static void
_validate_size(struct wl_resource *resource, int32_t value)
{
   if (value <= 0)
     wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid size passed");
}

static void
_validate_size_negative(struct wl_resource *resource, int32_t value)
{
   if (value < 0)
     wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid size passed");
}

static void
shell_positioner_set_size(struct wl_client *wl_client EINA_UNUSED, struct wl_resource *resource, int32_t w, int32_t h)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   _validate_size(resource, w);
   _validate_size(resource, h);

   p->size.w = w;
   p->size.h = h;
}

static void
shell_positioner_set_anchor_rect(struct wl_client *wl_client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   _validate_size_negative(resource, w);
   _validate_size_negative(resource, h);

   EINA_RECTANGLE_SET(&p->anchor_rect, x, y, w, h);
}

static void
shell_positioner_set_anchor(struct wl_client *wl_client EINA_UNUSED, struct wl_resource *resource, enum xdg_positioner_anchor anchor)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   p->anchor = anchor;
}

static void
shell_positioner_set_gravity(struct wl_client *wl_client EINA_UNUSED, struct wl_resource *resource, enum xdg_positioner_gravity gravity)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   if ((gravity & (XDG_POSITIONER_GRAVITY_TOP | XDG_POSITIONER_GRAVITY_BOTTOM)) ==
       (XDG_POSITIONER_GRAVITY_TOP | XDG_POSITIONER_GRAVITY_BOTTOM))
     wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid gravity values passed");
   else if ((gravity & (XDG_POSITIONER_GRAVITY_LEFT | XDG_POSITIONER_GRAVITY_RIGHT)) ==
       (XDG_POSITIONER_GRAVITY_LEFT | XDG_POSITIONER_GRAVITY_RIGHT))
     wl_resource_post_error(resource, XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid gravity values passed");
   else
     p->gravity = gravity;
}

static void
shell_positioner_set_constraint_adjustment(struct wl_client *wl_client EINA_UNUSED, struct wl_resource *resource, enum xdg_positioner_constraint_adjustment constraint_adjustment)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   p->constrain = constraint_adjustment;
}

static void
shell_positioner_set_offset(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y)
{
   Shell_Positioner *p = wl_resource_get_user_data(resource);

   p->offset.x = x;
   p->offset.y = y;
}

static const struct xdg_positioner_interface shell_positioner_interface =
{
   resource_destroy,
   shell_positioner_set_size,
   shell_positioner_set_anchor_rect,
   shell_positioner_set_anchor,
   shell_positioner_set_gravity,
   shell_positioner_set_constraint_adjustment,
   shell_positioner_set_offset,
};

static void
shell_positioner_impl_destroy(struct wl_resource *resource)
{
   Shell_Positioner *sp;

   sp = wl_resource_get_user_data(resource);
   if (!sp) return;
   if (sp->sd) sp->sd->positioners = eina_inlist_remove(sp->sd->positioners, EINA_INLIST_GET(sp));
   free(sp);
}

static void
shell_positioner_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   struct wl_resource *res;
   Shell_Data *sd;
   Shell_Positioner *sp;

   sd = wl_resource_get_user_data(resource);
   res = wl_resource_create(client, &xdg_positioner_interface, 1, id);
   sp = calloc(1, sizeof(Shell_Positioner));
   sp->anchor_rect.w = sp->anchor_rect.h = -1;
   sp->sd = sd;
   sp->res = res;
   sd->positioners = eina_inlist_append(sd->positioners, EINA_INLIST_GET(sp));
   wl_resource_set_implementation(res, &shell_positioner_interface, sp, shell_positioner_impl_destroy);
}

static void
shell_surface_set_window_geometry(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, int32_t x, int32_t y, int32_t w, int32_t h)
{
   Comp_Surface *cs = wl_resource_get_user_data(resource);

   EINA_RECTANGLE_SET(&cs->shell.geom, x, y, w, h);
}

static void
shell_surface_ack_configure(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t serial)
{
}

static const struct xdg_surface_interface shell_surface_interface =
{
   resource_destroy,
   shell_surface_toplevel_create,
   shell_surface_popup_create,
   shell_surface_set_window_geometry,
   shell_surface_ack_configure,
};

static void
shell_surface_impl_destroy(struct wl_resource *resource)
{
   Comp_Surface *ccs, *cs = wl_resource_get_user_data(resource);

   if (cs->role)
     {
        wl_resource_post_error(resource, XDG_WM_BASE_ERROR_DEFUNCT_SURFACES, "shell surface destroyed before role surfaces");
        wl_resource_destroy(cs->role);
     }
   cs->shell.surface = NULL;
   cs->shell.data->surfaces = eina_list_remove(cs->shell.data->surfaces, cs);
   cs->shell.data = NULL;
   shell_surface_reset(cs);
   while (cs->children)
     {
        ccs = EINA_INLIST_CONTAINER_GET(cs->children, Comp_Surface);
        evas_object_hide(ccs->obj);
        comp_surface_reparent(ccs, cs->parent);
     }
}

static void
shell_surface_create(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource)
{
   Shell_Data *sd = wl_resource_get_user_data(resource);
   Comp_Surface *cs = wl_resource_get_user_data(surface_resource);

   if (cs->role || cs->shell.surface)
     {
        wl_resource_post_error(surface_resource, WL_DISPLAY_ERROR_INVALID_OBJECT, "surface already has role");
        return;
     }

   cs->shell.surface = wl_resource_create(client, &xdg_surface_interface, 1, id);
   cs->shell.data = sd;
   wl_resource_set_implementation(cs->shell.surface, &shell_surface_interface, cs, shell_surface_impl_destroy);
   sd->surfaces = eina_list_append(sd->surfaces, cs);
}

static void
shell_pong(struct wl_client *client EINA_UNUSED, struct wl_resource *resource, uint32_t serial EINA_UNUSED)
{
   Shell_Data *sd = wl_resource_get_user_data(resource);

   sd->ping = 0;
}

static const struct xdg_wm_base_interface shell_interface =
{
   resource_destroy,
   shell_positioner_create,
   shell_surface_create,
   shell_pong
};

static void
shell_unbind(struct wl_resource *resource)
{
   Shell_Data *sd = wl_resource_get_user_data(resource);

   sd->c->shells = eina_inlist_remove(sd->c->shells, EINA_INLIST_GET(sd));
   while (sd->surfaces)
     {
        Comp_Surface *cs = eina_list_data_get(sd->surfaces);
        if (cs->shell.surface)
          {
             if (cs->role)
               wl_resource_destroy(cs->role);
             wl_resource_destroy(cs->shell.surface);
          }
     }
   free(sd);
}

static void
shell_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   Comp *c = data;
   struct wl_resource *res;
   Shell_Data *sd;

   if (!client_allowed_check(data, client)) return;
   sd = calloc(1, sizeof(Shell_Data));
   sd->c = c;
   c->shells = eina_inlist_append(c->shells, EINA_INLIST_GET(sd));

   res = wl_resource_create(client, &xdg_wm_base_interface, version, id);
   sd->res = res;
   wl_resource_set_implementation(res, &shell_interface, sd, shell_unbind);
}

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

static void
seat_update_caps(Comp_Seat *s, struct wl_resource *res)
{
   enum wl_seat_capability caps = 0;
   Eina_List *l;

   if (s->pointer)
     caps |= WL_SEAT_CAPABILITY_POINTER;
   if (s->keyboard)
     caps |= WL_SEAT_CAPABILITY_KEYBOARD;
   if (s->touch)
     caps |= WL_SEAT_CAPABILITY_TOUCH;

   if (!caps) return;
   if (res)
     wl_seat_send_capabilities(res, caps);
   else
     EINA_LIST_FOREACH(s->resources, l, res)
       wl_seat_send_capabilities(res, caps);
}

static int
anonymous_fd_get(off_t size)
{
   Eina_Tmpstr *file;
   int fd;

   fd = eina_file_mkstemp("comp-keymapXXXXXX", &file);
   if (fd < 0)
     {
        EINA_LOG_ERR("mkstemp failed!");
        return - 1;
     }
   if (!eina_file_close_on_exec(fd, 1))
     {
        EINA_LOG_ERR("Failed to set CLOEXEC on fd %d", fd);
        close(fd);
        return - 1;
     }
   if (ftruncate(fd, size) == -1)
     {
        EINA_LOG_ERR("ftruncate failed!");
     }
   eina_file_unlink(file);
   eina_tmpstr_del(file);

   return fd;
}

static void
_keymap_send(Comp_Seat *s, struct wl_resource *res)
{
   char *mem;
   int fd;

   fd = anonymous_fd_get(s->kbd.keymap_str_size);
   EINA_SAFETY_ON_TRUE_RETURN(fd == -1);

   mem = mmap(NULL, s->kbd.keymap_str_size,
              PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   memcpy(mem, s->kbd.keymap_str, s->kbd.keymap_str_size);
   munmap(mem, s->kbd.keymap_str_size);
   wl_keyboard_send_keymap(res, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, fd, s->kbd.keymap_str_size);
   close(fd);
}

static void
seat_keymap_send(Comp_Seat *s)
{
   Eina_List *l;
   Eina_Iterator *it;
   it = eina_hash_iterator_data_new(s->kbd.resources);
   EINA_ITERATOR_FOREACH(it, l)
     {
        Eina_List *ll;
        struct wl_resource *res;
        EINA_LIST_FOREACH(l, ll, res)
          _keymap_send(s, res);
     }
   eina_iterator_free(it);
}

static Eina_Bool
seat_kbd_mods_update(Comp_Seat *s)
{
   xkb_mod_mask_t mod;
   xkb_layout_index_t grp;

   mod = xkb_state_serialize_mods(s->kbd.state, XKB_STATE_DEPRESSED);
   s->kbd.mods.changed |= mod != s->kbd.mods.depressed;
   s->kbd.mods.depressed = mod;
   mod = xkb_state_serialize_mods(s->kbd.state, XKB_STATE_MODS_LATCHED);
   s->kbd.mods.changed |= mod != s->kbd.mods.latched;
   s->kbd.mods.latched = mod;
   mod = xkb_state_serialize_mods(s->kbd.state, XKB_STATE_MODS_LOCKED);
   s->kbd.mods.changed |= mod != s->kbd.mods.locked;
   s->kbd.mods.locked = mod;
   grp = xkb_state_serialize_layout(s->kbd.state, XKB_STATE_LAYOUT_EFFECTIVE);
   s->kbd.mods.changed |= grp != s->kbd.mods.group;
   s->kbd.mods.group = grp;
   return s->kbd.mods.changed;
}

static void
seat_kbd_external_init(Comp_Seat *s)
{
   Eina_List *l, *ll;
   uint32_t serial;
   struct wl_resource *res;

   seat_keymap_send(s);
   if (!seat_kbd_mods_update(s)) return;
   l = seat_kbd_active_resources_get(s);
   if (!l) return;
   serial = wl_display_next_serial(s->c->display);
   EINA_LIST_FOREACH(l, ll, res)
     comp_seat_send_modifiers(s, res, serial);
}

static void
seat_keymap_update(Comp_Seat *s)
{
   xkb_mod_mask_t latched = 0, locked = 0;

   s->kbd.keymap_str = NULL;
   s->kbd.keymap_str_size = 0;

#ifdef HAVE_ECORE_X
   if (!x11_kbd_keymap)
     {
#endif
        if (s->kbd.state)
          {
             latched = xkb_state_serialize_mods(s->kbd.state, XKB_STATE_MODS_LATCHED);
             locked = xkb_state_serialize_mods(s->kbd.state, XKB_STATE_MODS_LOCKED);
             xkb_state_unref(s->kbd.state);
          }
        if (!s->kbd.keymap)
          {
             s->kbd.state = NULL;
             s->kbd.keymap_str = NULL;
             s->kbd.keymap_str_size = 0;
             return;
          }

        s->kbd.state = xkb_state_new(s->kbd.keymap);
        xkb_state_update_mask(s->kbd.state, 0, latched, locked, 0, 0, 0);
#ifdef HAVE_ECORE_X
     }
#endif
   s->kbd.keymap_str = xkb_map_get_as_string(s->kbd.keymap);
   s->kbd.keymap_str_size = strlen(s->kbd.keymap_str) + 1;

   seat_keymap_send(s);
}

static inline void
seat_kbd_repeat_rate_update(Comp_Seat *s)
{
   double rate, delay;

   if (s->seat)
     {
        if (ecore_wl2_input_keyboard_repeat_get(s->seat, &rate, &delay))
          {
             s->kbd.repeat_rate = lround(1 / rate);
             s->kbd.repeat_delay = lround(delay * 1000);
          }
        else
          s->kbd.repeat_rate = s->kbd.repeat_delay = 0;
     }
   else
     {
        s->kbd.repeat_rate = 40;
        s->kbd.repeat_delay = 400;
     }
}

static void
seat_keymap_create(Comp_Seat *s)
{
   struct xkb_rule_names names;

   memset(&names, 0, sizeof(names));
   names.rules = "evdev";
   names.model = "pc105";
   names.layout = "us";
   s->kbd.context = xkb_context_new(0);
   s->kbd.keymap = xkb_map_new_from_names(s->kbd.context, &names, 0);
}

static const struct wl_keyboard_interface seat_kbd_interface =
{
   resource_destroy
};

static void
seat_kbd_unbind(struct wl_resource *resource)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_client *client = wl_resource_get_client(resource);

   eina_hash_list_remove(s->kbd.resources, &client, resource);
}

static void
seat_kbd_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_resource *res;
   Eina_List *l, *ll;
   uint32_t serial;

   res = wl_resource_create(client, &wl_keyboard_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(res, &seat_kbd_interface, s, seat_kbd_unbind);
   if (!s->kbd.resources) s->kbd.resources = eina_hash_pointer_new(NULL);
   eina_hash_list_append(s->kbd.resources, &client, res);

   _keymap_send(s, res);

   if (wl_resource_get_version(res) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)
     wl_keyboard_send_repeat_info(res, s->kbd.repeat_rate, s->kbd.repeat_delay);

   if (s->active_client != client) return;
   l = seat_kbd_active_resources_get(s);
   if (!l) return;
   serial = wl_display_next_serial(s->c->display);
   EINA_LIST_FOREACH(l, ll, res)
     {
        if (s->c->active_surface)
          wl_keyboard_send_enter(res, serial, s->c->active_surface->res, &s->kbd.keys);
        comp_seat_send_modifiers(s, res, serial);
     }
}

static void
seat_ptr_del(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Comp_Seat *s = data;

   evas_object_event_callback_del_full(s->ptr.efl.obj, EVAS_CALLBACK_DEL, seat_ptr_del, s);
   s->ptr.efl.obj = NULL;
}

static void
seat_ptr_inherit(Comp_Seat *s, Eo *dev)
{
   ecore_evas_cursor_device_get(ecore_evas_ecore_evas_get(s->c->evas), dev, &s->ptr.efl.obj,
              &s->ptr.efl.layer, &s->ptr.efl.x, &s->ptr.efl.y);
   if (s->ptr.efl.obj)
     evas_object_event_callback_add(s->ptr.efl.obj, EVAS_CALLBACK_DEL, seat_ptr_del, s);
}

static void
seat_ptr_set_cursor(struct wl_client *client, struct wl_resource *resource, uint32_t serial, struct wl_resource *surface_resource, int32_t x, int32_t y)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   Comp_Surface *cs = NULL;

   if (!s->active_client) return;
   if (surface_resource && (s->active_client != wl_resource_get_client(surface_resource))) return;
   if (s->ptr.enter_serial - serial > UINT32_MAX / 2) return;
   if (surface_resource)
     cs = wl_resource_get_user_data(surface_resource);
   if (cs && cs->role && (!cs->cursor))
     {
        wl_resource_post_error(surface_resource,
                               WL_POINTER_ERROR_ROLE, "surface already has role");
        return;
     }
   if (s->ptr.cursor.surface == cs) return;
   if (cs)
     {
        cs->cursor = 1;
        cs->pending.set_input = 1;
        eina_tiler_clear(cs->pending.input);
        evas_object_pass_events_set(cs->obj, 1);
     }
   if (s->ptr.cursor.surface)
     {
        s->ptr.cursor.surface->cursor = 0;
        s->ptr.cursor.surface->role = NULL;
     }

   if (s->ptr.in)
     {
        const Eina_List *l;
        Eo *dev;
        Ecore_Evas *ee = ecore_evas_ecore_evas_get(s->c->evas);

        EINA_LIST_FOREACH(evas_device_list(s->c->evas, s->dev), l, dev)
          if (evas_device_class_get(dev) == EVAS_DEVICE_CLASS_MOUSE)
            {
               if ((!s->ptr.efl.obj) && (!s->ptr.cursor.surface))
                 seat_ptr_inherit(s, dev);
               ecore_evas_cursor_device_unset(ee, dev);
               if (cs)
                 {
                    cs->role = cs->res;
                    ecore_evas_object_cursor_device_set(ee, dev, cs->obj, EVAS_LAYER_MAX, x, y);
                 }
            }
     }
   if (cs)
     evas_object_smart_member_del(cs->obj);
   s->ptr.cursor.surface = cs;
   s->ptr.cursor.x = x;
   s->ptr.cursor.y = y;
}

static const struct wl_pointer_interface seat_ptr_interface =
{
   seat_ptr_set_cursor,
   resource_destroy
};

static void
seat_ptr_unbind(struct wl_resource *resource)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_client *client = wl_resource_get_client(resource);

   eina_hash_list_remove(s->ptr.resources, &client, resource);
}

static void
seat_ptr_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_resource *res;
   Comp_Surface *cs;
   int x, y;

   res = wl_resource_create(client, &wl_pointer_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(res, &seat_ptr_interface, s, seat_ptr_unbind);
   if (!s->ptr.resources) s->ptr.resources = eina_hash_pointer_new(NULL);
   eina_hash_list_append(s->ptr.resources, &client, res);
   if (!s->ptr.enter) return;
   cs = s->ptr.enter;
   if (wl_resource_get_client(cs->res) != client) return;
   s->ptr.enter = NULL;
   evas_pointer_canvas_xy_get(s->c->evas, &x, &y);
   comp_surface_send_pointer_enter(cs, s, x, y);
}

static const struct wl_touch_interface seat_tch_interface =
{
   resource_destroy
};

static void
seat_tch_unbind(struct wl_resource *resource)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_client *client = wl_resource_get_client(resource);

   eina_hash_list_remove(s->tch.resources, &client, resource);
}

static void
seat_tch_create(struct wl_client *client, struct wl_resource *resource, uint32_t id)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);
   struct wl_resource *res;

   res = wl_resource_create(client, &wl_touch_interface, wl_resource_get_version(resource), id);
   wl_resource_set_implementation(res, &seat_tch_interface, s, seat_tch_unbind);
   if (!s->tch.resources) s->tch.resources = eina_hash_pointer_new(NULL);
   eina_hash_list_append(s->tch.resources, &client, res);
}

static const struct wl_seat_interface seat_interface =
{
   seat_ptr_create,
   seat_kbd_create,
   seat_tch_create,
   resource_destroy,
};

static void
seat_unbind(struct wl_resource *resource)
{
   Comp_Seat *s = wl_resource_get_user_data(resource);

   s->resources = eina_list_remove(s->resources, resource);
}

static void
seat_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   struct wl_resource *res;
   Comp_Seat *s = data;

   if (!client_allowed_check(s->c, client)) return;
   res = wl_resource_create(client, &wl_seat_interface, version, id);
   s->resources = eina_list_append(s->resources, res);
   if (s->c->active_surface)
     s->active_client = wl_resource_get_client(s->c->active_surface->res);

   wl_resource_set_implementation(res, &seat_interface, s, seat_unbind);

   seat_update_caps(s, res);
   if (s->name && (version >= WL_SEAT_NAME_SINCE_VERSION))
     wl_seat_send_name(res, s->name);
}

static void
seat_resource_hash_free(Eina_Hash *h)
{
   Eina_Iterator *it;
   Eina_List *l;
   void **key;

   if (!h) return;
   while (eina_hash_population(h))
     {
        it = eina_hash_iterator_key_new(h);
        EINA_ITERATOR_FOREACH(it, key)
          {
             struct wl_resource *res;
             l = eina_hash_set(h, key, NULL);

             EINA_LIST_FREE(l, res)
               wl_resource_destroy(res);
             break;
          }
        eina_iterator_free(it);
     }
   eina_hash_free(h);
}

static void
seat_kbd_destroy(Comp_Seat *s)
{
   if (s->kbd.external) return;
#ifdef HAVE_ECORE_X
   if (!x11_kbd_keymap)
     {
#endif
        if (s->kbd.state) xkb_state_unref(s->kbd.state);
        if (s->kbd.keymap) xkb_keymap_unref(s->kbd.keymap);
        if (s->kbd.context) xkb_context_unref(s->kbd.context);
#ifdef HAVE_ECORE_X
     }
#endif
   s->kbd.keymap_str = NULL;
   s->kbd.keymap_str_size = 0;
   wl_array_release(&s->kbd.keys);
}

static void
seat_destroy(Comp_Seat *s)
{
   Eina_Stringshare *type;
   seat_resource_hash_free(s->kbd.resources);
   seat_resource_hash_free(s->ptr.resources);
   seat_resource_hash_free(s->tch.resources);
   while (s->resources)
     wl_resource_destroy(eina_list_data_get(s->resources));
   eina_stringshare_del(s->name);
   seat_kbd_destroy(s);
   efl_unref(s->dev);
   s->c->seats = eina_inlist_remove(s->c->seats, EINA_INLIST_GET(s));
   eina_hash_free(s->data_devices);
   EINA_LIST_FREE(s->drag.x11_types, type) eina_stringshare_del(type);
   while (s->ptr.events)
     {
        Input_Sequence *ev = EINA_INLIST_CONTAINER_GET(s->ptr.events, Input_Sequence);
        s->ptr.events = eina_inlist_remove(s->ptr.events, s->ptr.events);
        free(ev);
     }
   while (s->tch.events)
     {
        Input_Sequence *ev = EINA_INLIST_CONTAINER_GET(s->tch.events, Input_Sequence);
        s->tch.events = eina_inlist_remove(s->tch.events, s->tch.events);
        free(ev);
     }
   wl_global_destroy(s->global);
   free(s);

}

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

static void
comp_gl_shutdown(Comp *c)
{
   if (c->glapi && c->glapi->evasglUnbindWaylandDisplay)
     c->glapi->evasglUnbindWaylandDisplay(c->gl, c->display);
   if (c->glsfc)
     evas_gl_surface_destroy(c->gl, c->glsfc);
   if (c->glctx)
     evas_gl_context_destroy(c->gl, c->glctx);
   evas_gl_free(c->gl);
   evas_gl_config_free(c->glcfg);
   c->glsfc = NULL;
   c->glctx = NULL;
   c->glcfg = NULL;
   c->gl = NULL;
}

static void
comp_gl_init(Comp *c)
{
   c->glctx = evas_gl_context_create(c->gl, NULL);
   if (!c->glctx) goto end;
   c->glcfg = evas_gl_config_new();
   if (!c->glcfg) goto end;
   c->glsfc = evas_gl_surface_create(c->gl, c->glcfg, 1, 1);
   if (!c->glsfc) goto end;
   if (!evas_gl_make_current(c->gl, c->glsfc, c->glctx)) goto end;
   c->glapi = evas_gl_context_api_get(c->gl, c->glctx);
   if (c->glapi->evasglBindWaylandDisplay &&
       c->glapi->evasglBindWaylandDisplay(c->gl, c->display))
     return;
end:
   comp_gl_shutdown(c);
}

static void
comp_render_pre(Comp *c, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs;
   Eina_List *l, *ll;

   c->rendering = 1;
   EINA_LIST_FOREACH_SAFE(c->render_queue, l, ll, cs)
     {
        Comp_Buffer *buffer;
//if (cs->subsurface) fprintf(stderr, "RENDER PRE\n");

        comp_surface_buffer_detach(&cs->buffer[1]);
        cs->buffer[1] = cs->buffer[0];
        cs->buffer[0] = NULL;
        cs->render_queue = 0;
        buffer = cs->buffer[1];

        if (!buffer)
          {
             c->render_queue = eina_list_remove_list(c->render_queue, l);
             evas_object_image_pixels_dirty_set(cs->img, 0);
             continue;
          }
        //if (cs->proxies) fprintf(stderr, "RENDER %d\n", wl_resource_get_id(buffer->res));
        cs->post_render_queue = 1;

        if (cs->opaque_rects && (eina_array_count(cs->opaque_rects) == 1))
          {
             Eo *r = eina_array_data_get(cs->opaque_rects, 0);
             int x, y, w, h, ox, oy, ow, oh;

             evas_object_geometry_get(cs->img, &x, &y, &w, &h);
             evas_object_geometry_get(r, &ox, &oy, &ow, &oh);
             evas_object_image_border_set(cs->img, ox - x, ox + ow, oy - y, oy + oh);
             evas_object_image_border_center_fill_set(cs->img, EVAS_BORDER_FILL_SOLID);
          }
        else
          {
             evas_object_image_border_set(cs->img, 0, 0, 0, 0);
             evas_object_image_border_center_fill_set(cs->img, EVAS_BORDER_FILL_DEFAULT);
          }
        evas_object_image_alpha_set(cs->img, comp_surface_is_alpha(cs, buffer));
        evas_object_resize(cs->img, buffer->w, buffer->h);
        evas_object_image_size_set(cs->img, buffer->w, buffer->h);
     }
   c->post_render_queue = c->render_queue;
   c->render_queue = NULL;
}

static void
comp_render_pre_proxied(Eo *o, Evas *e, void *event_info)
{
   Comp_Surface *cs = evas_object_data_get(o, "comp_surface");
   EINA_SAFETY_ON_NULL_RETURN(cs);
   Comp_Buffer *buffer = cs->buffer[!cs->render_queue];

   //fprintf(stderr, "PROXY RENDER_PRE %d\n", buffer ? wl_resource_get_id(buffer->res) : -1);
   if (buffer)
     {
        buffer->renders = eina_list_remove(buffer->renders, e);
        buffer->post_renders = eina_list_append(buffer->post_renders, e);
        evas_object_image_size_set(o, buffer->w, buffer->h);
        evas_object_image_alpha_set(o, comp_surface_is_alpha(cs, buffer));
        evas_object_resize(o, buffer->w, buffer->h);
     }
   else
     evas_object_image_pixels_dirty_set(o, 0);
}

static void
comp_render_post_proxied(Comp_Surface *cs, Evas *e, void *event_info)
{
   Comp_Buffer *buffer;

   buffer = cs->buffer[!cs->render_queue];
   //fprintf(stderr, "PROXY RENDER_POST %d\n", buffer ? wl_resource_get_id(buffer->res) : -1);
   buffer->post_renders = eina_list_remove(buffer->post_renders, e);
   if (buffer->post_renders) return;
   if (cs->buffer[0] || cs->dead) comp_surface_buffer_detach(&cs->buffer[1]);
   comp_surface_buffer_post_render(cs);
}

static void
comp_render_post(Comp *c, Evas *e, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs;
   Comp_Seat *s;

   c->rendering = 0;
   EINA_LIST_FREE(c->post_render_queue, cs)
     {
        //fprintf(stderr, "POST(%d)\n", wl_resource_get_id(cs->res));
        cs->buffer[1]->renders = eina_list_remove(cs->buffer[1]->renders, e);
        if ((!cs->buffer[1]->renders) && (!cs->buffer[1]->post_renders))
          {
             if (cs->buffer[0] || cs->dead) comp_surface_buffer_detach(&cs->buffer[1]);
             comp_surface_buffer_post_render(cs);
          }
        cs->post_render_queue = 0;
        if (!cs->dead) continue;
        if (cs->res) wl_resource_destroy(cs->res);
        else evas_object_del(cs->obj);
     }
   EINA_INLIST_FOREACH(c->seats, s)
     {
        Input_Sequence *ev;
        Eina_Inlist *l;

        EINA_INLIST_FOREACH_SAFE(s->ptr.events, l, ev)
          {
             if (!ev->up_serial) continue;
             if (!ev->pass++) continue;
             s->ptr.events = eina_inlist_remove(s->ptr.events, EINA_INLIST_GET(ev));
             free(ev);
          }
        EINA_INLIST_FOREACH_SAFE(s->tch.events, l, ev)
          {
             if (!ev->up_serial) continue;
             if (!ev->pass++) continue;
             s->tch.events = eina_inlist_remove(s->tch.events, EINA_INLIST_GET(ev));
             free(ev);
          }
     }
}

static void
comp_seat_selection_update(Comp_Seat *s)
{
   Ecore_Wl2_Offer *offer;
   Eina_Array *arr;
   unsigned int i;
   Eina_Array_Iterator it;
   char *type;

   if (!s->client_seat) return;
   s->selection_changed = 0;
   if (!s->selection_exists)
     {
        s->client_selection_serial = ecore_wl2_dnd_selection_clear(s->client_seat);
        return;
     }
   if (!s->seat) return; /* x11_fixes_selection_notify() */
   offer = ecore_wl2_dnd_selection_get(s->seat);
   if (!offer) return;
   arr = ecore_wl2_offer_mimes_get(offer);
   {
      char *types[eina_array_count(arr) + 1];

      EINA_ARRAY_ITER_NEXT(arr, i, type, it)
        types[i] = type;
      types[i] = NULL;
      s->client_selection_serial = ecore_wl2_dnd_selection_set(s->client_seat, (const char**)types);
   }
}

static void
seat_drag_offer_update(Comp_Seat *s)
{
   Eina_Array *arr;
   char **types;
   unsigned int i;
   Eina_Array_Iterator it;
   char *type;

    // drag.id = probably double proxied
   if (s->drag.id || (!s->client_offer)) return;
   arr = ecore_wl2_offer_mimes_get(s->client_offer);
   types = alloca(sizeof(void*) * (eina_array_count(arr) + 1));

   EINA_ARRAY_ITER_NEXT(arr, i, type, it)
     types[i] = type;
   types[i] = NULL;

   ecore_wl2_dnd_drag_types_set(s->client_seat, (const char**)types);
   ecore_wl2_dnd_set_actions(s->client_seat);
}

static void
seat_proxy_update(Comp_Seat *s)
{
   Eina_Iterator *it;
   Ecore_Wl2_Input *input;

   if (!s->name) return;
   it = ecore_wl2_display_inputs_get(s->c->client_disp);
   EINA_ITERATOR_FOREACH(it, input)
     {
        const char *name = ecore_wl2_input_name_get(input);

        if (!name) continue;
        if (!eina_streq(s->name, name)) continue;
        s->client_seat = input;
        if (s->selection_changed)
          comp_seat_selection_update(s);
        if (s->client_offer)
          seat_drag_offer_update(s);
        break;
     }
   eina_iterator_free(it);
}

static void
comp_seat_proxy_update(Comp_Seat *s)
{
   Eina_Iterator *it;
   Ecore_Wl2_Input *input;
   Ecore_Wl2_Window *win;
   int n = 0, i = 0;

   if (s->seat) return;
   win = ecore_evas_wayland2_window_get(ecore_evas_ecore_evas_get(s->c->evas));
   if (win)
     {
        Ecore_Wl2_Display *ewd = ecore_wl2_window_display_get(win);
        it = ecore_wl2_display_inputs_get(ewd);
        EINA_ITERATOR_FOREACH(it, input)
          {
             if (ecore_wl2_input_seat_id_get(input) == evas_device_seat_id_get(s->dev))
               break;
             n++;
          }
        eina_iterator_free(it);
     }
   it = ecore_wl2_display_inputs_get(s->c->parent_disp);
   EINA_ITERATOR_FOREACH(it, input)
     {
        if (!win)
          {
             if (eina_streq(ecore_wl2_input_name_get(input), evas_device_name_get(s->dev)))
               {
                  s->seat = input;
                  break;
               }
          }
        else if (i == n)
          {
             s->seat = input;
             break;
          }
        i++;
     }
   eina_iterator_free(it);
}

static void
comp_device_caps_update(Comp_Seat *s)
{
   const Eina_List *l;
   Eo *dev;
   Eina_Bool kbd;

   if (!s) return;
   kbd = s->keyboard;
   s->keyboard = s->touch = s->pointer = 0;
   EINA_LIST_FOREACH(evas_device_list(s->c->evas, s->dev), l, dev)
     {
        Evas_Device_Class clas = evas_device_class_get(dev);
        s->keyboard |= clas == EVAS_DEVICE_CLASS_KEYBOARD;
        s->touch |= clas == EVAS_DEVICE_CLASS_TOUCH;
        s->pointer |= (clas == EVAS_DEVICE_CLASS_MOUSE ||
                     clas == EVAS_DEVICE_CLASS_TOUCH ||
                     clas == EVAS_DEVICE_CLASS_PEN ||
                     clas == EVAS_DEVICE_CLASS_POINTER ||
                     clas == EVAS_DEVICE_CLASS_WAND);
     }
   if (s->keyboard != kbd)
     {
        if (s->kbd.external)
          seat_kbd_external_init(s);
        else
          {
             if (s->keyboard)
               {
#ifdef HAVE_ECORE_X
                  if ((!s->c->parent_disp) && ecore_x_display_get())
                    x11_kbd_apply(s);
                  else
#endif
                  seat_keymap_create(s);
                  seat_kbd_repeat_rate_update(s);
               }
             else
               {
                  xkb_keymap_unref(s->kbd.keymap);
                  s->kbd.keymap = NULL;
               }
             seat_keymap_update(s);
             s->keyboard = !!s->kbd.state;
          }
     }
   seat_update_caps(s, NULL);
}

static void
comp_seats_proxy(Comp *c)
{
   const Eina_List *l, *ll;
   Eo *dev;

   l = evas_device_list(c->evas, NULL);
   EINA_LIST_FOREACH(l, ll, dev)
     {
        Comp_Seat *s;
        Eina_Bool skip = EINA_FALSE;

        if (evas_device_class_get(dev) != EVAS_DEVICE_CLASS_SEAT) continue;
        EINA_INLIST_FOREACH(c->seats, s)
          if (s->dev == dev)
            {
               skip = EINA_TRUE;
               if (c->parent_disp)
                 comp_seat_proxy_update(s);
               break;
            }
        if (skip) continue;

        s = calloc(1, sizeof(Comp_Seat));
        s->c = c;
        s->dev = dev;
        efl_ref(s->dev);
        if (c->parent_disp)
          comp_seat_proxy_update(s);
        s->name = eina_stringshare_ref(evas_device_name_get(dev));
        s->data_devices = eina_hash_pointer_new(NULL);
        c->seats = eina_inlist_append(c->seats, EINA_INLIST_GET(s));
        wl_array_init(&s->kbd.keys);
        if (s->seat)
          {
             Ecore_Wl2_Seat_Capabilities caps = ecore_wl2_input_seat_capabilities_get(s->seat);
             s->keyboard = !!(caps & ECORE_WL2_SEAT_CAPABILITIES_KEYBOARD);
             s->pointer = !!(caps & ECORE_WL2_SEAT_CAPABILITIES_POINTER);
             s->touch = !!(caps & ECORE_WL2_SEAT_CAPABILITIES_TOUCH);
             if (s->keyboard)
               {
                  if (s->kbd.external)
                    seat_kbd_external_init(s);
                  else
                    {
                       if (s->seat)
                         {
                            s->kbd.keymap = ecore_wl2_input_keymap_get(s->seat);
                            if (s->kbd.keymap) xkb_keymap_ref(s->kbd.keymap);
                         }
                       else
                         {
#ifdef HAVE_ECORE_X
                            if ((!s->c->parent_disp) && ecore_x_display_get())
                              x11_kbd_apply(s);
                            else
#endif
                            seat_keymap_create(s);
                         }
                       seat_keymap_update(s);
                       seat_kbd_repeat_rate_update(s);
                       s->keyboard = !!s->kbd.state;
                    }
               }

          }
        else if (!c->parent_disp)
          comp_device_caps_update(s);
        s->global = wl_global_create(c->display, &wl_seat_interface, 4, s, seat_bind);
        efl_event_callback_call(s->c->obj, EFL_CANVAS_WL_EVENT_SEAT_ADDED, dev);
        if (ecore_wl2_display_sync_is_done(c->client_disp))
          seat_proxy_update(s);
     }
}

static void
comp_wayland_features_init(Comp *c)
{
   if (ecore_wl2_display_dmabuf_get(c->parent_disp))
     linux_dmabuf_setup(c->display, c);
}

static Eina_Bool
comp_sync_done(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Sync_Done *ev)
{
   Eina_List *l;
   Comp *c;

   EINA_LIST_FOREACH(comps, l, c)
     {
        if (c->parent_disp == ev->display)
          {
             c->client_disp = ecore_wl2_display_connect(c->env);
             if (!c->seats)
               comp_seats_proxy(c);
             comp_wayland_features_init(c);
          }
        else if (c->client_disp == ev->display)
          {
             Eina_Iterator *it;
             Ecore_Wl2_Input *input;

             it = ecore_wl2_display_inputs_get(c->client_disp);
             EINA_ITERATOR_FOREACH(it, input)
               {
                  Comp_Seat *s;
                  EINA_INLIST_FOREACH(c->seats, s)
                    {
                       if (!eina_streq(s->name, ecore_wl2_input_name_get(input)))
                         continue;
                       s->client_seat = input;
                       if (s->selection_changed)
                         comp_seat_selection_update(s);
                       if (s->client_offer)
                         seat_drag_offer_update(s);
                       break;
                    }
               }
             eina_iterator_free(it);
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_seat_selection_changed(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Seat_Selection *ev)
{
   Eina_List *l;
   Comp *c;
   Comp_Seat *s;

   EINA_LIST_FOREACH(comps, l, c)
     if (c->parent_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         {
            if (s->seat && (ecore_wl2_input_seat_id_get(s->seat) != ev->seat)) continue;
            s->selection_changed = 1;
            s->selection_exists = !!ecore_wl2_dnd_selection_get(s->seat);
            if (!s->focused) continue;
            comp_seat_selection_update(s);
         }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_seat_caps_handler(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Seat_Capabilities *ev)
{
   Eina_List *l;
   Comp *c;
   Comp_Seat *s;

   EINA_LIST_FOREACH(comps, l, c)
     if (c->parent_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         {
            if (ecore_wl2_input_seat_id_get(s->seat) != ev->id) continue;
            s->pointer = ev->pointer_enabled;
            if (s->keyboard != ev->keyboard_enabled)
              {
                 if (s->kbd.external)
                   seat_kbd_external_init(s);
                 else
                   {
                      if (ev->keyboard_enabled)
                        {
                           s->kbd.keymap = ecore_wl2_input_keymap_get(s->seat);
                           if (s->kbd.keymap) xkb_keymap_ref(s->kbd.keymap);
                           seat_kbd_repeat_rate_update(s);
                        }
                      else
                        {
                           xkb_keymap_unref(s->kbd.keymap);
                           s->kbd.keymap = NULL;
                        }
                      seat_keymap_update(s);
                   }
              }
            s->keyboard = !!ev->keyboard_enabled && !!s->kbd.state;
            s->touch = ev->touch_enabled;
            seat_update_caps(s, NULL);
         }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_seat_name_changed(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Seat_Name *ev)
{
   Eina_List *l;
   Comp *c;
   Comp_Seat *s;

   EINA_LIST_FOREACH(comps, l, c)
     if (c->parent_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         {
            Eina_List *lll;
            struct wl_resource *res;

            if (ecore_wl2_input_seat_id_get(s->seat) != ev->id) continue;
            eina_stringshare_replace(&s->name, ev->name);
            EINA_LIST_FOREACH(s->resources, lll, res)
              if (wl_resource_get_version(res) >= WL_SEAT_NAME_SINCE_VERSION)
                wl_seat_send_name(res, s->name);
            seat_proxy_update(s);
            break;
         }
     else if (c->client_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         if (!s->client_seat) seat_proxy_update(s);
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_seat_keymap_changed(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Seat_Keymap_Changed *ev)
{
   Eina_List *l;
   Comp *c;
   Comp_Seat *s;

   EINA_LIST_FOREACH(comps, l, c)
     if (c->parent_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         {
            struct xkb_keymap *keymap;

            if (s->kbd.external) continue;
            if (ecore_wl2_input_seat_id_get(s->seat) != ev->id) continue;

            if (s->kbd.keymap) xkb_map_unref(s->kbd.keymap);
            s->kbd.keymap = NULL;
            keymap = ecore_wl2_input_keymap_get(s->seat);
            if (keymap) s->kbd.keymap = xkb_keymap_ref(keymap);
            seat_keymap_update(s);
            if (s->keyboard != (!!s->kbd.state))
              {
                 s->keyboard = !!s->kbd.state;
                 seat_update_caps(s, NULL);
              }
         }
   return ECORE_CALLBACK_RENEW;
}

static void
seat_kbd_repeat_rate_send(Comp_Seat *s)
{
   Eina_List *ll, *lll;
   struct wl_resource *res;
   Eina_Iterator *it;

   it = eina_hash_iterator_data_new(s->kbd.resources);
   EINA_ITERATOR_FOREACH(it, ll)
     EINA_LIST_FOREACH(ll, lll, res)
       if (wl_resource_get_version(res) >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)
         wl_keyboard_send_repeat_info(res, s->kbd.repeat_rate, s->kbd.repeat_delay);
   eina_iterator_free(it);
}

static Eina_Bool
comp_seat_keyboard_repeat_changed(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Seat_Keyboard_Repeat_Changed *ev)
{
   Eina_List *l;
   Comp *c;
   Comp_Seat *s;

   EINA_LIST_FOREACH(comps, l, c)
     if (c->parent_disp == ev->display)
       EINA_INLIST_FOREACH(c->seats, s)
         {
            if (ecore_wl2_input_seat_id_get(s->seat) != ev->id) continue;
            if (s->kbd.external) continue;

            seat_kbd_repeat_rate_update(s);
            seat_kbd_repeat_rate_send(s);
         }
   return ECORE_CALLBACK_RENEW;
}

static void
comp_seat_key_send(Comp_Seat *s, xkb_keycode_t key, int dir, unsigned int timestamp, Eina_Bool mods)
{
   Eina_List *l, *ll;
   struct wl_resource *res;
   uint32_t serial = wl_display_next_serial(s->c->display);
   uint32_t wl[] = { WL_KEYBOARD_KEY_STATE_PRESSED, WL_KEYBOARD_KEY_STATE_RELEASED };

   l = seat_kbd_active_resources_get(s);

   EINA_LIST_FOREACH(l, ll, res)
     {
        wl_keyboard_send_key(res, serial, timestamp, key, wl[dir]);
        if (mods) comp_seat_send_modifiers(s, res, serial);
     }
}

static void
comp_seat_key_update(Comp_Seat *s, xkb_keycode_t key, int dir, unsigned int timestamp)
{
   uint32_t xkb[] = { XKB_KEY_DOWN, XKB_KEY_UP };
   Eina_Bool mods = EINA_FALSE;

   if (s->kbd.external || xkb_state_update_key(s->kbd.state, key + 8, xkb[dir]))
     mods = seat_kbd_mods_update(s);
   if (s->focused)
     comp_seat_key_send(s, key, dir, timestamp, mods);
}

static Eina_Bool
comp_key_down(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Key *ev)
{
   uint32_t keycode;
   Comp *c;
   Eina_List *l;
   Comp_Seat *s;
   const Eo *dev = evas_device_parent_get(ev->dev);

   keycode = (ev->keycode - 8);
   EINA_LIST_FOREACH(comps, l, c)
     EINA_INLIST_FOREACH(c->seats, s)
       {
          if (c->parent_disp && (dev != s->dev)) continue;

          if (s->kbd.external)
            {
               /* only doing passthrough in external mode */
               if (!s->focused) return ECORE_CALLBACK_RENEW;
            }
          else
            {
               uint32_t *end, *k;

               end = (uint32_t*)s->kbd.keys.data + (s->kbd.keys.size / sizeof(uint32_t));
               for (k = s->kbd.keys.data; k < end; k++)
                 if (*k == keycode) return ECORE_CALLBACK_RENEW;

               s->kbd.keys.size = (char*)end - (char*)s->kbd.keys.data;
               k = wl_array_add(&s->kbd.keys, sizeof(uint32_t));
               *k = keycode;
            }
          comp_seat_key_update(s, keycode, 0, ev->timestamp);
       }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_key_up(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Event_Key *ev)
{
   uint32_t keycode;
   Comp *c;
   Eina_List *l;
   Comp_Seat *s;
   const Eo *dev = evas_device_parent_get(ev->dev);

   keycode = (ev->keycode - 8);
   EINA_LIST_FOREACH(comps, l, c)
     EINA_INLIST_FOREACH(c->seats, s)
       {
          if (c->parent_disp && (dev != s->dev)) continue;

          if (s->kbd.external)
            {
               /* only doing passthrough in external mode */
               if (!s->focused) return ECORE_CALLBACK_RENEW;
            }
          else
            {
               uint32_t *end, *k;

               end = (uint32_t*)s->kbd.keys.data + (s->kbd.keys.size / sizeof(uint32_t));

               for (k = s->kbd.keys.data; k < end; k++)
                 if (*k == keycode)
                   {
                      *k = end[-1];
                      s->kbd.keys.size = (char*)end - (char*)s->kbd.keys.data - 1;
                      break;
                   }
            }
          comp_seat_key_update(s, keycode, 1, ev->timestamp);
       }
   return ECORE_CALLBACK_RENEW;
}

static void
comp_device_add(void *d, const Efl_Event *ev)
{
   Comp *c = d;

   if (efl_input_device_type_get(ev->info) == EFL_INPUT_DEVICE_TYPE_SEAT)
     comp_seats_proxy(c);
   else if (!c->parent_disp)
     comp_device_caps_update(comp_seat_find(c, ev->info));
}

static void
comp_device_del(void *d, const Efl_Event *ev)
{
   Comp *c = d;
   Eo *seat;
   Eina_Inlist *l;
   Comp_Seat *s;

   if (efl_input_device_type_get(ev->info) == EFL_INPUT_DEVICE_TYPE_SEAT)
     seat = ev->info;
   else
     seat = efl_input_device_seat_get(ev->info);
   EINA_INLIST_FOREACH_SAFE(c->seats, l, s)
     if (seat == s->dev)
       {
          if (ev->info == seat)
            seat_destroy(s);
          else if (!c->parent_disp)
            comp_device_caps_update(s);
       }
}

static Eina_Bool
comp_data_device_dnd_leave(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Dnd_Leave *ev)
{
   Comp *c;
   Comp_Seat *s;
   Eina_List *l;
   EINA_LIST_FOREACH(comps, l, c)
     {
        if (ev->display != c->parent_disp) continue;
        EINA_INLIST_FOREACH(c->seats, s)
          {
             if (ecore_wl2_input_seat_id_get(s->seat) != ev->seat) continue;
             if (s->drag.id) continue; // probably double proxied
             if (s->drag.source)
               wl_data_source_send_cancelled(s->drag.source->res);
             if (s->client_seat && s->drag.res)
               {
                  ecore_wl2_dnd_drag_end(s->client_seat);
                  s->client_offer = NULL;
               }
             if (s->drag.proxy_win)
               {
                  if (s->seat)
                    ecore_wl2_dnd_drag_end(s->seat);
                  ecore_evas_free(s->drag.proxy_win);
               }
             seat_drag_end(s);
             s->drag.source = NULL;
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_data_device_dnd_end(void *d, int t, Ecore_Wl2_Event_Data_Source_End *ev)
{
   Comp *c;
   Comp_Seat *s;
   Eina_List *l;

   EINA_LIST_FOREACH(comps, l, c)
     {
        if (ev->display != c->parent_disp) continue;
        EINA_INLIST_FOREACH(c->seats, s)
          {
             if (ecore_wl2_input_seat_id_get(s->seat) != ev->seat) continue;
             if (!s->drag.source) continue;
             if (s->drag.source->proxy_serial != ev->serial) continue;
             if (ev->cancelled)
               wl_data_source_send_cancelled(s->drag.source->res);
             else
               data_source_notify_finish(s->drag.source);
             if (s->drag.proxy_win)
               {
                  if (s->seat)
                    ecore_wl2_dnd_drag_end(s->seat);
                  ecore_evas_free(s->drag.proxy_win);
               }
             seat_drag_end(s);
             s->drag.source = NULL;
             s->client_offer = NULL;
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_data_device_dnd_enter(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Dnd_Enter *ev)
{
   Comp *c;
   Comp_Seat *s;
   Eina_List *l;

   EINA_LIST_FOREACH(comps, l, c)
     {
        if (ev->display != c->parent_disp) continue;
        EINA_INLIST_FOREACH(c->seats, s)
          {
             if (ecore_wl2_input_seat_id_get(s->seat) != ev->seat) continue;
             s->client_offer = ev->offer;
             seat_drag_offer_update(s);
          }
     }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_data_device_dnd_motion(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Dnd_Motion *ev)
{
   Comp *c;
   Comp_Seat *s;
   Eina_List *l;
   int x, y, w, h, ex, ey;
   Eina_Bool found = EINA_FALSE;

   EINA_LIST_FOREACH(comps, l, c)
     {
        int fx, fy;

        if (ev->display != c->parent_disp) continue;
        evas_output_framespace_get(c->evas, &fx, &fy, NULL, NULL);
        ex = ev->x - fx;
        ey = ev->y - fy;
        evas_object_geometry_get(c->obj, &x, &y, &w, &h);
        if (!COORDS_INSIDE(ex, ey, x, y, w, h)) continue;
        found = EINA_TRUE;
        break;
     }
   if (!found) return ECORE_CALLBACK_RENEW;

   EINA_INLIST_FOREACH(c->seats, s)
     if (ecore_wl2_input_seat_id_get(s->seat) == ev->seat)
       {
          dnd_motion(s, ex, ey);
          break;
       }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
comp_data_device_dnd_drop(void *d EINA_UNUSED, int t EINA_UNUSED, Ecore_Wl2_Event_Dnd_Drop *ev)
{
   Comp *c;
   Comp_Seat *s;
   Eina_List *l;

   EINA_LIST_FOREACH(comps, l, c)
     {
        if (c->parent_disp != ev->display) continue;
        EINA_INLIST_FOREACH(c->seats, s)
          if (ecore_wl2_input_seat_id_get(s->seat) == ev->seat)
            {
               if (s->drag.enter)
                 {
                    drag_grab_button(s, 0, s->drag.id, WL_POINTER_BUTTON_STATE_RELEASED);
                    if (s->drag.proxy_win)
                      {
                         ecore_wl2_dnd_drag_end(s->seat);
                         ecore_evas_free(s->drag.proxy_win);
                      }
                    return ECORE_CALLBACK_RENEW;
                 }
            }
     }
   return ECORE_CALLBACK_RENEW;
}

static void
comp_handlers_init(void)
{
   Ecore_Event_Handler *h;

   h = ecore_event_handler_add(ECORE_WL2_EVENT_SEAT_CAPABILITIES_CHANGED,
     (Ecore_Event_Handler_Cb)comp_seat_caps_handler, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_SEAT_KEYMAP_CHANGED,
     (Ecore_Event_Handler_Cb)comp_seat_keymap_changed, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_SEAT_NAME_CHANGED,
     (Ecore_Event_Handler_Cb)comp_seat_name_changed, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_SYNC_DONE,
            (Ecore_Event_Handler_Cb)comp_sync_done, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_SEAT_KEYBOARD_REPEAT_CHANGED,
            (Ecore_Event_Handler_Cb)comp_seat_keyboard_repeat_changed, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_SEAT_SELECTION,
            (Ecore_Event_Handler_Cb)comp_seat_selection_changed, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_DND_MOTION,
            (Ecore_Event_Handler_Cb)comp_data_device_dnd_motion, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_DND_ENTER,
            (Ecore_Event_Handler_Cb)comp_data_device_dnd_enter, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_DND_LEAVE,
            (Ecore_Event_Handler_Cb)comp_data_device_dnd_leave, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_DATA_SOURCE_END,
            (Ecore_Event_Handler_Cb)comp_data_device_dnd_end, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_WL2_EVENT_DND_DROP,
            (Ecore_Event_Handler_Cb)comp_data_device_dnd_drop, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_EVENT_KEY_DOWN,
            (Ecore_Event_Handler_Cb)comp_key_down, NULL);
   handlers = eina_list_append(handlers, h);
   h = ecore_event_handler_add(ECORE_EVENT_KEY_UP,
            (Ecore_Event_Handler_Cb)comp_key_up, NULL);
   handlers = eina_list_append(handlers, h);
}

static void
comp_focus_in(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Comp *c = data;
   //Efl_Input_Focus_Data *ev = event_info;
   Comp_Seat *s;

   EINA_INLIST_FOREACH(c->seats, s)
     {
        Eina_List *l, *ll;
        struct wl_resource *res;
        uint32_t serial;

        //if (ev->device != s->dev) continue;
        if (evas_seat_focus_get(c->evas, s->dev) != c->obj) continue;
        s->focused = 1;
        if (s->selection_changed)
          comp_seat_selection_update(s);
        if ((!s->kbd.keymap) && (!s->kbd.state)) continue;
        if (s->kbd.external)
          {
             Eina_Bool mods = seat_kbd_mods_update(s);
             if (!s->focused) return;
             l = seat_kbd_active_resources_get(s);

             EINA_LIST_FOREACH(l, ll, res)
               {
                  uint32_t *end, *k;
                  serial = wl_display_next_serial(s->c->display);

                  end = (uint32_t*)s->kbd.keys_external->data + (s->kbd.keys_external->size / sizeof(uint32_t));
                  if (mods) comp_seat_send_modifiers(s, res, serial);

                  for (k = s->kbd.keys_external->data; k < end; k++)
                    comp_seat_key_send(s, *k, 0, 0, mods);
               }
             return;
          }
        if (!seat_kbd_mods_update(s)) continue;
        l = seat_kbd_active_resources_get(s);
        if (!l) continue;
        serial = wl_display_next_serial(s->c->display);
        EINA_LIST_FOREACH(l, ll, res)
          comp_seat_send_modifiers(s, res, serial);
     }
}

static void
comp_focus_out(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Comp *c = data;
   //Efl_Input_Focus_Data *ev = event_info;
   Comp_Seat *s;

   EINA_INLIST_FOREACH(c->seats, s)
     {
        //if (ev->device != s->dev) continue;
        if (evas_seat_focus_get(c->evas, s->dev) != c->obj)
          s->focused = 0;
     }
}

static void
comp_mouse_in(void *data, Evas *e, Eo *obj, void *event_info)
{
   Comp *c = data;
   Evas_Event_Mouse_In *ev = event_info;
   Comp_Seat *s;
   s = comp_seat_find(c, ev->dev);

   if (!s->event_propagate)
     {
        if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
     }
   s->event_propagate = 0;
   if (s->drag.proxy_win)
     {
        ecore_evas_free(s->drag.proxy_win);
#ifdef HAVE_ECORE_X
        if (ecore_x_display_get())
          {
             ecore_x_dnd_abort(ecore_evas_window_get(ecore_evas_ecore_evas_get(s->c->evas)));
             s->drag.x11_owner = 0;
          }
#endif
        if (s->ptr.cursor.surface)
          evas_object_show(s->ptr.cursor.surface->obj);
     }
   if (s->drag.surface)
     evas_object_show(s->drag.surface->obj);
   s->ptr.in = 1;
   seat_ptr_inherit(s, ev->dev);
   ecore_evas_cursor_device_unset(ecore_evas_ecore_evas_get(e), ev->dev);
   if (s->ptr.efl.obj) evas_object_hide(s->ptr.efl.obj);
   if (!s->ptr.cursor.surface) return;
   s->ptr.cursor.surface->role = s->ptr.cursor.surface->res;
   ecore_evas_object_cursor_device_set(ecore_evas_ecore_evas_get(e), ev->dev,
     s->ptr.cursor.surface->obj, EVAS_LAYER_MAX, s->ptr.cursor.x, s->ptr.cursor.y);
}

static void
comp_multi_move(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Comp *c = data;
   Evas_Event_Multi_Move *ev = event_info;
   Comp_Seat *s;
   int w, h;

   s = comp_seat_find(c, ev->dev);
   if (!s->event_propagate)
     {
        if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
     }
   s->event_propagate = 0;
   s->tch.pos.x = ev->cur.canvas.x;
   s->tch.pos.y = ev->cur.canvas.y;
   if ((!s->drag.tch) || (!s->drag.surface)) return;
   evas_object_geometry_get(s->drag.surface->obj, NULL, NULL, &w, &h);
   evas_object_move(s->drag.surface->obj, ev->cur.canvas.x, ev->cur.canvas.y);
}

static void
comp_mouse_move(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info)
{
   Comp *c = data;
   Evas_Event_Mouse_Move *ev = event_info;
   Comp_Seat *s;
   int w, h;

   s = comp_seat_find(c, ev->dev);
   if (!s->event_propagate)
     {
        if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
     }
   s->event_propagate = 0;
   s->ptr.pos.x = ev->cur.canvas.x;
   s->ptr.pos.y = ev->cur.canvas.y;
   if (s->drag.tch || (!s->drag.surface)) return;
   evas_object_geometry_get(s->drag.surface->obj, NULL, NULL, &w, &h);
   evas_object_move(s->drag.surface->obj, ev->cur.canvas.x, ev->cur.canvas.y);
}

static void
comp_mouse_out(void *data, Evas *e EINA_UNUSED, Eo *obj, void *event_info)
{
   Comp *c = data;
   Evas_Event_Mouse_Out *ev = event_info;
   Comp_Seat *s;
   Eina_List *l;
   const char **types, *type;
   unsigned int i = 0;

   s = comp_seat_find(c, ev->dev);
   if (!s->event_propagate)
     {
        if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
     }
   s->event_propagate = 0;
   if (!s->ptr.in) return;
   s->ptr.in = 0;
   ecore_evas_cursor_device_unset(ecore_evas_ecore_evas_get(e), ev->dev);
   if (s->ptr.efl.obj)
     {
        ecore_evas_object_cursor_device_set(ecore_evas_ecore_evas_get(e), ev->dev,
          s->ptr.efl.obj, s->ptr.efl.layer, s->ptr.efl.x, s->ptr.efl.y);
        seat_ptr_del(s, NULL, NULL, NULL);
     }
   if (s->ptr.cursor.surface)
     {
        s->ptr.cursor.surface->role = NULL;
        evas_object_hide(s->ptr.cursor.surface->obj);
     }
   if ((!s->drag.res) || (!s->drag.source) || s->drag.source->proxy) return;
   if (s->drag.enter) comp_surface_send_data_device_leave(s->drag.enter, s);
   if (s->drag.surface)
     seat_drag_proxy_win_add(s);
   types = alloca(sizeof(void*) * (eina_list_count(s->drag.source->mime_types) + 1));
   EINA_LIST_FOREACH(s->drag.source->mime_types, l, type)
     types[i++] = type;
   types[i] = NULL;
   if (s->c->parent_disp)
     {
        Ecore_Wl2_Window *win = NULL;

        if (s->drag.proxy_win)
          win = ecore_evas_wayland2_window_get(s->drag.proxy_win);
        ecore_wl2_dnd_drag_types_set(s->seat, types);
        s->drag.source->proxy_serial = ecore_wl2_dnd_drag_start(s->seat,
          ecore_evas_wayland2_window_get(ecore_evas_ecore_evas_get(s->c->evas)),
          win);
        s->drag.source->proxy_send_handler =
          ecore_event_handler_add(ECORE_WL2_EVENT_DATA_SOURCE_SEND,
                           data_device_proxy_send_send, s->drag.source);
     }
#ifdef HAVE_ECORE_X
   else if (ecore_x_display_get())
     {
        Ecore_Window win = ecore_evas_window_get(ecore_evas_ecore_evas_get(s->c->evas));
        Ecore_Window xwin = ecore_evas_window_get(s->drag.proxy_win);
        Ecore_X_Atom actions[] = { ECORE_X_DND_ACTION_MOVE, ECORE_X_DND_ACTION_COPY };

        if (s->drag.proxy_win && s->drag.surface)
          {
             int x, y, ex, ey;
             ecore_evas_geometry_get(ecore_evas_ecore_evas_get(s->c->evas), &ex, &ey, NULL, NULL);
             evas_object_geometry_get(s->drag.surface->obj, &x, &y, NULL, NULL);
             x += ex, y += ey;
             ecore_x_window_ignore_set(xwin, 1);
             ecore_evas_move(s->drag.proxy_win, x, y);
          }
        s->drag.x11_owner = win;

        ecore_x_dnd_types_set(win, types, i);
        ecore_x_dnd_actions_set(win, actions, EINA_C_ARRAY_LENGTH(actions));
        ecore_x_dnd_self_begin(win, NULL, 0);
        ecore_x_dnd_callback_pos_update_set(x11_dnd_move, s->drag.proxy_win);
     }
#endif
}

EFL_CALLBACKS_ARRAY_DEFINE(comp_device_cbs,
  { EFL_CANVAS_SCENE_EVENT_DEVICE_ADDED, comp_device_add },
  { EFL_CANVAS_SCENE_EVENT_DEVICE_REMOVED, comp_device_del });

static void
hints_set_aspect(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface, uint32_t width, uint32_t height, uint32_t aspect)
{
   Comp_Surface *cs = wl_resource_get_user_data(surface);
   evas_object_size_hint_aspect_set(cs->obj, aspect, width, height);
   if (cs == cs->c->active_surface)
     shell_surface_aspect_update(cs);
}

static void
hints_set_weight(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface, int w, int h)
{
   Comp_Surface *cs = wl_resource_get_user_data(surface);
   cs->hint_set_weight = 1;
   evas_object_size_hint_weight_set(cs->obj, w / 100., h / 100.);
}

static const struct efl_hints_interface hints_interface =
{
   hints_set_aspect,
   hints_set_weight,
};

static void
efl_hints_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
{
   struct wl_resource *res;

   if (!client_allowed_check(data, client)) return;
   res = wl_resource_create(client, &efl_hints_interface, version, id);
   wl_resource_set_implementation(res, &hints_interface, data, NULL);
}

EOLIAN static Eo *
_efl_canvas_wl_efl_object_constructor(Eo *obj, Comp *c)
{
   efl_canvas_group_clipped_set(obj, EINA_TRUE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(!ecore_wl2_init(), NULL);
   return efl_constructor(efl_super(obj, MY_CLASS));
}

EOLIAN static void
_efl_canvas_wl_efl_canvas_group_group_add(Eo *obj, Comp *c)
{
   char *env, *dbg = NULL;

   efl_canvas_group_add(efl_super(obj, MY_CLASS));
   c->wayland_time_base = ecore_loop_time_get();
   c->obj = obj;
   c->flags = EFL_EXE_FLAGS_TERM_WITH_PARENT;
   env = getenv("WAYLAND_DISPLAY");
   if (env) env = strdup(env);

   if (getenv("EFL_CANVAS_WL_DEBUG"))
     {
        dbg = eina_strdup(getenv("WAYLAND_DEBUG"));
        setenv("WAYLAND_DEBUG", "1", 1);
     }
   c->disp = ecore_wl2_display_create(NULL);
   if (getenv("EFL_CANVAS_WL_DEBUG"))
     {
        if (dbg) setenv("WAYLAND_DEBUG", dbg, 1);
        else unsetenv("WAYLAND_DEBUG");
        free(dbg);
     }
   c->env = eina_strdup(getenv("WAYLAND_DISPLAY"));
   if (env) setenv("WAYLAND_DISPLAY", env, 1);
   else unsetenv("WAYLAND_DISPLAY");
   c->display = ecore_wl2_display_get(c->disp);
   c->client_surfaces = eina_hash_pointer_new(NULL);
   c->evas = efl_provider_find(obj, EVAS_CANVAS_CLASS);
   c->clip = (Eo*)efl_canvas_group_clipper_get(obj);
   efl_gfx_entity_geometry_set(c->clip, efl_gfx_entity_geometry_get(obj));
   c->events = evas_object_rectangle_add(c->evas);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_IN, comp_mouse_in, c);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_MOVE, comp_mouse_move, c);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MULTI_MOVE, comp_multi_move, c);
   evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_OUT, comp_mouse_out, c);
   evas_object_color_set(c->events, 0, 0, 0, 0);
   evas_object_smart_member_add(c->events, obj);
   evas_object_show(c->events);
   evas_object_lower(c->events);

   wl_global_create(c->display, &wl_compositor_interface, 4, c, comp_bind);
   wl_global_create(c->display, &wl_subcompositor_interface, 1, c, subcomp_bind);
   wl_global_create(c->display, &wl_output_interface, 2, c, output_bind);
   wl_global_create(c->display, &xdg_wm_base_interface, 1, c, shell_bind);
   wl_global_create(c->display, &wl_data_device_manager_interface, 3, c, data_device_manager_bind);
   wl_global_create(c->display, &efl_hints_interface, 1, c, efl_hints_bind);
   wl_display_init_shm(c->display);

   if (env)
     {
        c->parent_disp = ecore_wl2_display_connect(env);
        if (ecore_wl2_display_sync_is_done(c->parent_disp))
          {
             c->client_disp = ecore_wl2_display_connect(c->env);
             comp_seats_proxy(c);
             comp_wayland_features_init(c);
          }
     }
   else
     {
        c->client_disp = ecore_wl2_display_connect(c->env);
        comp_seats_proxy(c);
#ifdef HAVE_ECORE_X
        if (ecore_x_display_get())
          {
             ecore_x_xkb_track_state();
             // if proxiedallowed
             ecore_x_dnd_aware_set(ecore_evas_window_get(ecore_evas_ecore_evas_get(c->evas)), EINA_TRUE);
             if (!comps) x11_init();
          }
#endif
     }

   c->gl = evas_gl_new(c->evas);
   if (c->gl) comp_gl_init(c);
   evas_event_callback_add(c->evas, EVAS_CALLBACK_RENDER_PRE, (Evas_Event_Cb)comp_render_pre, c);
   evas_event_callback_add(c->evas, EVAS_CALLBACK_RENDER_POST, (Evas_Event_Cb)comp_render_post, c);
   efl_event_callback_array_add(c->evas, comp_device_cbs(), c);

   evas_object_event_callback_add(c->obj, EVAS_CALLBACK_FOCUS_IN, comp_focus_in, c);
   evas_object_event_callback_add(c->obj, EVAS_CALLBACK_FOCUS_OUT, comp_focus_out, c);

   if (!comps) comp_handlers_init();
   comps = eina_list_append(comps, c);
   free(env);
}

EOLIAN static void
_efl_canvas_wl_efl_canvas_group_group_del(Eo *obj, Comp *c)
{
   evas_object_del(c->events);
   free(c->env);
   if (c->gl) comp_gl_shutdown(c);
   while (c->shells)
     {
        Shell_Data *sd = EINA_INLIST_CONTAINER_GET(c->shells, Shell_Data);
        wl_resource_destroy(sd->res);
     }
   while (c->seats)
     {
        Comp_Seat *s = EINA_INLIST_CONTAINER_GET(c->seats, Comp_Seat);
        seat_destroy(s);
     }
   while (c->surfaces)
     {
        Comp_Surface *cs = EINA_INLIST_CONTAINER_GET(c->surfaces, Comp_Surface);
        evas_object_del(cs->obj);
     }
   eina_hash_free(c->client_surfaces);
   if (c->parent_disp) ecore_wl2_display_disconnect(c->parent_disp);
   ecore_wl2_display_destroy(c->disp);
   ecore_wl2_display_disconnect(c->client_disp);

   evas_event_callback_del_full(c->evas, EVAS_CALLBACK_RENDER_PRE, (Evas_Event_Cb)comp_render_pre, c);
   evas_event_callback_del_full(c->evas, EVAS_CALLBACK_RENDER_POST, (Evas_Event_Cb)comp_render_post, c);
   efl_event_callback_array_del(c->evas, comp_device_cbs(), c);
   eina_hash_free(c->exes);
   comps = eina_list_remove(comps, c);
   if (!comps)
     {
        void *h;
        EINA_LIST_FREE(handlers, h)
          ecore_event_handler_del(h);
#ifdef HAVE_ECORE_X
        x11_shutdown();
#endif
     }
   ecore_wl2_shutdown();
   efl_canvas_group_del(efl_super(obj, MY_CLASS));
}

static void
_efl_canvas_wl_efl_gfx_entity_position_set(Eo *obj, Comp *c, Eina_Position2D pos)
{
   efl_gfx_entity_position_set(efl_super(obj, MY_CLASS), pos);
   efl_gfx_entity_position_set(c->clip, pos);
}

static void
_efl_canvas_wl_efl_gfx_entity_size_set(Eo *obj, Comp *c, Eina_Size2D sz)
{
   Eina_List *l;
   Comp_Surface *cs;
   struct wl_resource *res;
   int w = sz.w, h = sz.h;

   efl_gfx_entity_size_set(efl_super(obj, MY_CLASS), sz);
   evas_object_resize(c->clip, w, h);
   evas_object_resize(c->events, w, h);
   EINA_LIST_FOREACH(c->output_resources, l, res)
     output_resize(c, res);
   //fprintf(stderr, "COMP %dx%d\n", w, h);
   EINA_INLIST_FOREACH(c->surfaces, cs)
     if (cs->shell.surface && cs->role && (!cs->extracted))
       shell_surface_send_configure(cs);
}

static void
_efl_canvas_wl_efl_gfx_entity_visible_set(Eo *obj, Comp *c, Eina_Bool vis)
{
   Comp_Surface *cs;

   efl_gfx_entity_visible_set(efl_super(obj, MY_CLASS), vis);
   EINA_INLIST_FOREACH(c->surfaces, cs)
     if (vis)
       comp_surface_output_enter(cs);
     else
       comp_surface_output_leave(cs);
}

static void
exe_event_del(void *data, const Efl_Event *ev)
{
   Comp *c = data;
   int32_t pid = efl_exe_pid_get(ev->object);

   eina_hash_del_by_key(c->exes, &pid);
}

# ifdef __GNUC__
#  if __GNUC__ >= 4
__attribute__ ((visibility("hidden")))
# endif
#endif
Eina_Bool
comp_dmabuf_test(struct linux_dmabuf_buffer *dmabuf)
{
   Evas_Native_Surface ns;
   Eo *test;
   int size;
   void *data;
   Comp *c = dmabuf->compositor;

   if (c->gl)
     {
        Eina_Bool ret;
        ns.type = EVAS_NATIVE_SURFACE_WL_DMABUF;
        ns.version = EVAS_NATIVE_SURFACE_VERSION;
        ns.data.wl_dmabuf.attr = &dmabuf->attributes;
        ns.data.wl_dmabuf.resource = NULL;
        ns.data.wl_dmabuf.scanout.handler = NULL;
        ns.data.wl_dmabuf.scanout.data = NULL;
        test = evas_object_image_add(c->evas);
        evas_object_image_native_surface_set(test, &ns);
        ret = evas_object_image_load_error_get(test) == EVAS_LOAD_ERROR_NONE;
        evas_object_del(test);
        if (!ns.data.wl_dmabuf.attr) return EINA_FALSE;
        return ret;
     }

   /* TODO: Software rendering for multi-plane formats */
   if (dmabuf->attributes.n_planes != 1) return EINA_FALSE;
   if (dmabuf->attributes.format != DRM_FORMAT_ARGB8888 &&
       dmabuf->attributes.format != DRM_FORMAT_XRGB8888) return EINA_FALSE;

   /* This is only legit for ARGB8888 */
   size = dmabuf->attributes.height * dmabuf->attributes.stride[0];
   data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf->attributes.fd[0], 0);
   if (data == MAP_FAILED) return EINA_FALSE;
   munmap(data, size);

   return EINA_TRUE;
}

void
comp_dmabuf_formats_query(void *compositor EINA_UNUSED, int **formats EINA_UNUSED, int *num_formats)
{
   *num_formats = 0;
}

void
comp_dmabuf_modifiers_query(void *compositor EINA_UNUSED, int format EINA_UNUSED, uint64_t **modifiers EINA_UNUSED, int *num_modifiers)
{
   *num_modifiers = 0;
}

Eo *
comp_run(Eo *obj, Comp *c, const char *cmd, Efl_Exe_Flags flags)
{
   char *env, *disp, *gl = NULL;
   Efl_Exe *exe;

   if (!c->exes)
     c->exes = eina_hash_int32_new(NULL);
   disp = getenv("DISPLAY");
   if (disp) disp = strdup(disp);
   unsetenv("DISPLAY");
   env = getenv("WAYLAND_DISPLAY");
   if (env) env = strdup(env);
   setenv("WAYLAND_DISPLAY", c->env, 1);
   if (c->gl)
     {
        gl = getenv("ELM_ACCEL");
        if (gl) gl = strdup(gl);
        setenv("ELM_ACCEL", "gl", 1);
     }
   exe = efl_add(EFL_EXE_CLASS, obj,
     efl_core_command_line_command_string_set(efl_added, cmd),
     efl_exe_flags_set(efl_added, flags),
     efl_task_run(efl_added));
   if (disp) setenv("DISPLAY", disp, 1);
   if (env) setenv("WAYLAND_DISPLAY", env, 1);
   else unsetenv("WAYLAND_DISPLAY");
   if (c->gl)
     {
        if (gl) setenv("ELM_ACCEL", gl, 1);
        else unsetenv("ELM_ACCEL");
        free(gl);
     }
   free(env);
   free(disp);
   if (exe)
     {
        int32_t pid = efl_exe_pid_get(exe);
        eina_hash_add(c->exes, &pid, (void*)1);
        efl_event_callback_add(exe, EFL_TASK_EVENT_EXIT, exe_event_del, c);
     }
   return exe;
}

EOLIAN static Efl_Exe *
_efl_canvas_wl_run(Eo *obj, Comp *c, const char *cmd)
{
   return comp_run(obj, c, cmd, EFL_EXE_FLAGS_TERM_WITH_PARENT);
}

EOLIAN static Efl_Exe_Flags
_efl_canvas_wl_exec_flags_get(const Eo *obj, Comp *c)
{
   return c->flags;
}

EOLIAN static void
_efl_canvas_wl_exec_flags_set(Eo *obj, Comp *c, Efl_Exe_Flags flags)
{
   c->flags = flags;
}

EOLIAN static void
_efl_canvas_wl_allowed_pid_add(Eo *obj, Comp *c, int32_t pid)
{
   if (!c->exes)
     c->exes = eina_hash_int32_new(NULL);
   eina_hash_add(c->exes, &pid, (void*)1);
}

EOLIAN static void
_efl_canvas_wl_allowed_pid_del(Eo *obj, Comp *c, int32_t pid)
{
   if (!c->exes) return;
   eina_hash_del_by_key(c->exes, &pid);
}

EOLIAN static Eo *
_efl_canvas_wl_surface_next(Eo *obj, Comp *c)
{
   Comp_Surface *cs;

   if (c->surfaces_count < 2) return NULL;
   EINA_INLIST_REVERSE_FOREACH(c->surfaces, cs)
     {
        if (cs->shell.activated) continue;
        if ((!cs->role) || (!cs->shell.surface) || cs->dead) continue;
        cs->shell.activated = 1;
        shell_surface_send_configure(cs);
        return cs->obj;
     }

   return NULL;
}

EOLIAN static Eo *
_efl_canvas_wl_surface_prev(Eo *obj, Comp *c)
{
   Comp_Surface *cs;

   if (c->surfaces_count < 2) return NULL;
   EINA_INLIST_FOREACH(c->surfaces, cs)
     {
        if (cs->shell.activated) continue;
        if ((!cs->role) || (!cs->shell.surface) || cs->dead) continue;
        cs->shell.activated = 1;
        shell_surface_send_configure(cs);
        return cs->obj;
     }

   return NULL;
}

EOLIAN static Eo *
_efl_canvas_wl_active_surface_get(const Eo *obj, Comp *c)
{
   if (c->active_surface && (!c->active_surface->dead))
     return c->active_surface->obj;
   return NULL;
}

EOLIAN static Eina_Bool
_efl_canvas_wl_active_surface_set(Eo *obj, Comp *c, Eo *surface)
{
   Comp_Surface *cs = efl_data_scope_get(surface, EFL_CANVAS_WL_SURFACE_CLASS);
   EINA_SAFETY_ON_NULL_RETURN_VAL(cs, EINA_FALSE);
   if (cs->dead) return EINA_FALSE;
   if (c->active_surface == cs) return EINA_TRUE;
   /* can't activate a popup */
   if (cs->shell.popup) return EINA_FALSE;
   cs->shell.activated = 1;
   shell_surface_send_configure(cs);
   return EINA_TRUE;
}

EOLIAN static void
_efl_canvas_wl_rotation_get(const Eo *obj EINA_UNUSED, Comp *c, Efl_Canvas_Wl_Rotation *rotation, Eina_Bool *rtl)
{
   if (rotation) *rotation = c->rotation;
   if (rtl) *rtl = c->rtl;
}

EOLIAN static void
_efl_canvas_wl_rotation_set(Eo *obj EINA_UNUSED, Comp *c, Efl_Canvas_Wl_Rotation rot, Eina_Bool rtl)
{
   Eina_List *l;
   struct wl_resource *res;

   c->rtl = !!rtl;
   c->rotation = rot;
   EINA_LIST_FOREACH(c->output_resources, l, res)
     output_resize(c, res);
}

EOLIAN static double
_efl_canvas_wl_efl_gfx_entity_scale_get(const Eo *obj EINA_UNUSED, Comp *c)
{
   return c->scale;
}

EOLIAN static void
_efl_canvas_wl_efl_gfx_entity_scale_set(Eo *obj EINA_UNUSED, Comp *c, double scale)
{
   Eina_List *l;
   struct wl_resource *res;

   c->scale = scale;

   EINA_LIST_FOREACH(c->output_resources, l, res)
     if (wl_resource_get_version(res) >= WL_OUTPUT_SCALE_SINCE_VERSION)
       wl_output_send_scale(res, lround(c->scale));
}

EOLIAN static Eina_Bool
_efl_canvas_wl_aspect_propagate_get(const Eo *obj EINA_UNUSED, Comp *c)
{
   return c->aspect;
}

EOLIAN static void
_efl_canvas_wl_aspect_propagate_set(Eo *obj, Comp *c, Eina_Bool set)
{
   if (c->aspect == (!!set)) return;
   c->aspect = !!set;
   if (c->aspect)
     shell_surface_aspect_update(c->active_surface);
   else
     evas_object_size_hint_aspect_set(obj, EVAS_ASPECT_CONTROL_NONE, 0, 0);
}

EOLIAN static Eina_Bool
_efl_canvas_wl_minmax_propagate_get(const Eo *obj EINA_UNUSED, Comp *c)
{
   return c->minmax;
}

EOLIAN static void
_efl_canvas_wl_minmax_propagate_set(Eo *obj, Comp *c, Eina_Bool set)
{
   if (c->minmax == (!!set)) return;
   c->minmax = !!set;
   if (c->minmax)
     shell_surface_minmax_update(c->active_surface);
   else
     {
        efl_gfx_hint_size_restricted_min_set(obj, EINA_SIZE2D(0, 0));
        efl_gfx_hint_size_restricted_max_set(obj, EINA_SIZE2D(-1, -1));
     }
}

EOLIAN static Efl_Canvas_Wl_Wl_Global *
_efl_canvas_wl_global_add(Eo *obj, Comp *c, const Efl_Canvas_Wl_Wl_Interface *interface, uint32_t version, Efl_Canvas_Wl_Wl_Interface_Data *data, Efl_Canvas_Wl_Wl_Interface_Bind_Cb *bind_cb)
{
   EINA_SAFETY_ON_NULL_RETURN_VAL(interface, NULL);
   return (void*)wl_global_create(c->display, (void*)interface, version, (void*)data, (void*)bind_cb);
}

static void
extracted_focus(void *data, Evas *e EINA_UNUSED, Eo *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs = data;

   if (cs->dead) return;
   if (!cs->shell.popup)
     {
        cs->shell.activated = 1;
        shell_surface_send_configure(data);
     }
   evas_object_focus_set(cs->c->obj, 1);
}

static void
extracted_unfocus(void *data, Evas *e EINA_UNUSED, Eo *obj, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs = data;
   Eo *focus;

   if (cs->dead) return;
   focus = evas_focus_get(cs->c->evas);
   if ((!focus) || (focus == cs->c->obj)) return;
   cs->shell.activated = 0;
   shell_surface_send_configure(data);
}

static void
extracted_changed(void *data, Evas *e EINA_UNUSED, Eo *obj, void *event_info EINA_UNUSED)
{
   Comp_Surface *cs = data;

   if (cs->dead) return;
   shell_surface_send_configure(data);
}

static EOLIAN int32_t
_efl_canvas_wl_surface_pid_get(const Eo *surface, Comp_Surface *cs)
{
   int32_t pid;
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->dead, -1);
   wl_client_get_credentials(wl_resource_get_client(cs->res), &pid, NULL, NULL);
   return pid;
}

static EOLIAN Eina_Bool
_efl_canvas_wl_surface_extract(Eo *surface, Comp_Surface *cs)
{
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->extracted, EINA_FALSE);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->dead, EINA_FALSE);
   cs->extracted = 1;
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_RESIZE, extracted_changed, cs);
   if (!cs->shell.popup)
     evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_FOCUS_OUT, extracted_unfocus, cs);
   evas_object_event_callback_add(cs->obj, EVAS_CALLBACK_FOCUS_IN, extracted_focus, cs);
   evas_object_smart_member_del(surface);
   return EINA_TRUE;
}

Eo *
efl_canvas_wl_extracted_surface_object_find(void *surface_resource)
{
   Comp_Surface *cs = wl_resource_get_user_data(surface_resource);

   EINA_SAFETY_ON_NULL_RETURN_VAL(cs, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(!cs->extracted, NULL);
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->dead, NULL);

   return cs->obj;
}

static EOLIAN Eo *
_efl_canvas_wl_surface_parent_surface_get(const Eo *surface, Comp_Surface *cs)
{
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->dead, NULL);

   if (cs->parent && cs->role) return cs->parent->obj;
   return NULL;
}

static EOLIAN Eina_Bool
_efl_canvas_wl_surface_extracted_get(const Eo *surface EINA_UNUSED, Comp_Surface *cs)
{
   EINA_SAFETY_ON_TRUE_RETURN_VAL(cs->dead, EINA_FALSE);

   return cs->extracted;
}

static EOLIAN void
_efl_canvas_wl_seat_keymap_set(Eo *obj, Comp *c, Eo *seat, Efl_Canvas_Wl_Xkb_State *state, const char *str, Efl_Canvas_Wl_Wl_Array *key_array)
{
   Comp_Seat *s;

   EINA_INLIST_FOREACH(c->seats, s)
     {
        if (!seat) efl_canvas_wl_seat_keymap_set(obj, s->dev, state, str, key_array);
        else if (s->dev == seat) break;
     }
   if (!seat) return;
   EINA_SAFETY_ON_NULL_RETURN(s);
   seat_kbd_destroy(s);
   s->kbd.external = 1;
   s->kbd.keys_external = (void*)key_array;
   s->kbd.state = (void*)state;
   s->kbd.keymap_str = str;
   if (str)
     s->kbd.keymap_str_size = strlen(str) + 1;
   else
     s->kbd.keymap_str_size = 0;
   s->kbd.context = NULL;
   s->kbd.keymap = NULL;
   if (s->keyboard)
     seat_kbd_external_init(s);
}

static EOLIAN void
_efl_canvas_wl_seat_key_repeat_set(Eo *obj, Comp *c, Eo *seat, int repeat_rate, int repeat_delay)
{
   Comp_Seat *s;

   EINA_INLIST_FOREACH(c->seats, s)
     {
        if (!seat) efl_canvas_wl_seat_key_repeat_set(obj, s->dev, repeat_rate, repeat_delay);
        else if (s->dev == seat) break;
     }
   if (!seat) return;
   EINA_SAFETY_ON_NULL_RETURN(s);
   s->kbd.repeat_rate = repeat_rate;
   s->kbd.repeat_delay = repeat_delay;
   seat_kbd_repeat_rate_send(s);
}

#define EFL_CANVAS_WL_EXTRA_OPS \
  EFL_CANVAS_GROUP_ADD_DEL_OPS(efl_canvas_wl), \

#define EFL_CANVAS_WL_SURFACE_EXTRA_OPS \
  EFL_CANVAS_GROUP_ADD_DEL_OPS(efl_canvas_wl_surface), \

#include "efl_canvas_wl.eo.c"
#include "efl_canvas_wl_surface.eo.c"
