server: Add wtz-video-shell implementation 39/313439/1
authorSeunghun Lee <shiin.lee@samsung.com>
Tue, 21 May 2024 09:42:15 +0000 (18:42 +0900)
committerTizen Window System <tizen.windowsystem@gmail.com>
Wed, 26 Jun 2024 00:34:33 +0000 (09:34 +0900)
The Video Shell is a tizen extension to the wayland protocol. It is
designed to ensure synchronization between updates to the toplevel
surface content and the viewport of a video surface traditionally
rendered by an extenal process responsible for rendering video content.

The protocol mainly introduces the wtz_video_exported_viewport and
wtz_video_viewport_source.

The wtz_video_exported_viewport is intended to be created within the
application process and provides configuration options such as
destination size and transformation settings.  By extending
wtz_video_exported_viewport from wl_subsurface, applications can
leverage the synchronization capabilities of wl_subsurface to align
updates between the main surface content and video output.

The wtz_video_viewport_source, on the other hand, is meant to be
created by the process handling video content creation. Its output will
be adjusted (scaled up or down) based on the destination size and
transformations specified through wtz_video_exported_viewport.

This design approach is largely inspired by the wtz-foreign-shell
protocol.

Change-Id: I47c11150a9b4d120aa8bd9ead9791d53c26475e2

configure.ac
packaging/enlightenment.spec
src/bin/Makefile.mk
src/bin/server/e_compositor.c
src/bin/server/e_compositor_intern.h
src/bin/server/e_server.c
src/bin/server/e_server_intern.h
src/bin/server/e_video_shell.c [new file with mode: 0644]
src/bin/server/e_video_shell_intern.h [new file with mode: 0644]

index 511472fb353c2909f613d3e8bdc9a5bee7920e38..9262b1e9ed96b3f524ed4c91616389f1fc775bcf 100755 (executable)
@@ -378,7 +378,7 @@ if test "x${e_cv_want_wayland_only}" != "xno" || test "x${e_cv_want_wayland_clie
                                 tizen-launch-server tizen-surface-server tizen-dpms-server eom-server presentation-time-server
                                 tizen-hwc-server linux-explicit-synchronization-unstable-v1-server wtz-foreign-server
                                 wtz-shell-server relative-pointer-unstable-v1-server pointer-constraints-unstable-v1-server
-                                single-pixel-buffer-v1-server libdrm],
+                                single-pixel-buffer-v1-server libdrm wtz-video-shell-server],
     [
       have_wayland=yes
       AC_DEFINE_UNQUOTED([HAVE_WAYLAND],[1],[enable wayland support])
index 12cea0672190d6cd4aca3837a1e7d9bdc2dc88e0..6c1781fedb8fbc7e20c092a932011a9c3e3c124f 100644 (file)
@@ -62,6 +62,7 @@ BuildRequires:  pkgconfig(linux-explicit-synchronization-unstable-v1-server)
 BuildRequires:  pkgconfig(tizen-hwc-server)
 BuildRequires:  pkgconfig(wtz-foreign-server)
 BuildRequires:  pkgconfig(wtz-shell-server)
+BuildRequires:  pkgconfig(wtz-video-shell-server)
 BuildRequires:  pkgconfig(pointer-constraints-unstable-v1-server)
 BuildRequires:  pkgconfig(relative-pointer-unstable-v1-server)
 BuildRequires:  pkgconfig(glib-2.0)
index 7e0d45071d3f57f4d768c6f3810c9385e229ca7d..b7f117b39231ec9d12b4eac3b845b43008a7dc61 100644 (file)
@@ -175,6 +175,7 @@ src/bin/server/e_screen_rotation.c \
 src/bin/server/e_server.c \
 src/bin/server/e_tbm_gbm_server.c \
 src/bin/server/e_tizen_surface_shm.c \
+src/bin/server/e_video_shell.c \
 src/bin/windowmgr/services/e_service_gesture.c \
 src/bin/windowmgr/services/e_service_lockscreen.c \
 src/bin/windowmgr/services/e_service_quickpanel.c \
index 58f5e21d30eb79861cd28235ae94e970236a04e1..176e00811d5223cb1d909546f9b99b8f36b30506 100644 (file)
@@ -18,6 +18,7 @@
 
 typedef struct _E_Compositor E_Compositor;
 typedef struct _E_Subsurface E_Subsurface;
+typedef struct _E_Subsurface_View E_Subsurface_View;
 typedef struct _E_Frame_Callback E_Frame_Callback;
 
 struct _E_Compositor
@@ -49,19 +50,14 @@ struct _E_Surface
      {
         struct wl_signal destroy;
         struct wl_signal parent_destroy;
+        struct wl_signal commit;
      } events;
 };
 
-typedef struct
+struct _E_Subsurface_View
 {
    E_Client *ec;
-   E_Subsurface *subsurface;
-
-   struct
-     {
-        struct wl_signal reposition;
-     } events;
-} E_Subsurface_View;
+};
 
 struct _E_Subsurface
 {
@@ -87,6 +83,8 @@ struct _E_Subsurface
         struct wl_signal sync_precommit;
         struct wl_signal desync_precommit;
      } events;
+
+   Eina_Bool internal;
 };
 
 struct _E_Frame_Callback
@@ -123,6 +121,8 @@ static void _e_subsurface_cb_desync_precommit(struct wl_listener *listener, void
 static void _e_subsurface_cb_request_move(struct wl_listener *listener, void *data);
 static void _e_subsurface_cb_surface_destroy(struct wl_listener *listener, void *data);
 static void _e_subsurface_cb_parent_surface_destroy(struct wl_listener *listener, void *data);
+static void _e_subsurface_view_init(E_Subsurface_View *view, E_Subsurface *subsurface);
+static void _e_subsurface_view_finish(E_Subsurface_View *view);
 
 static void _ds_surface_buffer_transform_set(struct ds_surface *ds_surface, enum wl_output_transform transform);
 static void _ds_surface_viewport_source_box_set(struct ds_surface *ds_surface, Eina_Rectangle *box);
@@ -203,6 +203,68 @@ e_comp_wl_subsurface_add(struct wl_resource *resource, uint32_t id, struct wl_re
                                  id);
 }
 
+EINTERN E_Subsurface *
+e_subsurface_create(struct wl_resource *factory_resource, uint32_t id, E_Surface *surface, E_Surface *parent)
+{
+   ds_subsurface_create(factory_resource, surface->ds_surface, parent->ds_surface, id);
+
+   return _e_subsurface_from_surface(surface);
+}
+
+EINTERN E_Subsurface *
+e_subsurface_internal_create(E_Surface *surface, E_Subsurface *parent_subsurface)
+{
+   E_Subsurface *sub;
+
+   sub = E_NEW(E_Subsurface, 1);
+   if (!sub)
+     return NULL;
+
+   sub->internal = EINA_TRUE;
+   sub->surface = surface;
+
+   sub->parent = parent_subsurface->surface;
+   sub->parent_surface_destroy.notify = _e_subsurface_cb_parent_surface_destroy;
+   wl_signal_add(&sub->parent->events.destroy, &sub->parent_surface_destroy);
+
+   e_comp_wl_client_subsurface_init(surface->ec, NULL, &sub->base, sub->parent->ec, NULL);
+
+   _e_subsurface_view_init(&sub->view, sub);
+
+   ELOGF("SUBSURFACE", "Create for internal use: parent(%p)", surface->ec, sub->parent->ec);
+
+   return sub;
+}
+
+EINTERN void
+e_subsurface_internal_destroy(E_Subsurface *sub)
+{
+   assert(sub->internal);
+
+   ELOGF("SUBSURFACE", "Destroy internal subsurface", sub->surface->ec);
+
+   _e_subsurface_view_finish(&sub->view);
+   e_comp_wl_client_subsurface_finish(sub->surface->ec);
+   wl_list_remove(&sub->parent_surface_destroy.link);
+   free(sub);
+}
+
+EINTERN void
+e_subsurface_stand_alone_mode_set(E_Subsurface *sub)
+{
+   EINA_SAFETY_ON_NULL_RETURN(sub);
+
+   sub->base.stand_alone = EINA_TRUE;
+}
+
+EINTERN void
+e_subsurface_stand_alone_mode_unset(E_Subsurface *sub)
+{
+   EINA_SAFETY_ON_NULL_RETURN(sub);
+
+   sub->base.stand_alone = EINA_FALSE;
+}
+
 EINTERN void
 e_comp_wl_subsurface_resource_stand_alone_mode_set(struct wl_resource *subsurface_resource)
 {
@@ -519,6 +581,35 @@ e_surface_role_set(E_Surface *surface, const char *role_name, struct wl_resource
    return EINA_FALSE;
 }
 
+// Do not use it unless you do know what you are doing
+EINTERN void
+e_surface_role_unset(E_Surface *surface)
+{
+   EINA_SAFETY_ON_NULL_RETURN(surface);
+
+   surface->base.role_name = NULL;
+}
+
+EINTERN void
+e_surface_name_set(E_Surface *surface, const char *name)
+{
+   EINA_SAFETY_ON_NULL_RETURN(surface);
+
+   e_client_netwm_name_set(surface->ec, name);
+}
+
+EINTERN E_Client *
+e_surface_ec_get(E_Surface *surface)
+{
+   return surface->ec;
+}
+
+EINTERN Eina_Bool
+e_surface_has_buffer(E_Surface *surface)
+{
+   return !!surface->base.buffer_ref.buffer;
+}
+
 EINTERN E_Subsurface *
 e_subsurface_from_surface(E_Surface *surface)
 {
@@ -548,7 +639,8 @@ e_subsurface_position_set(E_Subsurface *subsurface, int x, int y)
    subsurface->base.position.y = y;
    subsurface->base.position.set = EINA_TRUE;
 
-   _ds_subsurface_position_set(subsurface->ds_subsurface, x, y);
+   if (subsurface->ds_subsurface)
+     _ds_subsurface_position_set(subsurface->ds_subsurface, x, y);
 
    return EINA_TRUE;
 }
@@ -761,6 +853,7 @@ _e_surface_create(E_Client *ec)
 
    wl_signal_init(&surface->events.destroy);
    wl_signal_init(&surface->events.parent_destroy);
+   wl_signal_init(&surface->events.commit);
    wl_signal_init(&surface->base.destroy_signal);
    wl_signal_init(&surface->base.apply_viewport_signal);
    wl_signal_init(&surface->base.state_commit_signal);
@@ -1354,6 +1447,8 @@ _e_surface_cb_commit(struct wl_listener *listener, void *data)
    if (e_comp_wl_remote_surface_commit(surface->ec)) return;
 
    _e_surface_commit(surface);
+
+   wl_signal_emit(&surface->events.commit, surface);
 }
 
 static void
