--- /dev/null
+#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;
+}