/* GStreamer Wayland Library
 *
 * Copyright (C) 2011 Intel Corporation
 * Copyright (C) 2011 Sreerenj Balachandran <sreerenj.balachandran@intel.com>
 * Copyright (C) 2014 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 */

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

#include "gstwlwindow.h"

#include "color-management-v1-client-protocol.h"
#include "color-representation-v1-client-protocol.h"
#include "fullscreen-shell-unstable-v1-client-protocol.h"
#include "single-pixel-buffer-v1-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "xdg-shell-client-protocol.h"

#define GST_CAT_DEFAULT gst_wl_window_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);

typedef struct _GstWlWindowPrivate
{
  GObject parent_instance;

  GMutex *render_lock;

  GstWlDisplay *display;
  struct wl_surface *area_surface;
  struct wl_surface *area_surface_wrapper;
  struct wl_subsurface *area_subsurface;
  struct wp_viewport *area_viewport;
  struct wl_surface *video_surface;
  struct wl_surface *video_surface_wrapper;
  struct wl_subsurface *video_subsurface;
  struct wp_viewport *video_viewport;
  struct xdg_surface *xdg_surface;
  struct xdg_toplevel *xdg_toplevel;
  struct wp_color_management_surface_v1 *color_management_surface;
  struct wp_color_representation_surface_v1 *color_representation_surface;
  gboolean configured;
  GCond configure_cond;
  GMutex configure_mutex;

  /* the size and position of the area_(sub)surface */
  GstVideoRectangle render_rectangle;

  /* the size and position of the video_subsurface */
  GstVideoRectangle video_rectangle;

  /* the size of the video in the buffers (unpadded) */
  gint video_width, video_height;

  /* the size of the video in the buffers (padded) */
  gint buffer_width, buffer_height;

  /* default window dimension used when the compositor does not chose a size */
  gint default_width, default_height;

  /* video width scaled according to par */
  gint scaled_width;

  /* the crop rectangle */
  GstVideoRectangle crop;

  enum wl_output_transform buffer_transform;

  gboolean force_aspect_ratio;

  /* when this is not set both the area_surface and the video_surface are not
   * visible and certain steps should be skipped */
  gboolean is_area_surface_mapped;

  GMutex window_lock;
  GstWlBuffer *next_buffer;
  GstVideoInfo *next_video_info;
  GstVideoMasteringDisplayInfo *next_minfo;
  GstVideoContentLightLevel *next_linfo;
  GstWlBuffer *staged_buffer;
  gboolean clear_window;
  struct wl_callback *frame_callback;
  struct wl_callback *commit_callback;
} GstWlWindowPrivate;

G_DEFINE_TYPE_WITH_CODE (GstWlWindow, gst_wl_window, G_TYPE_OBJECT,
    G_ADD_PRIVATE (GstWlWindow)
    GST_DEBUG_CATEGORY_INIT (gst_wl_window_debug,
        "wlwindow", 0, "wlwindow library");
    );