@@ -1374,15 +1469,15 @@ _e_surface_cb_new_subsurface(struct wl_listener *listener, void *data)
 static void
 _e_subsurface_view_init(E_Subsurface_View *view, E_Subsurface *subsurface)
 {
-   int x, y;
-
-   view->subsurface = subsurface;
    view->ec = subsurface->surface->ec;
 
-   wl_signal_init(&view->events.reposition);
+   e_subsurface_view_reposition(subsurface);
+}
 
-   e_subsurface_coord_get(subsurface, &x, &y);
-   evas_object_move(view->ec->frame, x, y);   
+static void
+_e_subsurface_view_finish(E_Subsurface_View *view)
+{
+   view->ec = NULL;
 }
 
 static E_Subsurface *
@@ -1472,6 +1567,8 @@ _e_subsurface_destroy(E_Subsurface *sub)
 
    wl_signal_emit(&sub->events.destroy, sub);
 
+   _e_subsurface_view_finish(&sub->view);
+
    e_comp_wl_client_subsurface_finish(sub->surface->ec);
    wl_list_remove(&sub->desync_precommit.link);
    wl_list_remove(&sub->sync_precommit.link);
@@ -1786,6 +1883,7 @@ EINTERN void
 e_subsurface_destroy_listener_add(E_Subsurface *subsurface, struct wl_listener *listener)
 {
    EINA_SAFETY_ON_NULL_RETURN(subsurface);
+   assert(!subsurface->internal);
    wl_signal_add(&subsurface->events.destroy, listener);
 }
 
@@ -1793,6 +1891,7 @@ EINTERN void
 e_subsurface_cached_listener_add(E_Subsurface *subsurface, struct wl_listener *listener)
 {
    EINA_SAFETY_ON_NULL_RETURN(subsurface);
+   assert(!subsurface->internal);
    wl_signal_add(&subsurface->events.cached, listener);
 }
 
@@ -1800,6 +1899,7 @@ EINTERN void
 e_subsurface_sync_precommit_listener_add(E_Subsurface *subsurface, struct wl_listener *listener)
 {
    EINA_SAFETY_ON_NULL_RETURN(subsurface);
+   assert(!subsurface->internal);
    wl_signal_add(&subsurface->events.sync_precommit, listener);
 }
 
@@ -1807,6 +1907,7 @@ EINTERN void
 e_subsurface_desync_precommit_listener_add(E_Subsurface *subsurface, struct wl_listener *listener)
 {
    EINA_SAFETY_ON_NULL_RETURN(subsurface);
+   assert(!subsurface->internal);
    wl_signal_add(&subsurface->events.desync_precommit, listener);
 }
 
@@ -1856,22 +1957,16 @@ e_subsurface_parent_get(E_Subsurface *subsurface)
    return subsurface->parent;
 }
 
-EINTERN void
-e_subsurface_view_reposition_listener_add(E_Subsurface *subsurface, struct wl_listener *listener)
-{
-   E_Subsurface_View *view = &subsurface->view;
-
-   wl_signal_add(&view->events.reposition, listener);
-}
-
 EINTERN void
 e_subsurface_view_position_set(E_Subsurface *subsurface, int x, int y)
 {
    E_Subsurface_View *view = &subsurface->view;
 
-   evas_object_move(view->ec->frame, x, y);
+   if (!view->ec)
+     return;
 
-   wl_signal_emit(&view->events.reposition, subsurface);
+   evas_object_move(view->ec->frame, x, y);
+   e_client_transform_core_update(view->ec);
 }
 
 EINTERN void
index ac68f019a602bf87f8a0a2590c8537e4ebb4febb..7c9b941a3126e57243add95260597c52595cf425 100644 (file)
@@ -20,6 +20,7 @@ EINTERN void e_surface_map_listener_add(E_Surface *surface, struct wl_listener *
 EINTERN void e_surface_unmap_listener_add(E_Surface *surface, struct wl_listener *listener);
 EINTERN struct wl_listener *e_surface_destroy_listener_get(E_Surface *surface, wl_notify_func_t notify);
 EINTERN Eina_Bool e_surface_role_set(E_Surface *surface, const char *role_name, struct wl_resource *error_resource, uint32_t error_code);
+EINTERN void e_surface_role_unset(E_Surface *surface);
 EINTERN struct wl_resource *e_surface_viewporter_resource_get(E_Surface *surface);
 EINTERN Eina_Bool e_surface_viewporter_resource_set(E_Surface *surface, struct wl_resource *viewporter);
 EINTERN void e_surface_viewporter_resource_unset(E_Surface *surface);
@@ -35,6 +36,7 @@ EINTERN void e_surface_map(E_Surface *surface);
 EINTERN void e_surface_unmap(E_Surface *surface);
 EINTERN Eina_Bool e_surface_is_mapped(E_Surface *surface);
 
+EINTERN E_Subsurface *e_subsurface_create(struct wl_resource *factory_resource, uint32_t id, E_Surface *surface, E_Surface *parent);
 EINTERN E_Subsurface *e_subsurface_from_resource(struct wl_resource *resource);
 EINTERN E_Subsurface *e_subsurface_from_surface(E_Surface *surface);
 EINTERN E_Subsurface *e_subsurface_from_ec(E_Client *ec);
@@ -48,9 +50,10 @@ EINTERN void e_subsurface_stand_alone_mode_unset(E_Subsurface *sub);
 EINTERN void e_subsurface_coord_get(E_Subsurface *subsurface, int *x, int *y);
 EINTERN E_Surface *e_subsurface_surface_get(E_Subsurface *subsurface);
 EINTERN E_Surface *e_subsurface_parent_get(E_Subsurface *subsurface);
-
-EINTERN void e_subsurface_view_reposition_listener_add(E_Subsurface *subsurface, struct wl_listener *listener);
 EINTERN void e_subsurface_view_position_set(E_Subsurface *subsurface, int x, int y);
 EINTERN void e_subsurface_view_reposition(E_Subsurface *subsurface);
 
+EINTERN E_Subsurface *e_subsurface_internal_create(E_Surface *surface, E_Subsurface *parent_subsurface);
+EINTERN void e_subsurface_internal_destroy(E_Subsurface *subsurface);
+
 #endif
index 78382b7fd083bd05b3d7a0772024bcdbcbe9b502..db8e3b2b539585b187035220ed5659e2ed7e6528 100644 (file)
@@ -24,6 +24,7 @@
 #include "e_tizen_surface_shm_intern.h"
 #include "e_devicemgr_wl_intern.h"
 #include "e_keyrouter_wl_intern.h"
+#include "e_video_shell_intern.h"
 
 typedef struct _E_Server E_Server;
 
@@ -60,6 +61,7 @@ struct _E_Server
         E_Tizen_Surface_Shm *tizen_surface_shm;
         E_Tizen_Devicemgr *tizen_devicemgr_handle;
         Dummy_Handle *tizen_keyrouter_handle;
+        Dummy_Handle *tizen_video_shell;
     } handles; // feature handles
 };
 
@@ -115,6 +117,8 @@ e_server_init(void)
      e_server_feature_register(E_SERVER_FEATURE_EXPLICIT_SYNC, &dummy_handle);
    if (e_egl_sync_init())
      e_server_feature_register(E_SERVER_FEATURE_EGL_SYNC, &dummy_handle);
+   if (e_video_shell_create(e_comp_wl->wl.disp))
+     e_server_feature_register(E_SERVER_FEATURE_TIZEN_VIDEO_SHELL, &dummy_handle);
 
    e_server_feature_register(E_SERVER_FEATURE_TIZEN_SRCREEN_MANAGER, e_tizen_screen_manager_init());
 
@@ -243,6 +247,8 @@ e_server_feature_register(E_SERVER_FEATURE feature, void *handle)
          break;
        case E_SERVER_FEATURE_TIZEN_SURFACE_SHM:
          g_server->handles.tizen_surface_shm =(E_Tizen_Surface_Shm *)handle;
+       case E_SERVER_FEATURE_TIZEN_VIDEO_SHELL:
+         g_server->handles.tizen_video_shell = handle;
          break;
        case E_SERVER_FEATURE_TIZEN_DEVICEMGR:
          g_server->handles.tizen_devicemgr_handle = handle;