enum
{
  CLOSED,
  MAP,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void gst_wl_window_finalize (GObject * gobject);

static void gst_wl_window_update_geometry (GstWlWindow * self);

static void gst_wl_window_update_borders (GstWlWindow * self);

static void gst_wl_window_commit_buffer (GstWlWindow * self,
    GstWlBuffer * buffer);

static void gst_wl_window_set_colorimetry (GstWlWindow * self,
    const GstVideoColorimetry * colorimetry,
    const GstVideoMasteringDisplayInfo * minfo,
    const GstVideoContentLightLevel * linfo);

static void
handle_xdg_toplevel_close (void *data, struct xdg_toplevel *xdg_toplevel)
{
  GstWlWindow *self = data;

  GST_DEBUG_OBJECT (self, "XDG toplevel got a \"close\" event.");
  g_signal_emit (self, signals[CLOSED], 0);
}

static void
handle_xdg_toplevel_configure (void *data, struct xdg_toplevel *xdg_toplevel,
    int32_t width, int32_t height, struct wl_array *states)
{
  GstWlWindow *self = data;
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  const uint32_t *state;

  GST_DEBUG_OBJECT (self, "XDG toplevel got a \"configure\" event, [ %d, %d ].",
      width, height);

  wl_array_for_each (state, states) {
    switch (*state) {
      case XDG_TOPLEVEL_STATE_FULLSCREEN:
        GST_DEBUG_OBJECT (self, "XDG top-level now FULLSCREEN");
        break;
      case XDG_TOPLEVEL_STATE_MAXIMIZED:
        GST_DEBUG_OBJECT (self, "XDG top-level now MAXIMIXED");
        break;
      case XDG_TOPLEVEL_STATE_RESIZING:
        GST_DEBUG_OBJECT (self, "XDG top-level being RESIZED");
        break;
      case XDG_TOPLEVEL_STATE_ACTIVATED:
        GST_DEBUG_OBJECT (self, "XDG top-level being ACTIVATED");
        break;
    }
  }

  if (width <= 0 || height <= 0) {
    width = priv->default_width;
    height = priv->default_height;
  }

  g_mutex_lock (&priv->configure_mutex);
  priv->configured = FALSE;
  gst_wl_window_set_render_rectangle (self, 0, 0, width, height);
  g_mutex_unlock (&priv->configure_mutex);
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
  handle_xdg_toplevel_configure,
  handle_xdg_toplevel_close,
};

static void
handle_xdg_surface_configure (void *data, struct xdg_surface *xdg_surface,
    uint32_t serial)
{
  GstWlWindow *self = data;
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  xdg_surface_ack_configure (xdg_surface, serial);

  g_mutex_lock (&priv->configure_mutex);
  priv->configured = TRUE;
  g_cond_signal (&priv->configure_cond);
  gst_wl_window_update_geometry (self);
  g_mutex_unlock (&priv->configure_mutex);
}

static const struct xdg_surface_listener xdg_surface_listener = {
  handle_xdg_surface_configure,
};

static void
gst_wl_window_class_init (GstWlWindowClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = gst_wl_window_finalize;

  signals[CLOSED] = g_signal_new ("closed", G_TYPE_FROM_CLASS (gobject_class),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
  signals[MAP] = g_signal_new ("map", G_TYPE_FROM_CLASS (gobject_class),
      G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);

}

static void
gst_wl_window_init (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  priv->configured = TRUE;
  g_cond_init (&priv->configure_cond);
  g_mutex_init (&priv->configure_mutex);
  g_mutex_init (&priv->window_lock);
}

static void
gst_wl_window_finalize (GObject * gobject)
{
  GstWlWindow *self = GST_WL_WINDOW (gobject);
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  gst_wl_display_callback_destroy (priv->display, &priv->frame_callback);
  gst_wl_display_callback_destroy (priv->display, &priv->commit_callback);
  gst_wl_display_object_destroy (priv->display,
      (gpointer *) & priv->xdg_toplevel, (GDestroyNotify) xdg_toplevel_destroy);
  gst_wl_display_object_destroy (priv->display,
      (gpointer *) & priv->xdg_surface, (GDestroyNotify) xdg_surface_destroy);

  if (priv->staged_buffer)
    gst_wl_buffer_unref_buffer (priv->staged_buffer);

  g_cond_clear (&priv->configure_cond);
  g_mutex_clear (&priv->configure_mutex);
  g_mutex_clear (&priv->window_lock);

  if (priv->video_viewport)
    wp_viewport_destroy (priv->video_viewport);

  if (priv->color_management_surface)
    wp_color_management_surface_v1_destroy (priv->color_management_surface);

  if (priv->color_representation_surface)
    wp_color_representation_surface_v1_destroy
        (priv->color_representation_surface);

  wl_proxy_wrapper_destroy (priv->video_surface_wrapper);
  wl_subsurface_destroy (priv->video_subsurface);
  wl_surface_destroy (priv->video_surface);

  if (priv->area_subsurface)
    wl_subsurface_destroy (priv->area_subsurface);

  if (priv->area_viewport)
    wp_viewport_destroy (priv->area_viewport);

  wl_proxy_wrapper_destroy (priv->area_surface_wrapper);
  wl_surface_destroy (priv->area_surface);

  g_clear_object (&priv->display);

  G_OBJECT_CLASS (gst_wl_window_parent_class)->finalize (gobject);
}

static GstWlWindow *
gst_wl_window_new_internal (GstWlDisplay * display, GMutex * render_lock)
{
  GstWlWindow *self;
  GstWlWindowPrivate *priv;
  struct wl_compositor *compositor;
  struct wl_event_queue *event_queue;
  struct wl_region *region;
  struct wp_viewporter *viewporter;

  self = g_object_new (GST_TYPE_WL_WINDOW, NULL);
  priv = gst_wl_window_get_instance_private (self);
  priv->display = g_object_ref (display);
  priv->render_lock = render_lock;
  g_cond_init (&priv->configure_cond);
  priv->force_aspect_ratio = TRUE;

  compositor = gst_wl_display_get_compositor (display);
  priv->area_surface = wl_compositor_create_surface (compositor);
  priv->video_surface = wl_compositor_create_surface (compositor);

  priv->area_surface_wrapper = wl_proxy_create_wrapper (priv->area_surface);
  priv->video_surface_wrapper = wl_proxy_create_wrapper (priv->video_surface);

  event_queue = gst_wl_display_get_event_queue (display);
  wl_proxy_set_queue ((struct wl_proxy *) priv->area_surface_wrapper,
      event_queue);
  wl_proxy_set_queue ((struct wl_proxy *) priv->video_surface_wrapper,
      event_queue);

  /* embed video_surface in area_surface */
  priv->video_subsurface =
      wl_subcompositor_get_subsurface (gst_wl_display_get_subcompositor
      (display), priv->video_surface, priv->area_surface);
  wl_subsurface_set_desync (priv->video_subsurface);

  viewporter = gst_wl_display_get_viewporter (display);
  if (viewporter) {
    priv->area_viewport = wp_viewporter_get_viewport (viewporter,
        priv->area_surface);
    priv->video_viewport = wp_viewporter_get_viewport (viewporter,
        priv->video_surface);
  }

  /* never accept input events on the video surface */
  region = wl_compositor_create_region (compositor);
  wl_surface_set_input_region (priv->video_surface, region);
  wl_region_destroy (region);

  return self;
}

/**
 * gst_wl_window_ensure_fullscreen_for_output:
 * @self: A #GstWlWindow
 * @fullscreen: %TRUE to set fullscreen, %FALSE to unset it
 * @output_nane: (nullable): The name of the wl_output to fullscreen to
 *
 * Ensure the window fullscreen state matches the desired state. If a
 * output_name is provided, and this output exists, the window will be set to
 * fullscreen on that screen. Otherwise the compisitor will decide.
 *
 * Since: 1.28
 */
void
gst_wl_window_ensure_fullscreen_for_output (GstWlWindow * self,
    gboolean fullscreen, const gchar * output_name)
{
  GstWlWindowPrivate *priv;
  GstWlOutput *output = NULL;
  struct wl_output *wl_output = NULL;

  g_return_if_fail (self);
  priv = gst_wl_window_get_instance_private (self);

  if (!fullscreen) {
    xdg_toplevel_unset_fullscreen (priv->xdg_toplevel);
    wl_display_flush (gst_wl_display_get_display (priv->display));
    return;
  }

  if (output_name) {
    output = gst_wl_display_get_output_by_name (priv->display, output_name);
    if (output)
      wl_output = gst_wl_output_get_wl_output (output);
    else
      GST_WARNING ("Could not find any output named '%s'", output_name);
  }

  xdg_toplevel_set_fullscreen (priv->xdg_toplevel, wl_output);
  wl_display_flush (gst_wl_display_get_display (priv->display));

  // Unref last for thread safety
  if (output)
    g_object_unref (output);
}

/**
 * gst_wl_window_ensure_fullscreen:
 * @self: A #GstWlWindow
 * @fullscreen: %TRUE to set fullscreen, %FALSE to unset it
 *
 * Same as gst_wl_window_ensure_fullscreen_for_output() without specifying an
 * output.
 */
void
gst_wl_window_ensure_fullscreen (GstWlWindow * self, gboolean fullscreen)
{
  gst_wl_window_ensure_fullscreen_for_output (self, fullscreen, NULL);
}

GstWlWindow *
gst_wl_window_new_toplevel_full (GstWlDisplay * display,
    const GstVideoInfo * info, gboolean fullscreen, const gchar * output_name,
    GMutex * render_lock)
{
  GstWlWindow *self;
  GstWlWindowPrivate *priv;
  struct xdg_wm_base *xdg_wm_base;
  struct zwp_fullscreen_shell_v1 *fullscreen_shell;

  self = gst_wl_window_new_internal (display, render_lock);
  priv = gst_wl_window_get_instance_private (self);

  xdg_wm_base = gst_wl_display_get_xdg_wm_base (display);
  fullscreen_shell = gst_wl_display_get_fullscreen_shell_v1 (display);

  /* Check which protocol we will use (in order of preference) */
  if (xdg_wm_base) {
    gint64 timeout;

    /* First create the XDG surface */
    priv->xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base,
        priv->area_surface);
    if (!priv->xdg_surface) {
      GST_ERROR_OBJECT (self, "Unable to get xdg_surface");
      goto error;
    }
    xdg_surface_add_listener (priv->xdg_surface, &xdg_surface_listener, self);

    /* Then the toplevel */
    priv->xdg_toplevel = xdg_surface_get_toplevel (priv->xdg_surface);
    if (!priv->xdg_toplevel) {
      GST_ERROR_OBJECT (self, "Unable to get xdg_toplevel");
      goto error;
    }
    xdg_toplevel_add_listener (priv->xdg_toplevel,
        &xdg_toplevel_listener, self);
    if (g_get_prgname ()) {
      xdg_toplevel_set_app_id (priv->xdg_toplevel, g_get_prgname ());
    } else {
      xdg_toplevel_set_app_id (priv->xdg_toplevel, "org.gstreamer.wayland");
    }

    gst_wl_window_ensure_fullscreen_for_output (self, fullscreen, output_name);

    /* Finally, commit the xdg_surface state as toplevel */
    priv->configured = FALSE;

    /* set the initial size to be the same as the reported video size */
    priv->default_width =
        gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
    priv->default_height = info->height;
    gst_wl_window_set_render_rectangle (self, 0, 0, priv->default_width,
        priv->default_height);

    GST_INFO_OBJECT (self, "Configured default rectangle to %ix%i",
        priv->default_width, priv->default_height);

    wl_surface_commit (priv->area_surface);
    wl_display_flush (gst_wl_display_get_display (display));

    g_mutex_lock (&priv->configure_mutex);
    timeout = g_get_monotonic_time () + 5 * G_TIME_SPAN_SECOND;
    while (!priv->configured) {
      if (!g_cond_wait_until (&priv->configure_cond, &priv->configure_mutex,
              timeout)) {
        GST_WARNING_OBJECT (self,
            "The compositor did not send configure event.");
        break;
      }
    }
    g_mutex_unlock (&priv->configure_mutex);
  } else if (fullscreen_shell) {
    GstWlOutput *output = NULL;
    struct wl_output *wl_output = NULL;
    if (output_name) {
      output = gst_wl_display_get_output_by_name (priv->display, output_name);
      wl_output = gst_wl_output_get_wl_output (output);
    }

    zwp_fullscreen_shell_v1_present_surface (fullscreen_shell,
        priv->area_surface, ZWP_FULLSCREEN_SHELL_V1_PRESENT_METHOD_ZOOM,
        wl_output);

    if (output)
      gst_object_unref (output);
  } else {
    GST_ERROR_OBJECT (self,
        "Unable to use either xdg_wm_base or zwp_fullscreen_shell.");
    goto error;
  }


  return self;

error:
  g_object_unref (self);
  return NULL;
}

GstWlWindow *
gst_wl_window_new_toplevel (GstWlDisplay * display, const GstVideoInfo * info,
    gboolean fullscreen, GMutex * render_lock)
{
  return gst_wl_window_new_toplevel_full (display, info, fullscreen, NULL,
      render_lock);
}

GstWlWindow *
gst_wl_window_new_in_surface (GstWlDisplay * display,
    struct wl_surface *parent, GMutex * render_lock)
{
  GstWlWindow *self;
  GstWlWindowPrivate *priv;
  struct wl_region *region;

  self = gst_wl_window_new_internal (display, render_lock);
  priv = gst_wl_window_get_instance_private (self);

  /* do not accept input events on the area surface when embedded */
  region =
      wl_compositor_create_region (gst_wl_display_get_compositor (display));
  wl_surface_set_input_region (priv->area_surface, region);
  wl_region_destroy (region);

  /* embed in parent */
  priv->area_subsurface =
      wl_subcompositor_get_subsurface (gst_wl_display_get_subcompositor
      (display), priv->area_surface, parent);
  wl_subsurface_set_desync (priv->area_subsurface);

  wl_surface_commit (parent);

  return self;
}

GstWlDisplay *
gst_wl_window_get_display (GstWlWindow * self)
{
  GstWlWindowPrivate *priv;

  g_return_val_if_fail (self != NULL, NULL);

  priv = gst_wl_window_get_instance_private (self);
  return g_object_ref (priv->display);
}

struct wl_surface *
gst_wl_window_get_wl_surface (GstWlWindow * self)
{
  GstWlWindowPrivate *priv;

  g_return_val_if_fail (self != NULL, NULL);

  priv = gst_wl_window_get_instance_private (self);
  return priv->video_surface_wrapper;
}

struct wl_subsurface *
gst_wl_window_get_subsurface (GstWlWindow * self)
{
  GstWlWindowPrivate *priv;

  g_return_val_if_fail (self != NULL, NULL);

  priv = gst_wl_window_get_instance_private (self);
  return priv->area_subsurface;
}

gboolean
gst_wl_window_is_toplevel (GstWlWindow * self)
{
  GstWlWindowPrivate *priv;

  g_return_val_if_fail (self != NULL, FALSE);

  priv = gst_wl_window_get_instance_private (self);
  return (priv->xdg_toplevel != NULL);
}

static void
gst_wl_window_resize_video_surface (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  GstVideoRectangle src = { 0, };
  GstVideoRectangle wp_src = { 0, };
  GstVideoRectangle dst = { 0, };
  GstVideoRectangle res;

  /* viewport coordinates will be based on the trasnformed surface */
  wl_surface_set_buffer_transform (priv->video_surface_wrapper,
      priv->buffer_transform);

  /* adjust the width/height base on the rotation */
  switch (priv->buffer_transform) {
    case WL_OUTPUT_TRANSFORM_NORMAL:
    case WL_OUTPUT_TRANSFORM_180:
    case WL_OUTPUT_TRANSFORM_FLIPPED:
    case WL_OUTPUT_TRANSFORM_FLIPPED_180:
      src.w = priv->scaled_width;
      src.h = priv->video_height;
      wp_src.w = priv->crop.w;
      wp_src.h = priv->crop.h;
      break;
    case WL_OUTPUT_TRANSFORM_90:
    case WL_OUTPUT_TRANSFORM_270:
    case WL_OUTPUT_TRANSFORM_FLIPPED_90:
    case WL_OUTPUT_TRANSFORM_FLIPPED_270:
      src.w = priv->video_height;
      src.h = priv->scaled_width;
      wp_src.w = priv->crop.h;
      wp_src.h = priv->crop.w;
      break;
    default:
      g_assert_not_reached ();
  }

  /* apply the x/y crop based on the transformation */
  switch (priv->buffer_transform) {
    case WL_OUTPUT_TRANSFORM_NORMAL:
      wp_src.x = priv->crop.x;
      wp_src.y = priv->crop.y;
      break;
    case WL_OUTPUT_TRANSFORM_180:
      wp_src.x = priv->buffer_width - (priv->crop.w + priv->crop.x);
      wp_src.y = priv->buffer_height - (priv->crop.h + priv->crop.y);
      break;
    case WL_OUTPUT_TRANSFORM_FLIPPED:
      wp_src.x = priv->buffer_width - (priv->crop.w + priv->crop.x);
      wp_src.y = priv->crop.y;
      break;
    case WL_OUTPUT_TRANSFORM_FLIPPED_180:
      wp_src.x = priv->crop.x;
      wp_src.y = priv->buffer_height - (priv->crop.h + priv->crop.y);
      break;
    case WL_OUTPUT_TRANSFORM_90:
      wp_src.x = priv->buffer_height - (priv->crop.h + priv->crop.y);
      wp_src.y = priv->crop.x;
      break;
    case WL_OUTPUT_TRANSFORM_270:
      wp_src.x = priv->crop.y;
      wp_src.y = priv->buffer_width - (priv->crop.w + priv->crop.x);
      break;
    case WL_OUTPUT_TRANSFORM_FLIPPED_270:
      wp_src.x = priv->buffer_height - (priv->crop.h + priv->crop.y);
      wp_src.y = priv->buffer_width - (priv->crop.w + priv->crop.x);
      break;
    case WL_OUTPUT_TRANSFORM_FLIPPED_90:
      wp_src.x = priv->crop.y;
      wp_src.y = priv->crop.x;
      break;

    default:
      g_assert_not_reached ();
  }

  dst.w = priv->render_rectangle.w;
  dst.h = priv->render_rectangle.h;

  /* center the video_subsurface inside area_subsurface */
  if (priv->video_viewport) {
    if (!priv->force_aspect_ratio)
      res = dst;
    else
      gst_video_center_rect (&src, &dst, &res, TRUE);
    wp_viewport_set_source (priv->video_viewport, wl_fixed_from_int (wp_src.x),
        wl_fixed_from_int (wp_src.y), wl_fixed_from_int (wp_src.w),
        wl_fixed_from_int (wp_src.h));

    /* The protocol does not allow for a size set to 0 */
    res.w = MAX (res.w, 1);
    res.h = MAX (res.h, 1);

    wp_viewport_set_destination (priv->video_viewport, res.w, res.h);
  } else {
    gst_video_center_rect (&src, &dst, &res, FALSE);
  }

  wl_subsurface_set_position (priv->video_subsurface, res.x, res.y);

  priv->video_rectangle = res;
}

static void
gst_wl_window_set_opaque (GstWlWindow * self, const GstVideoInfo * info)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  struct wl_compositor *compositor;
  struct wl_region *region;