@@ -333,6 +339,8 @@ e_server_feature_handle_get(E_SERVER_FEATURE feature)
          break;
        case E_SERVER_FEATURE_TIZEN_SURFACE_SHM:
          handle = g_server->handles.tizen_surface_shm;
+       case E_SERVER_FEATURE_TIZEN_VIDEO_SHELL:
+         handle = g_server->handles.tizen_video_shell;
          break;
        case E_SERVER_FEATURE_TIZEN_DEVICEMGR:
          handle = g_server->handles.tizen_devicemgr_handle;
index 37974e2fdb9918ff4e7d1da8ea5186a083d4ef04..6529817c388b9a2e185795c248530dfac5190928 100644 (file)
@@ -29,6 +29,7 @@ typedef enum _E_SERVER_FEATURE {
     E_SERVER_FEATURE_TIZEN_SURFACE_SHM,
     E_SERVER_FEATURE_TIZEN_DEVICEMGR,
     E_SERVER_FEATURE_TIZEN_KEYROUTER,
+    E_SERVER_FEATURE_TIZEN_VIDEO_SHELL,
     E_SERVER_FEATURE_MAX
 } E_SERVER_FEATURE;
 
diff --git a/src/bin/server/e_video_shell.c b/src/bin/server/e_video_shell.c
new file mode 100644 (file)
index 0000000..b66e49d
--- /dev/null
@@ -0,0 +1,1644 @@
+#include "e_types.h"
+#include "e_log.h"
+#include "e_object.h"
+#include "e_comp_wl.h"
+#include "e_comp_wl_intern.h"
+#include "e_client_intern.h"
+#include "e_compositor_intern.h"
+#include "e_comp_wl_shell_intern.h"
+#include "e_comp_object_intern.h"
+#include "e_desk_intern.h"
+#include "e_comp_wl_subsurface_intern.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <uuid.h>
+#include <wtz-video-shell-server-protocol.h>
+#include <Eina.h>
+
+#ifdef VS_ERR
+#undef VS_ERR
+#endif
+
+#ifdef VS_INF
+#undef VS_INF
+#endif
+
+#ifdef VS_DBG
+#undef VS_DBG
+#endif
+
+#define VS_ERR(f, x...) ERR(f, ##x)
+#define VS_INF(f, x...) INF(f, ##x)
+#define VS_DBG(f, x...) DBG(f, ##x)
+
+#define WTZ_VIDEO_SHELL_VERSION 1
+#define E_VIDEO_SHELL_TYPE (int)0xE0b01004
+
+typedef struct _E_Video_Viewport_Source E_Video_Viewport_Source;
+
+typedef struct
+{
+   E_Object e_obj_inherit;
+   struct wl_display *display;
+   struct wl_global *global;
+   Eina_Hash *viewports;
+   struct wl_listener display_destroy;
+} E_Video_Shell;
+
+typedef enum
+{
+   E_VIDEO_VIEWPORT_STATE_CLEAN = 0,
+   E_VIDEO_VIEWPORT_STATE_MAP = (1 << 0),
+   E_VIDEO_VIEWPORT_STATE_DESTINATION = (1 << 1),
+   E_VIDEO_VIEWPORT_STATE_TRANSFORM = (1 << 2),
+} E_Video_Viewport_State_Field;
+
+typedef struct
+{
+   E_Video_Viewport_State_Field committed;
+   int32_t width, height;
+   int32_t transform;
+   Eina_Bool map;
+} E_Video_Viewport_State;
+
+typedef struct
+{
+   struct wl_resource *resource;
+   E_Subsurface *subsurface;
+   E_Video_Shell *shell;
+   E_Video_Viewport_Source *source;
+
+   E_Video_Viewport_State pending, cache, current;
+
+   uuid_t handle;
+
+   struct wl_listener destroy;
+   struct wl_listener cached;
+   struct wl_listener sync_precommit;
+   struct wl_listener desync_precommit;
+   struct wl_listener parent_map;
+   struct wl_listener parent_unmap;
+   struct wl_listener source_destroy;
+
+   Eina_Bool parent_mapped;
+   Eina_Bool mapped;
+} E_Video_Viewport;
+
+typedef enum
+{
+   E_VIDEO_SURFACE_ROLE_NONE,
+   E_VIDEO_SURFACE_ROLE_VIEWPORT_SOURCE,
+   E_VIDEO_SURFACE_ROLE_SUBSURFACE,
+} E_Video_Surface_Role;
+
+typedef struct
+{
+   struct wl_resource *resource;
+   E_Surface *surface;
+   E_Video_Shell *shell;
+
+   E_Video_Surface_Role role;
+   union
+     {
+        E_Subsurface *subsurface;
+        E_Video_Viewport_Source *source;
+     };
+
+   struct wl_listener surface_destroy;
+   struct wl_listener subsurface_destroy;
+   struct wl_listener source_destroy;
+
+   char *name;
+
+   Eina_Bool stand_alone;
+} E_Video_Surface;
+
+typedef enum
+{
+   E_VIDEO_VIEWPORT_SOURCE_STATE_CLEAN = 0,
+   E_VIDEO_VIEWPORT_SOURCE_STATE_SOURCE_REGION = (1 << 0),
+   E_VIDEO_VIEWPORT_SOURCE_STATE_ASPECT_RATIO = (1 << 1),
+} E_Video_Viewport_Source_State_Field;
+
+typedef struct
+{
+   E_Video_Viewport_Source_State_Field committed;
+
+   struct
+     {
+        double x, y, width, height;
+     } src;
+
+   struct
+     {
+        int32_t width, height;
+     } aspect_ratio;
+
+   Eina_Bool has_src, has_aspect_ratio;
+} E_Video_Viewport_Source_State;
+
+struct _E_Video_Viewport_Source
+{
+   struct wl_resource *resource;
+   E_Video_Surface *surface;
+   E_Video_Viewport *viewport;
+   E_Subsurface *subsurface;
+   E_Util_Transform *transform;
+
+   E_Video_Viewport_Source_State pending, current;
+
+   struct wl_listener surface_commit;
+
+   struct
+     {
+        struct wl_signal destroy;
+     } events;
+};
+
+static const char *e_video_surface_role_name = "wtz_video_surface";
+
+static void _source_viewport_destroyed(E_Video_Viewport_Source *source);
+static void _source_viewport_committed(E_Video_Viewport_Source *source);
+static void _source_viewport_map_state_changed(E_Video_Viewport_Source *source);
+
+static void
+_viewport_cb_source_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, source_destroy);
+
+   wl_list_remove(&viewport->source_destroy.link);
+   viewport->source = NULL;
+}
+
+static void
+_viewport_source_set(E_Video_Viewport *viewport, E_Video_Viewport_Source *source)
+{
+   viewport->source = source;
+   viewport->source_destroy.notify = _viewport_cb_source_destroy;
+   wl_signal_add(&source->events.destroy, &viewport->source_destroy);
+}
+
+static void
+_viewport_cb_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+   wl_resource_destroy(resource);
+}
+
+static void
+_viewport_cb_set_destination(struct wl_client *client, struct wl_resource *resource, int32_t width, int32_t height)
+{
+   E_Video_Viewport *viewport = wl_resource_get_user_data(resource);
+
+   if (!viewport)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_NO_SUBSURFACE,
+                               "wtz_video_exported_viewport.set_destination sent after wl_subsurface has been destroyed");
+        return;
+     }
+
+   VS_INF("VIEWPORT %p| Set destination(width: %d, height: %d)", viewport, width, height);
+
+   if ((width != -1 || height != -1) &&
+       (width < 1 || height <1))
+     {
+        wl_resource_post_error(resource,
+                               WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_BAD_VALUE,
+                               "destination size must be valid. ('%dx%d' specified)",
+                               width, height);
+        return;
+     }
+
+   if ((viewport->pending.width == width) &&
+       (viewport->pending.height == height))
+     return;
+
+   viewport->pending.committed |= E_VIDEO_VIEWPORT_STATE_DESTINATION;
+   viewport->pending.width = width;
+   viewport->pending.height = height;
+}
+
+static const char *
+_viewport_transform_to_str(int32_t transform)
+{
+   switch (transform)
+     {
+   default:
+   case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_NORMAL:
+      return "NORMAL";
+   case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_90:
+      return "90";
+   case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_180:
+      return "180";
+   case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_270:
+      return "270";
+     }
+}
+
+static void
+_viewport_state_print(E_Video_Viewport_State *state)
+{
+   if (state->committed & E_VIDEO_VIEWPORT_STATE_MAP)
+     VS_DBG(" - Map(%d)", state->map);
+   if (state->committed & E_VIDEO_VIEWPORT_STATE_DESTINATION)
+     VS_DBG(" - Destination(%dx%d)", state->width, state->height);
+   if (state->committed & E_VIDEO_VIEWPORT_STATE_TRANSFORM)
+     VS_DBG(" - Transform(%s)", _viewport_transform_to_str(state->transform));
+}
+
+static void
+_viewport_cb_set_transform(struct wl_client *client, struct wl_resource *resource, int32_t transform)
+{
+   E_Video_Viewport *viewport = wl_resource_get_user_data(resource);
+
+   if (!viewport)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_NO_SUBSURFACE,
+                               "wtz_video_exported_viewport.set_transform sent after wl_subsurface has been destroyed");
+        return;
+     }
+
+   VS_INF("VIEWPORT %p| Set transform(%s)", viewport, _viewport_transform_to_str(transform));
+
+   if ((transform < 0) ||
+       (transform > WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_270))
+     {
+        wl_resource_post_error(resource,
+                               WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_INVALID_TRANSFORM,
+                               "transform must be valid. ('%d' sepcified)",
+                               transform);
+        return;
+     }
+
+   if (viewport->pending.transform == transform)
+     return;
+
+   viewport->pending.committed |= E_VIDEO_VIEWPORT_STATE_TRANSFORM;
+   viewport->pending.transform = transform;
+}
+
+static void
+_viewport_cb_map(struct wl_client *client, struct wl_resource *resource)
+{
+   E_Video_Viewport *viewport = wl_resource_get_user_data(resource);
+
+   if (!viewport)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_NO_SUBSURFACE,
+                               "wtz_video_exported_viewport.map sent after wl_subsurface has been destroyed");
+        return;
+     }
+
+   VS_INF("VIEWPORT %p| Map", viewport);
+
+   if (viewport->pending.map)
+     return;
+
+   viewport->pending.committed |= E_VIDEO_VIEWPORT_STATE_MAP;
+   viewport->pending.map = EINA_TRUE;
+}
+
+static void
+_viewport_cb_unmap(struct wl_client *client, struct wl_resource *resource)
+{
+   E_Video_Viewport *viewport = wl_resource_get_user_data(resource);
+
+   if (!viewport)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_EXPORTED_VIEWPORT_ERROR_NO_SUBSURFACE,
+                               "wtz_video_exported_viewport.unmap sent after wl_subsurface has been destroyed");
+        return;
+     }
+
+   VS_INF("VIEWPORT %p| Unmap", viewport);
+
+   if (!viewport->pending.map)
+     return;
+
+   viewport->pending.committed |= E_VIDEO_VIEWPORT_STATE_MAP;
+   viewport->pending.map = EINA_FALSE;
+}
+
+static const struct wtz_video_exported_viewport_interface _viewport_impl =
+{
+   .destroy = _viewport_cb_destroy,
+   .set_destination = _viewport_cb_set_destination,
+   .set_transform = _viewport_cb_set_transform,
+   .map = _viewport_cb_map,
+   .unmap = _viewport_cb_unmap,
+};
+
+static void
+_viewport_destroy(E_Video_Viewport *viewport)
+{
+   VS_INF("VIEWPORT %p| destroy", viewport);
+
+   if (viewport->source)
+     {
+        _source_viewport_destroyed(viewport->source);
+        wl_list_remove(&viewport->source_destroy.link);
+     }
+
+   eina_hash_del_by_data(viewport->shell->viewports, viewport);
+   e_object_unref(E_OBJECT(viewport->shell));
+
+   wl_resource_set_user_data(viewport->resource, NULL);
+   wl_list_remove(&viewport->parent_map.link);
+   wl_list_remove(&viewport->parent_unmap.link);
+   wl_list_remove(&viewport->sync_precommit.link);
+   wl_list_remove(&viewport->desync_precommit.link);
+   wl_list_remove(&viewport->cached.link);
+   wl_list_remove(&viewport->destroy.link);
+   free(viewport);
+}
+
+static void
+_viewport_cb_resource_destroy(struct wl_resource *resource)
+{
+   E_Video_Viewport *viewport = wl_resource_get_user_data(resource);
+
+   if (!viewport)
+     return;
+
+   VS_INF("VIEWPORT %p| wl_resource(%p) destroyed", viewport, resource);
+
+   _viewport_destroy(viewport);
+}
+
+static void
+_viewport_cb_subsurface_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, destroy);
+
+   VS_INF("VIEWPORT %p| E_Subsurface(%p) destroyed", viewport, viewport->subsurface);
+
+   _viewport_destroy(viewport);
+}
+
+static void
+_viewport_state_move(E_Video_Viewport_State *state, E_Video_Viewport_State *next)
+{
+   if (next->committed & E_VIDEO_VIEWPORT_STATE_MAP)
+     state->map = next->map;
+
+   if (next->committed & E_VIDEO_VIEWPORT_STATE_DESTINATION)
+     {
+        state->width = next->width;
+        state->height = next->height;
+     }
+
+   if (next->committed & E_VIDEO_VIEWPORT_STATE_TRANSFORM)
+     state->transform = next->transform;
+
+   state->committed |= next->committed;
+   next->committed = E_VIDEO_VIEWPORT_STATE_CLEAN;
+}
+
+static void
+_viewport_consider_map(E_Video_Viewport *viewport)
+{
+   if (viewport->mapped)
+     return;
+
+   if (viewport->parent_mapped && viewport->current.map)
+     {
+        viewport->mapped = EINA_TRUE;
+        if (viewport->source)
+          _source_viewport_map_state_changed(viewport->source);
+     }
+}
+
+static void
+_viewport_unmap(E_Video_Viewport *viewport)
+{
+   if (!viewport->mapped)
+     return;
+
+   viewport->mapped = EINA_FALSE;
+   if (viewport->source)
+     _source_viewport_map_state_changed(viewport->source);
+}
+
+static void
+_viewport_state_commit(E_Video_Viewport *viewport, E_Video_Viewport_State *next)
+{
+   VS_DBG("VIEWPORT %p| Commit State", viewport);
+
+   viewport->current.committed = E_VIDEO_VIEWPORT_STATE_CLEAN;
+   _viewport_state_move(&viewport->current, next);
+   _viewport_state_print(&viewport->current);
+
+   if (viewport->current.committed & E_VIDEO_VIEWPORT_STATE_MAP)
+     {
+        if (viewport->current.map)
+          _viewport_consider_map(viewport);
+        else
+          _viewport_unmap(viewport);
+     }
+
+   if (viewport->source)
+     _source_viewport_committed(viewport->source);
+}
+
+static void
+_viewport_cb_cached(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, cached);
+
+   _viewport_state_move(&viewport->cache, &viewport->pending);
+
+   VS_INF("VIEWPORT %p| Commit to cache(cache.committed:%d)",
+          viewport, viewport->cache.committed);
+}
+
+static void
+_viewport_cb_sync_precommit(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, sync_precommit);
+
+   VS_INF("VIEWPORT %p| Sync Commit (pending.committed:%d, cache.committed:%d)",
+          viewport, viewport->pending.committed, viewport->cache.committed);
+
+   if (viewport->cache.committed != E_VIDEO_VIEWPORT_STATE_CLEAN)
+     _viewport_state_commit(viewport, &viewport->cache);
+}
+
+static void
+_viewport_cb_desync_precommit(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, desync_precommit);
+
+   VS_INF("VIEWPORT %p| Desync Commit (pending.committed:%d, cache.committed:%d)",
+          viewport, viewport->pending.committed, viewport->cache.committed);
+
+   if (viewport->cache.committed != E_VIDEO_VIEWPORT_STATE_CLEAN)
+     {
+        _viewport_state_move(&viewport->cache, &viewport->pending);
+        _viewport_state_commit(viewport, &viewport->cache);
+     }
+   else
+     {
+        _viewport_state_commit(viewport, &viewport->pending);
+     }
+}
+
+static void
+_viewport_cb_parent_map(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, parent_map);
+
+   viewport->parent_mapped = EINA_TRUE;
+   _viewport_consider_map(viewport);
+}
+
+static void
+_viewport_cb_parent_unmap(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport *viewport = wl_container_of(listener, viewport, parent_unmap);
+
+   viewport->parent_mapped = EINA_FALSE;
+   _viewport_unmap(viewport);
+}
+
+static void
+_viewport_state_init(E_Video_Viewport_State *state)
+{
+   state->committed = E_VIDEO_VIEWPORT_STATE_CLEAN;
+   state->width = -1;
+   state->height = -1;
+   state->transform = WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_NORMAL;
+   state->map = EINA_FALSE;
+}
+
+static void
+_viewport_export_handle_generate(E_Video_Viewport *viewport)
+{
+   char handle_str[37];
+
+   uuid_generate(viewport->handle);
+   uuid_unparse(viewport->handle, handle_str);
+
+   wtz_video_exported_viewport_send_handle(viewport->resource, handle_str);
+
+   eina_hash_add(viewport->shell->viewports, handle_str, viewport);
+
+   VS_INF("VIEWPORT %p| Export handle(%s) generated", viewport, handle_str);
+}
+
+static void
+_video_shell_cb_export_viewport(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *subsurface_resource)
+{
+   E_Video_Viewport *viewport;
+   E_Surface *parent_surface;
+
+   viewport = calloc(1, sizeof(*viewport));
+   if (!viewport)
+     {
+        wl_resource_post_no_memory(resource);
+        return;
+     }
+
+   viewport->resource = wl_resource_create(client, &wtz_video_exported_viewport_interface, wl_resource_get_version(resource), id);
+   if (!viewport->resource)
+     {
+        wl_resource_post_no_memory(resource);
+        free(viewport);
+     }
+   wl_resource_set_implementation(viewport->resource, &_viewport_impl, viewport, _viewport_cb_resource_destroy);
+
+   _viewport_state_init(&viewport->pending);
+   _viewport_state_init(&viewport->cache);
+   _viewport_state_init(&viewport->current);
+
+   viewport->subsurface = e_subsurface_from_resource(subsurface_resource);
+
+   viewport->destroy.notify = _viewport_cb_subsurface_destroy;
+   e_subsurface_destroy_listener_add(viewport->subsurface, &viewport->destroy);
+
+   viewport->cached.notify = _viewport_cb_cached;
+   e_subsurface_cached_listener_add(viewport->subsurface, &viewport->cached);
+
+   viewport->sync_precommit.notify = _viewport_cb_sync_precommit;
+   e_subsurface_sync_precommit_listener_add(viewport->subsurface, &viewport->sync_precommit);
+
+   viewport->desync_precommit.notify = _viewport_cb_desync_precommit;
+   e_subsurface_desync_precommit_listener_add(viewport->subsurface, &viewport->desync_precommit);
+
+   parent_surface = e_subsurface_parent_get(viewport->subsurface);
+
+   viewport->parent_map.notify = _viewport_cb_parent_map;
+   e_surface_map_listener_add(parent_surface, &viewport->parent_map);
+
+   viewport->parent_unmap.notify = _viewport_cb_parent_unmap;
+   e_surface_unmap_listener_add(parent_surface, &viewport->parent_unmap);
+
+   viewport->shell = wl_resource_get_user_data(resource);
+   e_object_ref(E_OBJECT(viewport->shell));
+
+   _viewport_export_handle_generate(viewport);
+
+   VS_INF("VIEWPORT %p| Created with E_Subsurface(%p)", viewport, viewport->subsurface);
+}
+
+static void
+_video_surface_cb_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+   wl_resource_destroy(resource);
+}
+
+static void
+_source_cb_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+   wl_resource_destroy(resource);
+}
+
+static void
+_source_cb_set_source(struct wl_client *client, struct wl_resource *resource, wl_fixed_t x_fixed, wl_fixed_t y_fixed, wl_fixed_t width_fixed, wl_fixed_t height_fixed)
+{
+   E_Video_Viewport_Source *source = wl_resource_get_user_data(resource);
+   double x, y, width, height;
+
+   if (!source)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_VIEWPORT_SOURCE_ERROR_NO_SURFACE,
+                               "wtz_video_viewport_source.set_source sent after wtz_video_surface has been destroyed");
+        return;
+     }
+
+   x = wl_fixed_to_double(x_fixed);
+   y = wl_fixed_to_double(y_fixed);
+   width = wl_fixed_to_double(width_fixed);
+   height = wl_fixed_to_double(height_fixed);
+
+   VS_INF("SOURCE %p| Set source region(x:%f, y:%f, width:%f, height:%f)",
+          source, x, y, width, height);
+
+   if (x == -1.0 && y == -1.0 && width == -1.0 && height == -1.0)
+     {
+        source->pending.has_src = EINA_FALSE;
+     }
+   else if (x < 0 || y < 0 || width <= 0 || height <= 0)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_VIEWPORT_SOURCE_ERROR_BAD_VALUE,
+                               "wtz_video_viewport_source.set_source sent with invalid values(%f, %f, %f, %f)",
+                               x, y, width, height);
+        return;
+     }
+   else
+     {
+        source->pending.has_src = EINA_TRUE;
+     }
+
+   source->pending.src.x = x;
+   source->pending.src.y = y;
+   source->pending.src.width = width;
+   source->pending.src.height = height;
+   source->pending.committed |= E_VIDEO_VIEWPORT_SOURCE_STATE_SOURCE_REGION;
+}
+
+static void
+_source_cb_set_aspect_ratio(struct wl_client *client, struct wl_resource *resource, int32_t width, int32_t height)
+{
+   E_Video_Viewport_Source *source = wl_resource_get_user_data(resource);
+
+   if (!source)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_VIEWPORT_SOURCE_ERROR_NO_SURFACE,
+                               "wtz_video_viewport_source.set_aspect_ratio sent after wtz_video_surface has been destroyed");
+        return;
+     }
+
+   VS_INF("SOURCE %p| Set aspect ratio(%dx%d)", source, width, height);
+
+   if (width == -1 && height == -1)
+     {
+        source->pending.has_aspect_ratio = EINA_FALSE;
+     }
+   else if (width <= 0 || height <= 0)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_VIEWPORT_SOURCE_ERROR_BAD_VALUE,
+                               "wtz_video_viewport_source.set_aspect_ratio sent with invalid values (%dx%d)",
+                               width, height);
+        return;
+     }
+   else
+     {
+        source->pending.has_aspect_ratio = EINA_TRUE;
+     }
+
+   source->pending.aspect_ratio.width = width;
+   source->pending.aspect_ratio.height = height;
+   source->pending.committed |= E_VIDEO_VIEWPORT_SOURCE_STATE_ASPECT_RATIO;
+}
+
+static const struct wtz_video_viewport_source_interface _source_impl =
+{
+   .destroy = _source_cb_destroy,
+   .set_source = _source_cb_set_source,
+   .set_aspect_ratio = _source_cb_set_aspect_ratio,
+};
+
+static void
+_source_destroy(E_Video_Viewport_Source *source)
+{
+   E_Client *ec;
+
+   VS_INF("SOURCE %p| Destroy", source);
+
+   wl_signal_emit(&source->events.destroy, source);
+
+   if (source->transform)
+     {
+        ec = e_surface_ec_get(source->surface->surface);
+        e_client_transform_core_remove(ec, source->transform);
+        e_util_transform_del(source->transform);
+        // TODO: Is it necessary to call e_client_transform_core_update?
+     }
+
+   if (source->subsurface)
+     e_subsurface_internal_destroy(source->subsurface);
+
+   wl_resource_set_user_data(source->resource, NULL);
+   wl_list_remove(&source->surface_commit.link);
+   free(source);
+}
+
+static void
+_source_cb_resource_destroy(struct wl_resource *resource)
+{
+   E_Video_Viewport_Source *source = wl_resource_get_user_data(resource);
+
+   if (!source)
+     return;
+
+   VS_INF("SOURCE %p| wl_resource(%p) destroyed", source, source->resource);
+
+   _source_destroy(source);
+}
+
+static void
+_source_pixmap_update(E_Video_Viewport_Source *source)
+{
+   E_Client *ec = e_surface_ec_get(source->surface->surface);
+
+   /* e_comp_wl has been setting pixmap of e_client to usable only for
+    * xdg_shell */
+   e_pixmap_usable_set(ec->pixmap, e_surface_has_buffer(source->surface->surface));
+
+   /* It's to update pixmap size. It's supposed to be called by
+    * e_comp_wl_surface_attach but it would be failed due to
+    * e_pixmap_usable_get. */
+   e_pixmap_dirty(ec->pixmap);
+   e_pixmap_refresh(ec->pixmap);
+}
+
+static void
+_source_map(E_Video_Viewport_Source *source)
+{
+   E_Client *ec = e_surface_ec_get(source->surface->surface);
+
+   if (e_surface_is_mapped(source->surface->surface))
+     return;
+
+   VS_INF("SOURCE %p| Map", source);
+
+   if (ec->ignored)
+     {
+        EC_CHANGED(ec);
+        ec->new_client = 1;
+        e_comp->new_clients++;
+        e_client_unignore(ec);
+     }
+
+   e_surface_map(source->surface->surface);
+}
+
+static void
+_source_unmap(E_Video_Viewport_Source *source)
+{
+   E_Client *ec = e_surface_ec_get(source->surface->surface);
+
+   if (!e_surface_is_mapped(source->surface->surface))
+     return;
+
+   VS_INF("SOURCE %p| Unmap", source);
+
+   e_surface_unmap(source->surface->surface);
+
+   // FIXME: Need to be done by another module handling view
+   evas_object_hide(ec->frame);
+}
+
+static void
+_source_map_state_update(E_Video_Viewport_Source *source)
+{
+   if (source->viewport &&
+       source->viewport->mapped &&
+       e_surface_has_buffer(source->surface->surface))
+     {
+        _source_map(source);
+     }
+   else
+     {
+        _source_unmap(source);
+     }
+}
+
+static void
+_letter_box_geometry_calc(Eina_Rectangle *in_out, double ratio_width, double ratio_height)
+{
+   int fit_width;
+   int fit_height;
+   double fw, fh, ssh, max;
+
+   if (ratio_width != -1.0)
+     ssh = ratio_height / ratio_width;
+   else
+     ssh = 1;
+
+   fw = 1 / (double)in_out->w;
+   fh = ssh / (double)in_out->h;
+   max = MAX(fw, fh);
+
+   fit_width = 1 / max;
+   fit_height = ssh / max;
+
+   in_out->x = (in_out->w - fit_width) / 2;
+   in_out->y = (in_out->h - fit_height) / 2;
+   in_out->w = fit_width;
+   in_out->h = fit_height;
+}
+
+static void
+_source_viewport_destination_update(E_Video_Viewport_Source *source)
+{
+   E_Video_Viewport *viewport = source->viewport;
+   Eina_Rectangle dest_rect = {0, 0, -1, -1};
+   int32_t ratio_width, ratio_height;
+
+   if ((!viewport) ||
+       ((viewport->current.width == -1) || (viewport->current.height == -1)))
+     goto end;
+
+   dest_rect.w = viewport->current.width;
+   dest_rect.h = viewport->current.height;
+
+   VS_INF("SOURCE %p| Update destination(%dx%d)", source, dest_rect.w, dest_rect.h);
+
+   if (source->current.has_aspect_ratio)
+     {
+        ratio_width = source->current.aspect_ratio.width;
+        ratio_height = source->current.aspect_ratio.height;
+
+        _letter_box_geometry_calc(&dest_rect, ratio_width, ratio_height);
+
+        VS_INF("SOURCE %p| Update destination with aspect ratio(%d,%d) -> (%d,%d) %dx%d",
+               source, ratio_width, ratio_height,
+               dest_rect.x, dest_rect.y, dest_rect.w, dest_rect.h);
+     }
+
+end:
+   e_subsurface_position_set(source->subsurface, dest_rect.x, dest_rect.y);
+   e_subsurface_view_reposition(source->subsurface);
+   e_surface_viewport_destination_set(source->surface->surface, dest_rect.w, dest_rect.h);
+}
+
+static void
+_source_viewport_transform_update(E_Video_Viewport_Source *source)
+{
+   E_Video_Viewport *viewport = source->viewport;
+   E_Client *ec = e_surface_ec_get(source->surface->surface);
+   int x, y, w, h;
+   uint32_t transform;
+   double sx, sy;
+   double cx, cy, dx, dy;
+   double rx, ry, rw, rh;
+
+   if (!viewport ||
+       viewport->current.transform == WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_NORMAL)
+     {
+        if (source->transform)
+          {
+             e_client_transform_core_remove(ec, source->transform);
+             e_util_transform_del(source->transform);
+             source->transform = NULL;
+             goto end;
+          }
+        return;
+     }
+
+   if (!source->transform)
+     {
+        source->transform = e_util_transform_new();
+        e_client_transform_core_add(ec, source->transform);
+     }
+
+   e_subsurface_coord_get(source->subsurface, &x, &y);
+   w = ec->comp_data->width_from_viewport;
+   h = ec->comp_data->height_from_viewport;
+   transform = viewport->current.transform;
+
+   cx = ((double)x + (double)w/2);
+   cy = ((double)y + (double)h/2);
+
+   if ((transform == WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_90) ||
+       (transform == WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_270))
+     {
+        sx = (double)h / w;
+        sy = (double)w / h;
+
+        rw = (double)h;
+        rh = (double)w;
+        rx = cx - rw / 2;
+        ry = cy - rh / 2;
+
+        dx = ((double)w - sx * rw) * 0.5f;
+        dy = ((double)h - sy * rh) * 0.5f;
+
+        e_util_transform_rotation_axis_set(source->transform, cx, cy, -1);
+        e_util_transform_move(source->transform,
+                              -(rx * sx) + dx + x,
+                              -(ry * sy) + dy + y,
+                              0.0);
+        e_util_transform_scale(source->transform, sx, sy, 1.0);
+     }
+   else if (transform == WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_180)
+     {
+        e_util_transform_rotation_axis_set(source->transform, cx, cy, -1);
+        e_util_transform_scale(source->transform, 1.0, 1.0, 1.0);
+        e_util_transform_move(source->transform, 0, 0, 0);
+     }
+
+   switch (transform)
+     {
+      case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_90:
+         e_util_transform_rotation(source->transform, 0, 0, 90);
+         break;
+      case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_270:
+         e_util_transform_rotation(source->transform, 0, 0, 270);
+         break;
+      case WTZ_VIDEO_EXPORTED_VIEWPORT_TRANSFORM_180:
+         e_util_transform_rotation(source->transform, 0, 0, 180);
+         break;
+      default:
+         assert(0);
+     }
+
+end:
+   e_client_transform_core_update(ec);
+}
+
+static void
+_source_viewport_apply(E_Video_Viewport_Source *source)
+{
+   E_Client *ec;
+   E_Comp_Wl_Buffer_Viewport *vp;
+   E_Comp_Wl_Subsurf_Data *sdata;
+   E_Comp_Wl_Client_Data *cdata;
+   E_Subsurface *subsurface;
+   int x1, y1, x2, y2, x, y;
+   int dx = 0, dy = 0;
+   Eina_Bool zoom_animating = EINA_FALSE;
+
+   ec = e_surface_ec_get(source->surface->surface);
+   e_comp_wl_map_size_cal_from_viewport(ec);
+
+   // FIXME: This code is copied from e_comp_wl_map_apply()
+   e_comp_object_map_update(ec->frame);
+
+   if (e_comp_wl_subsurface_check(ec))
+     {
+        subsurface = e_subsurface_from_ec(ec);
+        e_subsurface_coord_get(subsurface, &dx, &dy);
+
+        sdata = ec->comp_data->sub.data;
+        if (sdata->remote_surface.offscreen_parent)
+          {
+             E_Client *offscreen_parent = sdata->remote_surface.offscreen_parent;
+             Eina_Rectangle *rect;
+             Eina_List *l;
+
+             EINA_LIST_FOREACH(offscreen_parent->comp_data->remote_surface.regions, l, rect)
+               {
+                  /* TODO: If there are one more regions, it means that provider's offscreen
+                   * is displayed by one more remote_surfaces. Have to consider it later. At
+                   * this time, just consider only one remote_surface.
+                   */
+                  dx += rect->x;
+                  dy += rect->y;
+                  break;
+               }
+          }
+
+        evas_object_geometry_get(ec->frame, &x, &y, NULL, NULL);
+        if (x != dx || y != dy)
+          e_subsurface_view_position_set(subsurface, dx, dy);
+     }
+   else
+     {
+        dx = ec->x;
+        dy = ec->y;
+
+        evas_object_geometry_get(ec->frame, &x, &y, NULL, NULL);
+        if (x != dx || y != dy)
+          evas_object_move(ec->frame, dx, dy);
+     }
+
+   cdata = ec->comp_data;
+   if (!cdata->viewport_transform)
+     {
+        cdata->viewport_transform = e_util_transform_new();
+        e_util_transform_role_set(cdata->viewport_transform, "viewport_transform");
+        e_client_transform_core_add(ec, cdata->viewport_transform);
+     }
+
+   e_util_transform_viewport_set(cdata->viewport_transform, dx, dy,
+                                 ec->comp_data->width_from_viewport,
+                                 ec->comp_data->height_from_viewport);
+
+   vp = &cdata->scaler.buffer_viewport;
+   if (vp->buffer.src_width == wl_fixed_from_int(-1))
+     {
+        x1 = 0;
+        y1 = 0;
+        x2 = cdata->width_from_buffer;
+        y2 = cdata->height_from_buffer;
+     }
+   else
+     {
+        x1 = wl_fixed_to_int(vp->buffer.src_x);
+        y1 = wl_fixed_to_int(vp->buffer.src_y);
+        x2 = wl_fixed_to_int(vp->buffer.src_x + vp->buffer.src_width);
+        y2 = wl_fixed_to_int(vp->buffer.src_y + vp->buffer.src_height);
+     }
+
+   e_util_transform_texcoord_set(cdata->viewport_transform, 0, x1, y1);
+   e_util_transform_texcoord_set(cdata->viewport_transform, 1, x2, y1);
+   e_util_transform_texcoord_set(cdata->viewport_transform, 2, x2, y2);
+   e_util_transform_texcoord_set(cdata->viewport_transform, 3, x1, y2);
+
+   E_Desk *desk;
+   desk = e_comp_desk_find_by_ec(ec);
+   if (desk)
+     zoom_animating = e_desk_zoom_is_animating(desk);
+
+   ELOGF("TRANSFORM", "viewport map: point(%d,%d %dx%d) uv(%d,%d %d,%d %d,%d %d,%d), zoom_animating: %d",
+         ec, ec->x, ec->y, ec->comp_data->width_from_viewport,
+         ec->comp_data->height_from_viewport, x1, y1, x2, y1, x2, y2, x1, y2, zoom_animating);
+
+   /* workaround:: when the desk zoom is doing their animation,
+      the transform core update can override animation's zoom boundary.
+      so, don't doing transform core update while desk zoom animation */
+   if (!zoom_animating)
+     e_client_transform_core_update(ec);
+}
+
+static void
+_source_viewport_source_box_update(E_Video_Viewport_Source *source)
+{
+   E_Video_Viewport_Source_State *state = &source->current;
+   Eina_Rectangle src_rect = { .x = -1, .y = -1, .w = -1, .h = -1, };
+
+   if (state->has_src)
+     {
+        src_rect.x = ceil(state->src.x);
+        src_rect.y = ceil(state->src.y);
+        src_rect.w = ceil(state->src.width);
+        src_rect.h = ceil(state->src.height);
+     }
+
+   VS_INF("SOURCE %p| Update source box (%d,%d) %dx%d",
+          source, src_rect.x, src_rect.y, src_rect.w, src_rect.h);
+
+   e_surface_viewport_source_box_set(source->surface->surface, &src_rect);
+}
+
+static void
+_source_cb_surface_commit(struct wl_listener *listener, void *data)
+{
+   E_Video_Viewport_Source *source = wl_container_of(listener, source, surface_commit);
+   Eina_Bool need_apply = EINA_FALSE;
+
+   source->current = source->pending;
+   source->pending.committed = E_VIDEO_VIEWPORT_SOURCE_STATE_CLEAN;
+
+   VS_INF("SOURCE %p| Commit (current.committed:%d)", source, source->current.committed);
+
+   _source_pixmap_update(source);
+   _source_map_state_update(source);
+
+   if (source->current.committed & E_VIDEO_VIEWPORT_SOURCE_STATE_SOURCE_REGION)
+     {
+        _source_viewport_source_box_update(source);
+        need_apply = EINA_TRUE;
+     }
+
+   if (source->current.committed & E_VIDEO_VIEWPORT_SOURCE_STATE_ASPECT_RATIO)
+     {
+        _source_viewport_destination_update(source);
+        need_apply = EINA_TRUE;
+     }
+
+   if (need_apply)
+     _source_viewport_apply(source);
+}
+
+static const char *
+_video_surface_role_to_str(E_Video_Surface_Role role)
+{
+   switch (role)
+     {
+   default:
+   case E_VIDEO_SURFACE_ROLE_NONE:
+      return "none";
+   case E_VIDEO_SURFACE_ROLE_VIEWPORT_SOURCE:
+      return "wtz_video_viewport_source";
+   case E_VIDEO_SURFACE_ROLE_SUBSURFACE:
+      return "wl_subsurface";
+     }
+}
+
+static void
+_video_surface_cb_source_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Surface *surface = wl_container_of(listener, surface, source_destroy);
+
+   e_surface_viewporter_resource_unset(surface->surface);
+
+   wl_list_remove(&surface->source_destroy.link);
+   surface->source = NULL;
+   surface->role = E_VIDEO_SURFACE_ROLE_NONE;
+}
+
+static void
+_video_surface_viewport_source_configure(E_Video_Surface *surface)
+{
+   E_Client *ec = e_surface_ec_get(surface->surface);
+
+   surface->role = E_VIDEO_SURFACE_ROLE_VIEWPORT_SOURCE;
+
+   ec->netwm.type = E_WINDOW_TYPE_UTILITY; /* To avoid being maximized due to wm policy */
+   ec->lock_focus_in = ec->lock_focus_out = EINA_TRUE;
+   ec->netwm.state.skip_taskbar = EINA_TRUE;
+   ec->netwm.state.skip_pager = EINA_TRUE;
+   ec->border_size = 0;
+   ec->lock_user_location = 0;
+   ec->lock_client_location = 0;
+   ec->lock_user_size = 0;
+   ec->lock_client_size = 0;
+   ec->lock_client_stacking = 0;
+   ec->lock_user_maximize = 0;
+   ec->lock_client_maximize = 0;
+   ec->changes.need_maximize = 0;
+   ec->maximized = E_MAXIMIZE_NONE;
+}
+
+static void
+_video_surface_source_set(E_Video_Surface *surface, E_Video_Viewport_Source *source)
+{
+   surface->source = source;
+
+   surface->source_destroy.notify = _video_surface_cb_source_destroy;
+   wl_signal_add(&source->events.destroy, &surface->source_destroy);
+
+   _video_surface_viewport_source_configure(surface);
+}
+
+static void
+_source_surface_link(E_Video_Viewport_Source *source, E_Video_Surface *surface)
+{
+   source->surface = surface;
+
+   source->surface_commit.notify = _source_cb_surface_commit;
+   e_surface_commit_listener_add(surface->surface, &source->surface_commit);
+
+   _video_surface_source_set(surface, source);
+}
+
+static Eina_Bool
+_source_viewport_link(E_Video_Viewport_Source *source, E_Video_Viewport *viewport)
+{
+   if (!viewport)
+     {
+        wtz_video_viewport_source_send_viewport_destroyed(source->resource);
+        return EINA_TRUE;
+     }
+
+   source->viewport = viewport;
+
+   source->subsurface = e_subsurface_internal_create(source->surface->surface, viewport->subsurface);
+   if (!source->subsurface)
+     {
+        VS_ERR("SOURCE %p| Could not create an internal subsurface", source);
+        return EINA_FALSE;
+     }
+
+   if (source->surface->stand_alone)
+     e_subsurface_stand_alone_mode_set(source->subsurface);
+
+   _viewport_source_set(viewport, source);
+
+   _source_viewport_destination_update(source);
+   _source_viewport_apply(source);
+
+   return EINA_TRUE;
+}
+
+static void
+_source_state_init(E_Video_Viewport_Source_State *state)
+{
+   state->committed = E_VIDEO_VIEWPORT_SOURCE_STATE_CLEAN;
+   state->src.x = -1.0;
+   state->src.y = -1.0;
+   state->src.width = -1.0;
+   state->src.height = -1.0;
+   state->aspect_ratio.width = -1;
+   state->aspect_ratio.height = -1;
+   state->has_src = EINA_FALSE;
+   state->has_aspect_ratio = EINA_FALSE;
+}
+
+static void
+_source_viewport_destroyed(E_Video_Viewport_Source *source)
+{
+   VS_INF("SOURCE %p| viewport destroyed", source);
+
+   source->viewport = NULL;
+   wtz_video_viewport_source_send_viewport_destroyed(source->resource);
+}
+
+static void
+_source_viewport_committed(E_Video_Viewport_Source *source)
+{
+   E_Video_Viewport_State *state = &source->viewport->current;
+
+   VS_INF("SOURCE %p| viewport committed", source);
+
+   if (state->committed & E_VIDEO_VIEWPORT_STATE_DESTINATION)
+     {
+        _source_viewport_destination_update(source);
+        _source_viewport_apply(source);
+     }
+
+   if (state->committed & E_VIDEO_VIEWPORT_STATE_TRANSFORM)
+     {
+        _source_viewport_transform_update(source);
+     }
+}
+
+static void
+_source_viewport_map_state_changed(E_Video_Viewport_Source *source)
+{
+   VS_INF("SOURCE %p| viewport map state changed: mapped(%d)",
+          source, source->viewport->mapped);
+
+   _source_map_state_update(source);
+}
+
+static void
+_video_surface_cb_get_viewport_source(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+   E_Video_Viewport *viewport;
+   E_Video_Viewport_Source *source;
+
+   if (!surface)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_NO_SURFACE,
+                               "wtz_video_surface.get_viewport_source sent after wl_surface has been destroyed");
+        return;
+     }
+
+   if (surface->role != E_VIDEO_SURFACE_ROLE_NONE)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_ROLE,
+                               "Cannot assign role 'wtz_video_viewport_source', already has role(%s)",
+                               _video_surface_role_to_str(surface->role));
+        return;
+     }
+
+   if (!e_surface_viewporter_resource_set(surface->surface, surface->resource))
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_VIEWPORT_EXISTS,
+                               "Cannot assign role wtz_video_viewport_source to wl_surface, "
+                               "already has a viewport object");
+        return;
+     }
+
+   viewport = eina_hash_find(surface->shell->viewports, handle);
+   if (viewport && viewport->source)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_HANDLE_ALREADY_USED,
+                               "Given handle(%s) is already used by another source",
+                               handle);
+        return;
+     }
+
+   source = calloc(1, sizeof(*source));
+   if (!source)
+     {
+        wl_resource_post_no_memory(resource);
+        return;
+     }
+
+   source->resource = wl_resource_create(client, &wtz_video_viewport_source_interface, wl_resource_get_version(resource), id);
+   if (!source->resource)
+     {
+        wl_resource_post_no_memory(resource);
+        free(source);
+        return;
+     }
+   wl_resource_set_implementation(source->resource, &_source_impl, source, _source_cb_resource_destroy);
+
+   wl_signal_init(&source->events.destroy);
+
+   _source_state_init(&source->pending);
+   _source_state_init(&source->current);
+
+   _source_surface_link(source, surface);
+   if (!_source_viewport_link(source, viewport))
+     {
+        wl_resource_post_no_memory(resource);
+        _source_destroy(source);
+        return;
+     }
+
+   VS_INF("SOURCE %p| Created with E_Video_Surface(%p), Viewport(%p)", source, surface, viewport);
+}
+
+static void
+_video_surface_cb_subsurface_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Surface *surface = wl_container_of(listener, surface, subsurface_destroy);
+
+   VS_INF("SURFACE %p| Subsurface(%p) destroyed", surface, surface->subsurface);
+
+   wl_list_remove(&surface->subsurface_destroy.link);
+   surface->subsurface = NULL;
+   surface->role = E_VIDEO_SURFACE_ROLE_NONE;
+
+   // HACK
+   // Restore surface role to prevent it from being used as another role
+   e_surface_role_unset(surface->surface);
+   e_surface_role_set(surface->surface, e_video_surface_role_name, NULL, 0);
+}
+
+static void
+_video_surface_cb_get_subsurface(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *parent_surface_resource)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+   E_Surface *parent_surface;
+
+   if (!surface)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_NO_SURFACE,
+                               "wtz_video_surface.get_subsurface sent after wl_surface has been destroyed");
+        return;
+     }
+
+   if (surface->role != E_VIDEO_SURFACE_ROLE_NONE)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_ROLE,
+                               "Cannot assign role 'wl_subsurface', already has role(%s)",
+                               _video_surface_role_to_str(surface->role));
+        return;
+     }
+
+   parent_surface = e_surface_from_resource(parent_surface_resource);
+   assert(parent_surface);
+
+   // HACK: The call to e_subsurface_create will assign role to given surface
+   // and raise protocol error if it has already role. This is to avoid it.
+   e_surface_role_unset(surface->surface);
+
+   surface->subsurface = e_subsurface_create(resource, id, surface->surface, parent_surface);
+   if (!surface->subsurface)
+     {
+        e_surface_role_set(surface->surface, e_video_surface_role_name, NULL, 0);
+        return;
+     }
+
+   if (surface->stand_alone)
+     e_subsurface_stand_alone_mode_set(surface->subsurface);
+
+   surface->subsurface_destroy.notify = _video_surface_cb_subsurface_destroy;
+   e_subsurface_destroy_listener_add(surface->subsurface, &surface->subsurface_destroy);
+
+   surface->role = E_VIDEO_SURFACE_ROLE_SUBSURFACE;
+
+   VS_INF("SURFACE %p| Subsurface(%p) created with Parent E_Surface(%p)",
+          surface, surface->subsurface, parent_surface);
+}
+
+static void
+_video_surface_cb_set_name(struct wl_client *client, struct wl_resource *resource, const char *name)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+   char *tmp;
+
+   if (!surface)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_NO_SURFACE,
+                               "wtz_video_surface.set_name sent after wl_surface has been destroyed");
+        return;
+     }
+
+   tmp = strdup(name);
+   if (!tmp)
+     {
+        wl_resource_post_no_memory(resource);
+        return;
+     }
+
+   VS_INF("SURFACE %p| Set name(old:%s, new:%s)", surface, surface->name ?: "", tmp);
+
+   free(surface->name);
+   surface->name = tmp;
+
+   e_surface_name_set(surface->surface, tmp);
+}
+
+static void
+_video_surface_cb_set_stand_alone(struct wl_client *client, struct wl_resource *resource)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+
+   if (!surface)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_NO_SURFACE,
+                               "wtz_video_surface.set_stand_alone sent after wl_surface has been destroyed");
+        return;
+     }
+
+   VS_INF("SURFACE %p| Set stand alone mode", surface);
+
+   if (surface->stand_alone)
+     return;
+
+   surface->stand_alone = EINA_TRUE;
+
+   if (surface->role == E_VIDEO_SURFACE_ROLE_SUBSURFACE)
+     {
+        e_subsurface_stand_alone_mode_set(surface->subsurface);
+     }
+   else if (surface->role == E_VIDEO_SURFACE_ROLE_VIEWPORT_SOURCE)
+     {
+        e_subsurface_stand_alone_mode_set(surface->source->subsurface);
+     }
+}
+
+static void
+_video_surface_cb_unset_stand_alone(struct wl_client *client, struct wl_resource *resource)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+
+   if (!surface)
+     {
+        wl_resource_post_error(resource, WTZ_VIDEO_SURFACE_ERROR_NO_SURFACE,
+                               "wtz_video_surface.unset_stand_alone sent after wl_surface has been destroyed");
+        return;
+     }
+
+   VS_INF("SURFACE %p| Unset stand alone mode", surface);
+
+   if (!surface->stand_alone)
+     return;
+
+   surface->stand_alone = EINA_FALSE;
+
+   if (surface->role == E_VIDEO_SURFACE_ROLE_SUBSURFACE)
+     {
+        e_subsurface_stand_alone_mode_unset(surface->subsurface);
+     }
+   else if (surface->role == E_VIDEO_SURFACE_ROLE_VIEWPORT_SOURCE)
+     {
+        e_subsurface_stand_alone_mode_unset(surface->source->subsurface);
+     }
+}
+
+static const struct wtz_video_surface_interface _video_surface_impl =
+{
+   .destroy = _video_surface_cb_destroy,
+   .get_viewport_source = _video_surface_cb_get_viewport_source,
+   .get_subsurface = _video_surface_cb_get_subsurface,
+   .set_name = _video_surface_cb_set_name,
+   .set_stand_alone = _video_surface_cb_set_stand_alone,
+   .unset_stand_alone = _video_surface_cb_unset_stand_alone,
+};
+
+static void
+_video_surface_destroy(E_Video_Surface *surface)
+{
+   VS_INF("SURFACE %p| Destroy", surface);
+
+   if (surface->source)
+     _source_destroy(surface->source);
+
+   if (surface->subsurface)
+     wl_list_remove(&surface->subsurface_destroy.link);
+
+   e_object_unref(E_OBJECT(surface->shell));
+   wl_resource_set_user_data(surface->resource, NULL);
+   wl_list_remove(&surface->surface_destroy.link);
+   free(surface->name);
+   free(surface);
+}
+
+static void
+_video_surface_cb_resource_destroy(struct wl_resource *resource)
+{
+   E_Video_Surface *surface = wl_resource_get_user_data(resource);
+
+   if (!surface)
+     return;
+
+   VS_INF("SURFACE %p| wl_resource(%p) destroyed", surface, surface->resource);
+
+   _video_surface_destroy(surface);
+}
+
+static void
+_video_surface_cb_surface_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Surface *surface = wl_container_of(listener, surface, surface_destroy);
+
+   VS_INF("SURFACE %p| E_Surface(%p) destroyed", surface, surface->surface);
+
+   _video_surface_destroy(surface);
+}
+
+static void
+_video_shell_cb_get_surface(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource)
+{
+   E_Surface *e_surface = e_surface_from_resource(surface_resource);
+   E_Video_Surface *surface;
+
+   assert(e_surface);
+
+   if (!e_surface_role_set(e_surface, e_video_surface_role_name, resource, WTZ_VIDEO_SHELL_ERROR_ROLE))
+     return;
+
+   surface = calloc(1, sizeof(*surface));
+   if (!surface)
+     {
+        wl_resource_post_no_memory(resource);
+        return;
+     }
+
+   surface->resource = wl_resource_create(client, &wtz_video_surface_interface, wl_resource_get_version(resource), id);
+   if (!surface->resource)
+     {
+        wl_resource_post_no_memory(resource);
+        free(surface);
+        return;
+     }
+   wl_resource_set_implementation(surface->resource, &_video_surface_impl, surface, _video_surface_cb_resource_destroy);
+
+   surface->role = E_VIDEO_SURFACE_ROLE_NONE;
+   surface->shell = wl_resource_get_user_data(resource);
+   e_object_ref(E_OBJECT(surface->shell));
+
+   surface->surface = e_surface;
+   surface->surface_destroy.notify = _video_surface_cb_surface_destroy;
+   e_surface_destroy_listener_add(surface->surface, &surface->surface_destroy);
+
+   VS_INF("SURFACE %p| Created with E_Surface(%p)", surface, e_surface);
+}
+
+static void
+_video_shell_cb_get_global_resource_id_from_handle(struct wl_client *client, struct wl_resource *resource, const char *handle)
+{
+   E_Video_Shell *shell = wl_resource_get_user_data(resource);
+   E_Video_Viewport *viewport;
+   E_Client *ec;
+   uint32_t id = 0;
+
+   VS_INF("SHELL %p| Get global resource id from handle(%s)", shell, handle);
+
+   viewport = eina_hash_find(shell->viewports, handle);
+   if (!viewport)
+     goto end;
+
+   ec = e_surface_ec_get(e_subsurface_surface_get(viewport->subsurface));
+   id = e_pixmap_res_id_get(ec->pixmap);
+
+end:
+   VS_INF("SHELL %p| Send global resource id(%d)", shell, id);
+   wtz_video_shell_send_global_resource_id(resource, id);
+}
+
+static const struct wtz_video_shell_interface _video_shell_impl =
+{
+   .export_viewport = _video_shell_cb_export_viewport,
+   .get_surface = _video_shell_cb_get_surface,
+   .get_global_resource_id_from_handle = _video_shell_cb_get_global_resource_id_from_handle,
+};
+
+static void
+_video_shell_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id)
+{
+   E_Video_Shell *shell = data;
+   struct wl_resource *resource;
+
+   resource = wl_resource_create(client, &wtz_video_shell_interface, version, id);
+   if (!resource)
+     return;
+
+   wl_resource_set_implementation(resource, &_video_shell_impl, shell, NULL);
+}
+
+static void
+_video_shell_cb_display_destroy(struct wl_listener *listener, void *data)
+{
+   E_Video_Shell *shell = wl_container_of(listener, shell, display_destroy);
+
+   VS_INF("SHELL %p| wl_display destroyed", shell);
+
+   wl_list_remove(&shell->display_destroy.link);
+   wl_global_destroy(shell->global);
+   e_object_del(E_OBJECT(shell));
+}
+
+static void
+_video_shell_free(E_Video_Shell *shell)
+{
+   VS_INF("SHELL %p| Free", shell);
+
+   eina_hash_free(shell->viewports);
+   free(shell);
+}
+
+EINTERN E_Video_Shell *
+e_video_shell_create(struct wl_display *display)
+{
+   E_Video_Shell *shell;
+
+   shell = E_OBJECT_ALLOC(E_Video_Shell, E_VIDEO_SHELL_TYPE, _video_shell_free);
+   if (!shell)
+     return NULL;
+
+   shell->viewports = eina_hash_string_superfast_new(NULL);
+   if (!shell->viewports)
+     {
+        free(shell);
+        return NULL;
+     }
+
+   shell->global = wl_global_create(display, &wtz_video_shell_interface, WTZ_VIDEO_SHELL_VERSION, shell, _video_shell_bind);
+   if (!shell->global)
+     {
+        eina_hash_free(shell->viewports);
+        free(shell);
+        return NULL;
+     }
+
+   shell->display = display;
+
+   shell->display_destroy.notify = _video_shell_cb_display_destroy;
+   wl_display_add_destroy_listener(display, &shell->display_destroy);
+
+   VS_INF("SHELL %p| Created", shell);
+
+   return shell;
+}
diff --git a/src/bin/server/e_video_shell_intern.h b/src/bin/server/e_video_shell_intern.h
new file mode 100644 (file)
index 0000000..8f3dd9c
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef E_VIDEO_SHELL_INTERN_H
+#define E_VIDEO_SHELL_INTERN_H
+
+struct E_Video_Shell;
+typedef struct E_Video_Shell E_Video_Shell;
+
+E_Video_Shell *e_video_shell_create(struct wl_display *display);
+
+#endif