  /* Set area opaque */
  compositor = gst_wl_display_get_compositor (priv->display);
  region = wl_compositor_create_region (compositor);
  wl_region_add (region, 0, 0, G_MAXINT32, G_MAXINT32);
  wl_surface_set_opaque_region (priv->area_surface, region);
  wl_region_destroy (region);

  if (!GST_VIDEO_INFO_HAS_ALPHA (info)) {
    /* Set video opaque */
    region = wl_compositor_create_region (compositor);
    wl_region_add (region, 0, 0, G_MAXINT32, G_MAXINT32);
    wl_surface_set_opaque_region (priv->video_surface, region);
    wl_region_destroy (region);
  }
}

static void
frame_redraw_callback (void *data, struct wl_callback *callback, uint32_t time)
{
  GstWlWindow *self = data;
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  GstWlBuffer *next_buffer;

  GST_DEBUG_OBJECT (self, "frame_redraw_cb");

  wl_callback_destroy (callback);
  priv->frame_callback = NULL;

  g_mutex_lock (&priv->window_lock);
  next_buffer = priv->next_buffer = priv->staged_buffer;
  priv->staged_buffer = NULL;
  g_mutex_unlock (&priv->window_lock);

  if (next_buffer || priv->clear_window)
    gst_wl_window_commit_buffer (self, next_buffer);

  if (next_buffer)
    gst_wl_buffer_unref_buffer (next_buffer);
}

static const struct wl_callback_listener frame_callback_listener = {
  frame_redraw_callback
};

static gboolean
gst_wl_window_crop_rectangle_changed (GstWlWindow * self,
    const GstVideoRectangle * pending_crop)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  if (priv->crop.x == pending_crop->x
      && priv->crop.y == pending_crop->y
      && priv->crop.w == pending_crop->w && priv->crop.h == pending_crop->h)
    return FALSE;

  return TRUE;
}

static void
gst_wl_window_commit_buffer (GstWlWindow * self, GstWlBuffer * buffer)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  GstVideoInfo *info = priv->next_video_info;
  GstVideoMasteringDisplayInfo *minfo = priv->next_minfo;
  GstVideoContentLightLevel *linfo = priv->next_linfo;
  struct wl_callback *callback;
  gboolean needs_layout_update = FALSE;
  GstVideoRectangle crop = priv->crop;

  if (G_UNLIKELY (info)) {
    priv->scaled_width =
        gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
    priv->video_width = priv->buffer_width = info->width;
    priv->video_height = priv->buffer_height = info->height;

    /* we don't have video_width/height saved initially, so if we didn't have a
     * crop meta the width/height needs to be fixed from its reset value of 0 */
    if (crop.w == 0)
      crop.w = priv->video_width;
    if (crop.h == 0)
      crop.h = priv->video_height;

    needs_layout_update = TRUE;
  }

  if (G_LIKELY (buffer)) {
    GstVideoMeta *vmeta = gst_wl_buffer_get_video_meta (buffer);
    GstVideoCropMeta *cmeta = gst_wl_buffer_get_video_crop_meta (buffer);

    if (vmeta && (priv->buffer_width != vmeta->width
            || priv->buffer_height != vmeta->height)) {
      priv->buffer_width = vmeta->width;
      priv->buffer_height = vmeta->height;
      needs_layout_update = TRUE;
    }

    if (cmeta) {
      crop.x = cmeta->x;
      crop.y = cmeta->y;
      crop.w = cmeta->width;
      crop.h = cmeta->height;
    }
  }

  if (gst_wl_window_crop_rectangle_changed (self, &crop)) {
    priv->crop = crop;
    needs_layout_update = TRUE;
  }

  if (G_UNLIKELY (needs_layout_update)) {
    wl_subsurface_set_sync (priv->video_subsurface);
    gst_wl_window_resize_video_surface (self);
    gst_wl_window_set_opaque (self, info);

    gst_wl_window_set_colorimetry (self, &info->colorimetry, minfo, linfo);
  }

  if (G_LIKELY (buffer)) {
    callback = wl_surface_frame (priv->video_surface_wrapper);
    priv->frame_callback = callback;
    wl_callback_add_listener (callback, &frame_callback_listener, self);
    gst_wl_buffer_attach (buffer, priv->video_surface_wrapper);
    wl_surface_damage_buffer (priv->video_surface_wrapper, 0, 0, G_MAXINT32,
        G_MAXINT32);
    wl_surface_commit (priv->video_surface_wrapper);

    if (!priv->is_area_surface_mapped) {
      gst_wl_window_update_borders (self);
      wl_surface_commit (priv->area_surface_wrapper);
      priv->is_area_surface_mapped = TRUE;
      g_signal_emit (self, signals[MAP], 0);
    }
  } else {
    /* clear both video and parent surfaces */
    wl_surface_attach (priv->video_surface_wrapper, NULL, 0, 0);
    wl_surface_commit (priv->video_surface_wrapper);
    wl_surface_attach (priv->area_surface_wrapper, NULL, 0, 0);
    wl_surface_commit (priv->area_surface_wrapper);
    priv->is_area_surface_mapped = FALSE;
    priv->clear_window = FALSE;
  }

  if (G_UNLIKELY (needs_layout_update)) {
    /* commit also the parent (area_surface) in order to change
     * the position of the video_subsurface */
    wl_surface_commit (priv->area_surface_wrapper);
    wl_subsurface_set_desync (priv->video_subsurface);
    gst_video_info_free (priv->next_video_info);
    priv->next_video_info = NULL;
    g_clear_pointer (&priv->next_minfo, g_free);
    g_clear_pointer (&priv->next_linfo, g_free);
  }

}

static void
commit_callback (void *data, struct wl_callback *callback, uint32_t serial)
{
  GstWlWindow *self = data;
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  GstWlBuffer *next_buffer;

  wl_callback_destroy (callback);
  priv->commit_callback = NULL;

  g_mutex_lock (&priv->window_lock);
  next_buffer = priv->next_buffer;
  g_mutex_unlock (&priv->window_lock);

  gst_wl_window_commit_buffer (self, next_buffer);

  if (next_buffer)
    gst_wl_buffer_unref_buffer (next_buffer);
}

static const struct wl_callback_listener commit_listener = {
  commit_callback
};

gboolean
gst_wl_window_render (GstWlWindow * self, GstWlBuffer * buffer,
    const GstVideoInfo * info)
{
  return gst_wl_window_render_hdr (self, buffer, info, NULL, NULL);
}

gboolean
gst_wl_window_render_hdr (GstWlWindow * self, GstWlBuffer * buffer,
    const GstVideoInfo * info, const GstVideoMasteringDisplayInfo * minfo,
    const GstVideoContentLightLevel * linfo)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  gboolean ret = TRUE;

  if (G_LIKELY (buffer))
    gst_wl_buffer_ref_gst_buffer (buffer);

  g_mutex_lock (&priv->window_lock);
  if (G_UNLIKELY (info)) {
    gst_video_info_free (priv->next_video_info);
    priv->next_video_info = gst_video_info_copy (info);
  }

  if (G_UNLIKELY (minfo)) {
    g_clear_pointer (&priv->next_minfo, g_free);
    priv->next_minfo = g_memdup2 (minfo, sizeof (*minfo));
  }

  if (G_UNLIKELY (linfo)) {
    g_clear_pointer (&priv->next_linfo, g_free);
    priv->next_linfo = g_memdup2 (linfo, sizeof (*linfo));
  }

  if (priv->next_buffer && priv->staged_buffer) {
    GST_LOG_OBJECT (self, "buffer %p dropped (replaced)", priv->staged_buffer);
    gst_wl_buffer_unref_buffer (priv->staged_buffer);
    ret = FALSE;
  }

  if (!priv->next_buffer) {
    priv->next_buffer = buffer;
    priv->commit_callback =
        gst_wl_display_sync (priv->display, &commit_listener, self);
    wl_display_flush (gst_wl_display_get_display (priv->display));
  } else {
    priv->staged_buffer = buffer;
  }
  if (!buffer)
    priv->clear_window = TRUE;

  g_mutex_unlock (&priv->window_lock);

  return ret;
}

/**
 * gst_wl_window_flush:
 * @self: a #GstWlWindow
 *
 * Releases and drops the currently staged buffer associated with the window,
 * if one exists. This function is thread-safe and will set the staged buffer
 * pointer to NULL after unreferencing it.
 *
 * Returns: %TRUE if flush successful, %FALSE otherwise.
 *
 * Since: 1.28
 */
gboolean
gst_wl_window_flush (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  g_mutex_lock (&priv->window_lock);
  if (priv->staged_buffer) {
    GST_LOG_OBJECT (self, "drop buffer %p", priv->staged_buffer);
    gst_wl_buffer_unref_buffer (priv->staged_buffer);
    priv->staged_buffer = NULL;
  }
  g_mutex_unlock (&priv->window_lock);

  return TRUE;
}

/* Update the buffer used to draw black borders. When we have viewporter
 * support, this is a scaled up 1x1 image, and without we need an black image
 * the size of the rendering areay. */
static void
gst_wl_window_update_borders (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  gint width, height;
  GstBuffer *buf;
  struct wl_buffer *wlbuf;
  struct wp_single_pixel_buffer_manager_v1 *single_pixel;
  GstWlBuffer *gwlbuf;

  if (gst_wl_display_get_viewporter (priv->display)) {
    wp_viewport_set_destination (priv->area_viewport,
        priv->render_rectangle.w, priv->render_rectangle.h);

    if (priv->is_area_surface_mapped) {
      /* The area_surface is already visible and only needed to get resized.
       * We don't need to attach a new buffer and are done here. */
      return;
    }
  }

  if (gst_wl_display_get_viewporter (priv->display)) {
    width = height = 1;
  } else {
    width = priv->render_rectangle.w;
    height = priv->render_rectangle.h;
  }

  /* draw the area_subsurface */
  single_pixel =
      gst_wl_display_get_single_pixel_buffer_manager_v1 (priv->display);
  if (width == 1 && height == 1 && single_pixel) {
    buf = gst_buffer_new_allocate (NULL, 1, NULL);
    wlbuf =
        wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer (single_pixel,
        0, 0, 0, 0xffffffffU);
  } else {
    GstVideoFormat format;
    GstVideoInfo info;
    GstAllocator *alloc;

    /* we want WL_SHM_FORMAT_XRGB8888 */
    format = GST_VIDEO_FORMAT_BGRx;
    gst_video_info_set_format (&info, format, width, height);
    alloc = gst_shm_allocator_get ();

    buf = gst_buffer_new_allocate (alloc, info.size, NULL);
    gst_buffer_memset (buf, 0, 0, info.size);

    wlbuf =
        gst_wl_shm_memory_construct_wl_buffer (gst_buffer_peek_memory (buf, 0),
        priv->display, &info);

    g_object_unref (alloc);
  }

  gwlbuf = gst_buffer_add_wl_buffer (buf, wlbuf, priv->display);
  gst_wl_buffer_attach (gwlbuf, priv->area_surface_wrapper);
  wl_surface_damage_buffer (priv->area_surface_wrapper, 0, 0, G_MAXINT32,
      G_MAXINT32);

  /* at this point, the GstWlBuffer keeps the buffer
   * alive and will free it on wl_buffer::release */
  gst_buffer_unref (buf);
}

static void
gst_wl_window_update_geometry (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  /* position the area inside the parent - needs a parent commit to apply */
  if (priv->area_subsurface) {
    wl_subsurface_set_position (priv->area_subsurface, priv->render_rectangle.x,
        priv->render_rectangle.y);
  }

  if (priv->is_area_surface_mapped)
    gst_wl_window_update_borders (self);

  if (!priv->configured)
    return;

  if (priv->scaled_width != 0) {
    wl_subsurface_set_sync (priv->video_subsurface);
    gst_wl_window_resize_video_surface (self);
    wl_surface_commit (priv->video_surface_wrapper);
  }

  wl_surface_commit (priv->area_surface_wrapper);

  if (priv->scaled_width != 0)
    wl_subsurface_set_desync (priv->video_subsurface);
}

void
gst_wl_window_set_render_rectangle (GstWlWindow * self, gint x, gint y,
    gint w, gint h)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  if (priv->render_rectangle.x == x && priv->render_rectangle.y == y &&
      priv->render_rectangle.w == w && priv->render_rectangle.h == h)
    return;

  priv->render_rectangle.x = x;
  priv->render_rectangle.y = y;
  priv->render_rectangle.w = w;
  priv->render_rectangle.h = h;

  gst_wl_window_update_geometry (self);
}

const GstVideoRectangle *
gst_wl_window_get_render_rectangle (GstWlWindow * self)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  return &priv->render_rectangle;
}

static enum wl_output_transform
output_transform_from_orientation_method (GstVideoOrientationMethod method)
{
  switch (method) {
    case GST_VIDEO_ORIENTATION_IDENTITY:
      return WL_OUTPUT_TRANSFORM_NORMAL;
    case GST_VIDEO_ORIENTATION_90R:
      return WL_OUTPUT_TRANSFORM_90;
    case GST_VIDEO_ORIENTATION_180:
      return WL_OUTPUT_TRANSFORM_180;
    case GST_VIDEO_ORIENTATION_90L:
      return WL_OUTPUT_TRANSFORM_270;
    case GST_VIDEO_ORIENTATION_HORIZ:
      return WL_OUTPUT_TRANSFORM_FLIPPED;
    case GST_VIDEO_ORIENTATION_VERT:
      return WL_OUTPUT_TRANSFORM_FLIPPED_180;
    case GST_VIDEO_ORIENTATION_UL_LR:
      return WL_OUTPUT_TRANSFORM_FLIPPED_90;
    case GST_VIDEO_ORIENTATION_UR_LL:
      return WL_OUTPUT_TRANSFORM_FLIPPED_270;
    default:
      g_assert_not_reached ();
  }
}

void
gst_wl_window_set_rotate_method (GstWlWindow * self,
    GstVideoOrientationMethod method)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  priv->buffer_transform = output_transform_from_orientation_method (method);

  gst_wl_window_update_geometry (self);
}

enum ImageDescriptionFeedback
{
  IMAGE_DESCRIPTION_FEEDBACK_UNKNOWN = 0,
  IMAGE_DESCRIPTION_FEEDBACK_READY,
  IMAGE_DESCRIPTION_FEEDBACK_FAILED,
};

static void
image_description_failed (void *data,
    struct wp_image_description_v1 *wp_image_description_v1, uint32_t cause,
    const char *msg)
{
  enum ImageDescriptionFeedback *image_description_feedback = data;

  *image_description_feedback = IMAGE_DESCRIPTION_FEEDBACK_FAILED;
}

static void
image_description_ready (void *data,
    struct wp_image_description_v1 *wp_image_description_v1, uint32_t identity)
{
  enum ImageDescriptionFeedback *image_description_feedback = data;

  *image_description_feedback = IMAGE_DESCRIPTION_FEEDBACK_READY;
}

static const struct wp_image_description_v1_listener description_listerer = {
  .failed = image_description_failed,
  .ready = image_description_ready,
};

static enum wp_color_manager_v1_transfer_function
gst_colorimetry_tf_to_wl (GstVideoTransferFunction tf)
{
  switch (tf) {
    case GST_VIDEO_TRANSFER_SRGB:
      return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB;
    case GST_VIDEO_TRANSFER_BT601:
    case GST_VIDEO_TRANSFER_BT709:
    case GST_VIDEO_TRANSFER_BT2020_10:
      return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886;
    case GST_VIDEO_TRANSFER_SMPTE2084:
      return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ;
    case GST_VIDEO_TRANSFER_ARIB_STD_B67:
      return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG;
    default:
      GST_WARNING ("Transfer function not handled");
      return 0;
  }
}

static enum wp_color_manager_v1_primaries
gst_colorimetry_primaries_to_wl (GstVideoColorPrimaries primaries)
{
  switch (primaries) {
    case GST_VIDEO_COLOR_PRIMARIES_BT709:
      return WP_COLOR_MANAGER_V1_PRIMARIES_SRGB;
    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
      return WP_COLOR_MANAGER_V1_PRIMARIES_NTSC;
    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
      return WP_COLOR_MANAGER_V1_PRIMARIES_BT2020;
    default:
      GST_WARNING ("Primaries not handled");
      return 0;
  }
}

static enum wp_color_representation_surface_v1_coefficients
gst_colorimetry_matrix_to_wl (GstVideoColorMatrix matrix)
{
  switch (matrix) {
    case GST_VIDEO_COLOR_MATRIX_RGB:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY;
    case GST_VIDEO_COLOR_MATRIX_BT709:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT709;
    case GST_VIDEO_COLOR_MATRIX_BT601:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601;
    case GST_VIDEO_COLOR_MATRIX_BT2020:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020;
    default:
      GST_WARNING ("Matrix not handled");
      return 0;
  }
}

static enum wp_color_representation_surface_v1_range
gst_colorimetry_range_to_wl (GstVideoColorRange range)
{
  switch (range) {
    case GST_VIDEO_COLOR_RANGE_0_255:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL;
    case GST_VIDEO_COLOR_RANGE_16_235:
      return WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED;
    default:
      GST_WARNING ("Range not handled");
      return 0;
  }
}

static void
gst_wl_window_set_image_description (GstWlWindow * self,
    const GstVideoColorimetry * colorimetry,
    const GstVideoMasteringDisplayInfo * minfo,
    const GstVideoContentLightLevel * linfo)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  struct wl_display *wl_display;
  struct wp_color_manager_v1 *color_manager;
  struct wp_color_manager_v1 *color_manager_wrapper = NULL;
  struct wl_event_queue *color_manager_queue = NULL;
  struct wp_image_description_v1 *image_description = NULL;
  struct wp_image_description_creator_params_v1 *params;
  enum ImageDescriptionFeedback image_description_feedback =
      IMAGE_DESCRIPTION_FEEDBACK_UNKNOWN;
  uint32_t wl_transfer_function;
  uint32_t wl_primaries;

  if (!gst_wl_display_is_color_parametric_creator_supported (priv->display)) {
    GST_INFO_OBJECT (self,
        "Color management or parametric creator not supported");
    return;
  }

  color_manager = gst_wl_display_get_color_manager_v1 (priv->display);
  if (!priv->color_management_surface) {
    priv->color_management_surface =
        wp_color_manager_v1_get_surface (color_manager,
        priv->video_surface_wrapper);
  }

  wl_transfer_function = gst_colorimetry_tf_to_wl (colorimetry->transfer);
  wl_primaries = gst_colorimetry_primaries_to_wl (colorimetry->primaries);

  if (!gst_wl_display_is_color_transfer_function_supported (priv->display,
          wl_transfer_function) ||
      !gst_wl_display_are_color_primaries_supported (priv->display,
          wl_primaries)) {
    wp_color_management_surface_v1_unset_image_description
        (priv->color_management_surface);

    GST_INFO_OBJECT (self,
        "Can not create image description: primaries or transfer function not supported");
    return;
  }

  color_manager_wrapper = wl_proxy_create_wrapper (color_manager);
  wl_display = gst_wl_display_get_display (priv->display);
#ifdef HAVE_WL_EVENT_QUEUE_NAME
  color_manager_queue = wl_display_create_queue_with_name (wl_display,
      "GStreamer color manager queue");
#else
  color_manager_queue = wl_display_create_queue (wl_display);
#endif
  wl_proxy_set_queue ((struct wl_proxy *) color_manager_wrapper,
      color_manager_queue);

  params =
      wp_color_manager_v1_create_parametric_creator (color_manager_wrapper);

  wp_image_description_creator_params_v1_set_tf_named (params,
      wl_transfer_function);
  wp_image_description_creator_params_v1_set_primaries_named (params,
      wl_primaries);

  if (gst_wl_display_is_color_mastering_display_supported (priv->display)
      && minfo) {
    /* first validate our luminance range */
    guint min_luminance = minfo->min_display_mastering_luminance / 10000;
    guint max_luminance =
        MAX (min_luminance + 1, minfo->max_display_mastering_luminance / 10000);

    /* We need to convert from 0.00002 unit to 0.000001 */
    const guint f = 20;
    wp_image_description_creator_params_v1_set_mastering_display_primaries
        (params,
        minfo->display_primaries[0].x * f, minfo->display_primaries[0].y * f,
        minfo->display_primaries[1].x * f, minfo->display_primaries[1].y * f,
        minfo->display_primaries[2].x * f, minfo->display_primaries[2].y * f,
        minfo->white_point.x * f, minfo->white_point.y * f);
    wp_image_description_creator_params_v1_set_mastering_luminance (params,
        minfo->min_display_mastering_luminance, max_luminance);

    /*
     * FIXME its unclear what makes a color volume exceeds the primary volume,
     * and how to verify it, ignoring this aspect for now, but may need to be
     * revisited.
     */

    /* We can't set the light level if we don't know the luminance range */
    if (linfo) {
      guint maxFALL = CLAMP (linfo->max_frame_average_light_level,
          min_luminance + 1, max_luminance);
      guint maxCLL =
          CLAMP (linfo->max_content_light_level, maxFALL, max_luminance);
      wp_image_description_creator_params_v1_set_max_cll (params, maxCLL);
      wp_image_description_creator_params_v1_set_max_fall (params, maxFALL);
    }
  }

  image_description = wp_image_description_creator_params_v1_create (params);
  wp_image_description_v1_add_listener (image_description,
      &description_listerer, &image_description_feedback);

  while (image_description_feedback == IMAGE_DESCRIPTION_FEEDBACK_UNKNOWN) {
    if (wl_display_dispatch_queue (wl_display, color_manager_queue) == -1)
      break;
  }

  if (image_description_feedback == IMAGE_DESCRIPTION_FEEDBACK_READY) {
    wp_color_management_surface_v1_set_image_description
        (priv->color_management_surface, image_description,
        WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);

    GST_INFO_OBJECT (self, "Successfully set parametric image description");
  } else {
    wp_color_management_surface_v1_unset_image_description
        (priv->color_management_surface);

    GST_INFO_OBJECT (self, "Creating image description failed");
  }

  /* Setting the image description has copy semantics */
  wp_image_description_v1_destroy (image_description);
  wl_proxy_wrapper_destroy (color_manager_wrapper);
  wl_event_queue_destroy (color_manager_queue);
}

static void
gst_wl_window_set_color_representation (GstWlWindow * self,
    const GstVideoColorimetry * colorimetry)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);
  struct wp_color_representation_manager_v1 *cr_manager;
  uint32_t wl_alpha_mode;
  uint32_t wl_coefficients;
  uint32_t wl_range;
  gboolean alpha_mode_supported;
  gboolean coefficients_supported;

  cr_manager =
      gst_wl_display_get_color_representation_manager_v1 (priv->display);
  if (!cr_manager) {
    GST_INFO_OBJECT (self, "Color representation not supported");
    return;
  }

  wl_alpha_mode = WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_STRAIGHT;
  alpha_mode_supported =
      gst_wl_display_is_color_alpha_mode_supported (priv->display,
      wl_alpha_mode);

  wl_coefficients = gst_colorimetry_matrix_to_wl (colorimetry->matrix);
  wl_range = gst_colorimetry_range_to_wl (colorimetry->range);
  coefficients_supported =
      gst_wl_display_are_color_coefficients_supported (priv->display,
      wl_coefficients, wl_range);

  if (alpha_mode_supported || coefficients_supported) {
    if (!priv->color_representation_surface) {
      priv->color_representation_surface =
          wp_color_representation_manager_v1_get_surface (cr_manager,
          priv->video_surface_wrapper);
    }

    if (alpha_mode_supported)
      wp_color_representation_surface_v1_set_alpha_mode
          (priv->color_representation_surface, wl_alpha_mode);

    if (coefficients_supported)
      wp_color_representation_surface_v1_set_coefficients_and_range
          (priv->color_representation_surface, wl_coefficients, wl_range);

    GST_INFO_OBJECT (self, "Successfully set color representation");
  } else {
    if (priv->color_representation_surface) {
      wp_color_representation_surface_v1_destroy
          (priv->color_representation_surface);
      priv->color_representation_surface = NULL;
    }

    GST_INFO_OBJECT (self, "Coefficients and range not supported");
  }
}

static void
gst_wl_window_set_colorimetry (GstWlWindow * self,
    const GstVideoColorimetry * colorimetry,
    const GstVideoMasteringDisplayInfo * minfo,
    const GstVideoContentLightLevel * linfo)
{
  GST_OBJECT_LOCK (self);

  GST_INFO_OBJECT (self, "Trying to set colorimetry: %s",
      gst_video_colorimetry_to_string (colorimetry));

  gst_wl_window_set_image_description (self, colorimetry, minfo, linfo);
  gst_wl_window_set_color_representation (self, colorimetry);

  GST_OBJECT_UNLOCK (self);
}

void
gst_wl_window_set_force_aspect_ratio (GstWlWindow * self,
    gboolean force_aspect_ratio)
{
  GstWlWindowPrivate *priv = gst_wl_window_get_instance_private (self);

  priv->force_aspect_ratio = force_aspect_ratio;

  gst_wl_window_update_geometry (self);
}
