/*****************************************************************************
 *
 *   ctxh264_fb.cpp
 *
 *   H.264 v3 interface software fallback implementation.
 *
 *   $Id$
 *
 *   Copyright 2016-2017 Citrix Systems, Inc.  All Rights Reserved.
 *
 *****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <execinfo.h>
#include <unistd.h>
#include <time.h>
#include <dlfcn.h>
#include <algorithm>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <linux/limits.h>

#include "citrix.h"
#include "UI_if.h"
#include "mainloop.h"

#ifndef PLUGIN_SDK
#include "hostfeat.h"
#endif

#include "decoder.cpp"

#include "ctxh264.h"

#ifdef X11_SUPPORT
#include <X11/X.h>

static Display  *disp = NULL;
static Screen   *scr = NULL;
static bool     g_bUsingXShmExtension = false;
#endif /* X11_SUPPORT */

/* Forward declarations. */
static bool v3_init();
static H264_context v3_open_context(int width, int height, void *codec_data,
                             int len, unsigned int options);
static bool v3_start_frame(H264_context Ctx, unsigned int encoded_size,
                    SIGNED_RECT dirty_rects[], unsigned int num_rects);
static bool v3_decode_frame(H264_context Ctx, void* H264_data, int len, bool last);
static bool v3_compose_with_fb(H264_context Ctx, ImgBuf *fb,
                        SIGNED_RECT interesting_rects[],
                        unsigned int num_rects);
static bool v3_compose_with_rects(H264_context Ctx, ImgBuf text_rects[],
                           unsigned int num_rects, bool last);
static bool v3_push_frame(H264_context cxt, struct window_info windows[],
                   unsigned int num_windows, bool wait, bool *pushed);
static void v3_close_context(H264_context Ctx);
static void v3_end ();

static CANV_context create_canvas(int width, int height, LLPixelFormat pix_format, int x_off, int y_off);
static H264_context create_h264_context(CANV_context cxt, SIGNED_RECT dest, ChromaFormat fmt);
static bool copy_image(CANV_context cxt, SIGNED_RECT dest,  struct image_buf *source);
static bool copy_rect(CANV_context dest_cxt, SIGNED_RECT dest, CANV_context src_cxt, SIGNED_RECT source);
static bool fill_rect(CANV_context cxt, SIGNED_RECT rect, unsigned int rgb);
static bool push_canvas(CANV_context cxt, struct window_info windows[], unsigned int num_windows);
static void destroy_canvas(CANV_context cxt);

typedef std::map<H264_context, V3FBCtxData*> GstContextMap;
typedef GstContextMap::iterator GstContextMapIter;

static GstContextMap g_GstCtxMap;
static unsigned int g_GstDecodeCtxListIdx = H264_INVALID_CONTEXT + 1;

static std::map<CANV_context, V2_CANVAS_STRUCT> canvasses;
static CANV_context next_canvas_context = CANV_INVALID_CONTEXT + 1;

struct H264_decoder H264_decoder =
{
    CTXH264_VERSION_MAJOR,
    CTXH264_VERSION_MINOR,
    32,     /* Max contexts. */
    4096,   /* Max width. */
    2048,   /* Max height. */
    60,
    H264_OPTION_LOSSLESS | H264_OPTION_PREFER_TEXT_RECTS |
    H264_OPTION_WINDOW_SUPPORT |     /* Seamless */
    H264_OPTION_SMALL_FRAME_SUPPORT,
    H264_CHROMA_FORMAT_444,
    0,                 /* Preferred alpha value for lossless objects. */
    PIXEL_FORMAT_ARGB, /* Preferred pixel format for lossless objects. */
    &v3_init,
    &v3_open_context,
    &v3_start_frame,
    &v3_decode_frame,
    &v3_compose_with_fb,
    &v3_compose_with_rects,
    &v3_push_frame,
    &v3_close_context,
    &v3_end,
    /* V2 entry points. */
    0, /* Max canvasses, 0 = no limit. */
    &create_canvas,
    &create_h264_context,
    NULL,
    &copy_image,
    &copy_rect,
    &fill_rect,
    &push_canvas,
    &destroy_canvas,
    NULL, /* show_cursor() not implemented. */
    NULL, /* hide_cursor() not implemented. */
    NULL, /* mouse_changed() callback - populated by Receiver. */
    NULL, /* set_mouse_cursor() not implemented. */
    NULL, /* update_monitor_layout() callback - populated by Receiver. */
    NULL, /* get_monitor_info() not implemented. */
    NULL, /* open_context_ex() not implemented. */
};

static V3FBCtxData *GetCtxData(H264_context Ctx)
{
    V3FBCtxData *pCtxData;

    /* map.at () function throws if element is not present at the location
     * specified by the key.
     */

    pCtxData = g_GstCtxMap.at(Ctx);
    return pCtxData;
}

#ifdef X11_SUPPORT
/* This function would be called only once, to initialize the DLL. */
static bool X11_init(void)
{
    /* Check for at least 24-bit colour depth. This is done here as
     * returning "false" will result in Receiver falling back to
     * compatible (JPEG) mode.
     */

    /* Set up X11 display variables. */

    disp = GetICADisplay(); /* Exported by the main Receiver process. */

    scr = DefaultScreenOfDisplay(disp);

    if (DefaultDepth(disp, DefaultScreen(disp)) < 24)
    {
        /* This decoder can't deal with anything other than 24 or 32-bit
         * displays.
         */
        /* TODO: Down convert? */

        return false;
    }

    return true;
}

static XImage *setupXImage(XShmSegmentInfo *shm_info, int width, int height)
{
    static bool init = false;
    unsigned int size = 0;
    XImage *fb = NULL;

    if (!init)
    {
        /* Perform one time initialization post Thinwire init here, where we
         * know whether Receiver is configured to use SHM or not.
         */

        init = true;

        /* Query Receiver as to whether SHM is being used. */

        g_bUsingXShmExtension = Get_SHM_event_base() > 0;
    }

    if (g_bUsingXShmExtension)
    {
        /* Use MIT-SHM. */
        fb = XShmCreateImage(disp, DefaultVisualOfScreen(scr),
                           DefaultDepthOfScreen(scr), ZPixmap,
                           NULL, shm_info,
                           width, height);
    }
    else
    {
        /* Use XPutImage. */
        fb = XCreateImage(disp, DefaultVisualOfScreen(scr),
                          DefaultDepthOfScreen(scr), ZPixmap, 0, NULL,
                          width, height, 32, 0);
    }

    if (fb)
    {
        size = fb->bytes_per_line * height;

        if (g_bUsingXShmExtension)
        {
            /* Allocated shared memory. */
            shm_info->shmseg = None;
            shm_info->readOnly = False;
            shm_info->shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);

            if (shm_info->shmid == -1)
            {
                goto cleanup;
            }

            shm_info->shmaddr = (char *)shmat(shm_info->shmid, 0, 0);

            if (shm_info->shmaddr == (void *)-1)
            {
                goto cleanup;
            }

            /* Tell the X server to attach the segment. */

            if (XShmAttach(disp, shm_info) > 0)
            {
                fb->data = (char *)shm_info->shmaddr;

                /* Mark the shared memory for removal now, so that it does
                 * not remain if this program dies unexpectedly.
                 */

                shmctl(shm_info->shmid, IPC_RMID, 0);
            }
        }
        else
        {
            /* Use traditional memory for XPutImage(). This memory is freed
             * by XDestroyImage();
             */

            fb->data = (char *)malloc(size);
        }
    }

    return fb;

cleanup:
    if (fb)
    {
        XDestroyImage(fb);
    }

    return NULL;
}

static bool intersects(SIGNED_RECT *r1, SIGNED_RECT *r2, SIGNED_RECT *r3)
{
    r3->top = max(r1->top, r2->top);
    r3->bottom = min(r1->bottom, r2->bottom);
    if (r3->top >= r3->bottom)
    {
        return false;
    }

    r3->left = max(r1->left, r2->left);
    r3->right = min(r1->right, r2->right);
    if (r3->left >= r3->right)
    {
        return false;
    }
    return true;
}

static void inflate_rect(SIGNED_RECT *rect, int amount)
{
    if (rect)
    {
        rect->left -= amount;
        rect->top -= amount;
        rect->right += amount;
        rect->bottom += amount;
    }
}

static void pushXImage(XImage *fb, struct window_info windows[], unsigned int num_windows, SIGNED_RECT *dirty_rects, int num_rects, bool wait)
{
    /* Show composed frame buffer. We must work out, based on the dirty rects.
     * what portions of the window(s) require updating.
     */

    for (unsigned int i = 0; i < num_windows; i++)
    {
        Window X_window;
        SIGNED_RECT & window_rect = windows[i].rect;

        X_window = windows[i].id;

        /* Check if any of the dirty rects lie within this window. */

        for (int j = 0; j < num_rects; j++)
        {
            SIGNED_RECT dirty_rect, overlap;

            if (windows[i].flags & WINDOW_INFO_FLAG_REPAINT)
            {
                /* Repaint entire window. */
                dirty_rect = window_rect;
            }
            else
            {
                /* Paint only dirty rectangles. */
                dirty_rect = dirty_rects[j];

                /* Inflate the dirty rect to minimize draw artifacts. */
                inflate_rect(&dirty_rect, 2);
            }

            if (intersects(&window_rect, &dirty_rect, &overlap))
            {
                int width, height, src_x, src_y, dest_x, dest_y;

                if (g_bUsingXShmExtension)
                {
                    /* No special alignment required. */

                    width = overlap.right - overlap.left;
                    height = overlap.bottom - overlap.top;

                    src_x = overlap.left;
                    src_y = overlap.top;

                    dest_x = windows[i].target_x + overlap.left;
                    dest_y = windows[i].target_y + overlap.top;

                    XShmPutImage(disp, X_window, GetICAGC(), fb,
                                 src_x, src_y, dest_x, dest_y,
                                 width,  height,
                                 true);

                    /* Flush the rendering pipeline and
                     * wait for synchronization.
                     */

                    /* ALWAYS exported by the main process. */

                    SHM_FramesPushed++;

                    if (wait)
                    {
                        XFlush(disp);

                        /* Explicitly call WaitForShmCompletion() here. */

                        WaitForShmCompletion();
                    }
                }
                else
                {
                    /* XPutImage - no special alignment reqV3FBCtxData *pCtxDatauired. */

                    width = overlap.right - overlap.left;
                    height = overlap.bottom - overlap.top;
                    src_x = overlap.left;
                    src_y = overlap.top;

                    dest_x = windows[i].target_x + overlap.left;
                    dest_y = windows[i].target_y + overlap.top;

                    XPutImage(disp, X_window, GetICAGC(), fb,
                              src_x,  src_y, dest_x, dest_y,
                              width, height);

                    /* No need to wait for syncrhonization with XPutImage(). */
                }
            }
        }
    }
}
#endif /* X11_SUPPORT */

static void close_context(V3FBCtxData *pCtxData)
{
    H264_context cxt = pCtxData->CtxId;

    close_decoder_context(&pCtxData->dec);

    if (pCtxData->pEncodedFrame)
    {
        free(pCtxData->pEncodedFrame);
    }

    /* Destroy XImage if H.264 context isn't attached to
     * a canvas.
     */
    if (pCtxData->canvas == CANV_INVALID_CONTEXT)
    {
#ifdef X11_SUPPORT
        if (g_bUsingXShmExtension)
        {
            XShmDetach(disp, &pCtxData->shm_info);
            /* Free shared memory. */

            shmdt(pCtxData->shm_info.shmaddr);
            shmctl(pCtxData->shm_info.shmid, IPC_RMID, 0);
        }

        if (pCtxData->fb)
        {
            /* XDestroyImage also frees output buffer pCtxData->fb->data */

            XDestroyImage(pCtxData->fb);
        }
#endif /* X11_SUPPORT */
    }

#ifdef DUMP_OUTPUT
    if (pCtxData->fp)
    {
        fclose(pCtxData->fp);
    }
#endif
    delete pCtxData;

    g_GstCtxMap.erase(cxt);
}

static V3FBCtxData *open_context_common(int width, int height, void *codec_data,
                                 int len, unsigned int options, CANV_context canvas,
                                 int x_off, int y_off)
{
    H264_context CurCtx = g_GstDecodeCtxListIdx;
    V3FBCtxData *pCtxData;

    REFERENCE_VAR(codec_data);
    REFERENCE_VAR(len);
    REFERENCE_VAR(options);

    pCtxData = new V3FBCtxData;
    pCtxData->CtxId = CurCtx;
    pCtxData->uCtxWidth	= width;
    pCtxData->uCtxHeight = height;
    pCtxData->uEncMaxFrameSize = 0;
    pCtxData->uEncFrameCurSize = 0;
    pCtxData->uEncFrameSize = 0;
    pCtxData->pEncodedFrame = NULL;
    pCtxData->has_text = false;
    pCtxData->is_small_frame = false;


    /* Save context offset within canvas. This will be (0,0) if the H.264
     * context occupies the entire screen.
     */
    pCtxData->x_off = x_off;
    pCtxData->y_off = y_off;

#ifdef X11_SUPPORT
    if (canvas == CANV_INVALID_CONTEXT)
    {
        /* No canvas supplied. Setup a new XImage. */
        pCtxData->canvas = CANV_INVALID_CONTEXT;
        pCtxData->fb = setupXImage(&pCtxData->shm_info, width, height);
    }
    else
    {
        /* Use the already setup canvas XImage. */
        pCtxData->canvas = canvas;
        V2_CANVAS_STRUCT *cs = &canvasses[canvas];

        pCtxData->fb = cs->fb;
    }

    pCtxData->obuf = pCtxData->fb->data
                   + (pCtxData->y_off * pCtxData->fb->bytes_per_line)
                   + (pCtxData->x_off * 4);

    pCtxData->obuf_stride = pCtxData->fb->bytes_per_line;
#endif /* X11_SUPPORT */

    g_GstCtxMap[CurCtx] = pCtxData;
    g_GstDecodeCtxListIdx ++;

#ifdef DUMP_OUTPUT
    {
        char str[256];

        sprintf(str, "/tmp/data_%d.h264", CurCtx);
        pCtxData->fp = fopen(str, "w+");
        if (pCtxData->fp)
        {
            fwrite(&width, 1, sizeof(int), pCtxData->fp);
            fwrite(&height, 1, sizeof(int), pCtxData->fp);
            fflush(pCtxData->fp);
        }
    }
#endif
    return pCtxData;
}

static void free_lossless_object(struct image_buf &image)
{
    if (image.mem)
    {
#ifdef MAINLOOP
        struct special_buffer buf;

        if ((H264_decoder.options & H264_OPTION_PREFER_TEXT_RECTS) &&
            (Plug.special_allocate &&
             ((Plug.major_version > 1) ||
              (Plug.major_version == 1 && Plug.minor_version >= 2))))
        {
            /* Allocated using "special_allocate()". So free
             * using "special_free()". Only supported for plugins with
             * mainloop version >= 1.2.
             */

            buf.type = small_bitmap;
            buf.buf_addr = image.mem;

	    /* This is *very* rude, as any value for the "priv" member of
	     * struct special_buffer has been lost.  In fact mainloop.h says:
	     * "The identical structure is passed to special_free() to
	     *  release the memory." FIXME!
	     */

            Plug.special_free(&buf);
        } else
#endif
        {
            /* Allocated using traditional "malloc()". So
             * free using "free()".
             */

            free(image.mem);
        }
    }
}

static void blt_object(V3FBCtxData *pCtxData, struct image_buf &image)
{
    unsigned char *loc, *bits;

    loc = (unsigned char *)pCtxData->obuf +
                  (image.dst_y * pCtxData->obuf_stride) +
                  (image.dst_x * 4);
    bits = (unsigned char *)image.bits;

    /* Make sure we draw in the pixel format we asked for. */

    if (image.pixel_format == H264_decoder.pref_lossless_pixel_fmt)
    {
        /* memcpy() each scanline onto the frame buffer.
         * This should use optimised copier: FIXME.
         */

        for (unsigned int y = 0; y < image.height; y++)
        {
            memcpy(loc, bits, image.stride);
            loc += pCtxData->obuf_stride;
            bits += image.stride;
        }
    }
}

static void update_canvas_dirty_rect(CANV_context cxt, SIGNED_RECT *dest)
{
    /* TODO: replace this with a more intelligent dirty region tracker, for
     * example, the one that tracks dirty regions on the LVB.
     */

    V2_CANVAS_STRUCT *canvas = &canvasses[cxt];

    if (dest->left < canvas->dirty_rect.left)
    {
        canvas->dirty_rect.left = dest->left;
    }

    if (dest->top < canvas->dirty_rect.top)
    {
        canvas->dirty_rect.top = dest->top;
    }

    if (dest->right > canvas->dirty_rect.right)
    {
        canvas->dirty_rect.right = dest->right;
    }

    if (dest->bottom > canvas->dirty_rect.bottom)
    {
        canvas->dirty_rect.bottom = dest->bottom;
    }
}

/* This function would be called only once, to initialize the DLL. */

static bool v3_init()
{
#ifdef X11_SUPPORT
    if (!X11_init())
    {
        return false;
    }
#endif /* X11_SUPPORT */

    if (!init_decoder())
    {
        return false;
    }

    return true;
}

/* This function would be called only once, to de-initialize the DLL. */

static void v3_end(void)
{
    deinit_decoder();

    /* Close all contexts. */

    for (GstContextMap::iterator i = g_GstCtxMap.begin();
	 i != g_GstCtxMap.end();
	 i++)
    {
        V3FBCtxData *pCtxData = i->second;
        close_context(pCtxData);
    }
}

static void v3_close_context(H264_context Ctx)
{
    V3FBCtxData *pCtxData = GetCtxData(Ctx);

    close_context(pCtxData);
}

static H264_context local_open_context(int width, int height,
                                       void *codec_data, int len,
                                       unsigned int options,
                                       CANV_context canvas,
                                       int x_off, int y_off)
{
    V3FBCtxData *pCtxData;

    pCtxData = open_context_common(width,
                                   height,
                                   codec_data,
                                   len,
                                   options,
                                   canvas,
                                   x_off,
                                   y_off);

    if (pCtxData)
    {
        if (open_decoder_context(&pCtxData->dec, (unsigned char *)pCtxData->obuf,
                                 pCtxData->obuf_stride, width, height))
        {
            return pCtxData->CtxId;
        }
    }

    return H264_INVALID_CONTEXT;
}

static H264_context open_context_for_canvas(int width, int height,
                                     void *codec_data, int len,
                                     unsigned int options,
                                     CANV_context canvas,
                                     int x_off, int y_off)
{
    return local_open_context(width,
                              height,
                              codec_data,
                              len,
                              options,
                              canvas,
                              x_off, y_off);
}

static H264_context v3_open_context(int width, int height,
                          void *codec_data, int len,
                          unsigned int options)
{
    return local_open_context(width,
                              height,
                              codec_data,
                              len,
                              options,
                              CANV_INVALID_CONTEXT,
                              0, 0);
}

static void get_bounds(V3FBCtxData *pCtxData,
                SIGNED_RECT *dirty_rects, unsigned int num_rects)
{
    /* Compute a single bounding rectangle for YUV->RGB conversion. */

    if (num_rects > 0)
    {
        pCtxData->yuv_bounds.left = pCtxData->uCtxWidth;
        pCtxData->yuv_bounds.right = 0;
        pCtxData->yuv_bounds.top = pCtxData->uCtxHeight;
        pCtxData->yuv_bounds.bottom = 0;

        for (unsigned int i = 0; i < num_rects; i++)
        {
            pCtxData->dirty_rects[i] = dirty_rects[i];
            pCtxData->yuv_bounds.left = min(pCtxData->yuv_bounds.left,
                                      dirty_rects[i].left);
            pCtxData->yuv_bounds.right = max(pCtxData->yuv_bounds.right,
                                      dirty_rects[i].right);
            pCtxData->yuv_bounds.top = min(pCtxData->yuv_bounds.top,
                                      dirty_rects[i].top);
            pCtxData->yuv_bounds.bottom = max(pCtxData->yuv_bounds.bottom,
                                      dirty_rects[i].bottom);
        }
        pCtxData->num_rects = num_rects;
    }
    else
    {
        /* No dirty rect means entire context needs updating. */

        pCtxData->dirty_rects[0].left = pCtxData->yuv_bounds.left = 0;
        pCtxData->dirty_rects[0].top = pCtxData->yuv_bounds.top = 0;
        pCtxData->dirty_rects[0].right = pCtxData->yuv_bounds.right =
            pCtxData->uCtxWidth;
        pCtxData->dirty_rects[0].bottom = pCtxData->yuv_bounds.bottom =
            pCtxData->uCtxHeight;
        pCtxData->num_rects = 1;
    }
}

static bool v3_start_frame(H264_context Ctx, unsigned int encoded_size,
                    SIGNED_RECT dirty_rects[], unsigned int num_rects)
{
    V3FBCtxData *pCtxData = GetCtxData(Ctx);

    if (pCtxData)
    {
        pCtxData->uEncFrameCurSize = 0;
        pCtxData->uEncFrameSize = encoded_size;

        /*  It is possible for Receiver to issue a start_frame() during the
         *  processing of an existing frame. Receiver will always set
         *  encoded_size to zero. This may happen as a result of an expose
         *  event. In this case, the plug-in should simply push the last
         *  composed frame.
         */

        if (encoded_size)
        {
            /* This frame contains H.264 data, so ensure we re-apply
             * any text rects and small frames.
             */

            pCtxData->has_text = true;

            if (pCtxData->uEncFrameSize > pCtxData->uEncMaxFrameSize)
            {
                pCtxData->uEncMaxFrameSize = pCtxData->uEncFrameSize;
                if (pCtxData->pEncodedFrame)
                {
                    free(pCtxData->pEncodedFrame);
                }

                pCtxData->pEncodedFrame =
                    (unsigned char *)malloc(pCtxData->uEncMaxFrameSize);
                if (!pCtxData->pEncodedFrame)
                {
                    return false;
                }
            }
        }

        get_bounds(pCtxData, dirty_rects, num_rects);

        return true;
    }

    return false;
}

static bool v3_compose_with_fb(H264_context Ctx, ImgBuf *ib,
                        SIGNED_RECT interesting_rects[],
                        unsigned int num_rects)
{
    REFERENCE_VAR(interesting_rects);
    V3FBCtxData *pCtxData;
    unsigned int x, y;

    if (num_rects == 0)
    {
        return true;
    }

    pCtxData = GetCtxData(Ctx);

    /* Very inefficient alpha blend. Assumes 32-bit LVB. */

    for (y = 0; y < ib->height; y++)
    {
        unsigned char *alpha = (unsigned char *)ib->bits + (y * ib->stride) + 3;

        for (x = 0; x < ib->width; x++)
        {
            if (H264_decoder.pref_lossless_alpha_val == *alpha)
            {
                /* Show this pixel. */

                unsigned char *loc = (unsigned char *)pCtxData->obuf +
                    (y * pCtxData->obuf_stride) + (x * 4);
                *((unsigned int *)loc) = *((unsigned int *)(alpha - 3));
            }
            alpha += 4;
        }
    }

    return true;
}

static bool v3_compose_with_rects(H264_context Ctx, ImgBuf rects[],
                           unsigned int num_rects, bool last)
{
    REFERENCE_VAR(last);
    V3FBCtxData* pCtxData;
    unsigned int i;

    if (num_rects == 0)
    {
        return true;
    }

    pCtxData = GetCtxData(Ctx);

    if (num_rects)
    {
        for (i = 0; i < num_rects; i++)
        {
            if (IMAGE_OP_DRAW_LOSSLESS == rects[i].lossless_op ||
                IMAGE_OP_SMALL_FRAME_BITMAP == rects[i].lossless_op)
            {
                struct image_buf &image = rects[i];

                /* Take a copy of the image and store internally. */

                if (IMAGE_OP_DRAW_LOSSLESS == image.lossless_op)
                {
                    /* Lossless text data. */

                    pCtxData->ltdata.push_back(image);
                    pCtxData->has_text = true;
                }
                else
                {
                    /* Small frame data, list to be purged on new H.264 frame. */

                    pCtxData->sfdata.push_back(image);
                    pCtxData->is_small_frame = true;
                }
            }
            else if (IMAGE_OP_DELETE_LOSSLESS == rects[i].lossless_op)
            {
                /* Find based on dest, width, height. This could be done better,
                 * using a hash table.
                 */

                for (std::vector<struct image_buf>::iterator it =
                         pCtxData->ltdata.begin();
                     it != pCtxData->ltdata.end();
                     it++)
                {
                    struct image_buf &image = *it;

                    if (   image.width == rects[i].width
                        && image.height == rects[i].height
                        && image.dst_x == rects[i].dst_x
                        && image.dst_y == rects[i].dst_y)
                    {
                        /* Free lossless object. */

                        free_lossless_object(image);
                        pCtxData->ltdata.erase(it);
                        break;
                    }
                }
            }
            else if (IMAGE_OP_SMALL_FRAME_SOLID_FILL == rects[i].lossless_op)
            {
                /* Small frame solid fill, list to be purged on new H.264 frame. */

                pCtxData->sfdata.push_back(rects[i]);
            }
        }
    }

    return true;
}

static bool v3_decode_frame(H264_context Ctx, void* H264_data, int len, bool last)
{
    V3FBCtxData *pCtxData = GetCtxData(Ctx);

    if (!pCtxData)
    {
        return false;
    }

    /* Incomplete frame, or dumping, copy to assembly buffer. */

    memcpy(&pCtxData->pEncodedFrame[pCtxData->uEncFrameCurSize],
           H264_data, len);

    pCtxData->uEncFrameCurSize += len;

    if (last)
    {
        /* Last chunk received. */
#ifdef DUMP_OUTPUT
        if (pCtxData->fp)
        {
            fwrite(&pCtxData->uEncFrameCurSize, 1, sizeof (unsigned int),
                   pCtxData->fp);
            fwrite(pCtxData->pEncodedFrame, 1, pCtxData->uEncFrameCurSize,
                   pCtxData->fp);
            fflush(pCtxData->fp);
        }
#endif
        if (decode_frame(&pCtxData->dec, (unsigned char *)pCtxData->pEncodedFrame, pCtxData->uEncFrameCurSize))
        {
            if (yuv2rgb(&pCtxData->dec, (unsigned char *)pCtxData->obuf, pCtxData->obuf_stride,
                        pCtxData->yuv_bounds, pCtxData->uCtxWidth, pCtxData->uCtxHeight))
            {
                if (pCtxData->canvas != CANV_INVALID_CONTEXT)
                {
                    /* Update the canvas. */
                    SIGNED_RECT rect;
                    rect.left = pCtxData->yuv_bounds.left + pCtxData->x_off;
                    rect.top = pCtxData->yuv_bounds.top + pCtxData->y_off;
                    rect.right = pCtxData->yuv_bounds.right + pCtxData->x_off;
                    rect.bottom = pCtxData->yuv_bounds.bottom + pCtxData->y_off;

                    update_canvas_dirty_rect(pCtxData->canvas, &rect);
                }
            }
        }

        pCtxData->uEncFrameCurSize = 0;
        pCtxData->uEncFrameSize = 0;
    }

    return true;
}

static bool v3_push_frame(H264_context Ctx, struct window_info windows[],
                   unsigned int num_windows, bool wait, bool *pushed)
{
    V3FBCtxData *pCtxData = GetCtxData(Ctx);

    if ((H264_decoder.options & (H264_OPTION_PREFER_TEXT_RECTS |
                                 H264_OPTION_LOSSLESS))
        == (H264_OPTION_PREFER_TEXT_RECTS | H264_OPTION_LOSSLESS))
    {
        /* Apply individual text rectangles if this is frame
         * contains H.264 data as the frame buffer will have been
         * overwritten with new H.264 data.
         *
         * Also, reapply text rectangles if we've received new text
         * rectangles for this frame.
         */

        if (pCtxData->has_text)
        {
            /* We have text objects to apply. */
            for (std::vector<struct image_buf>::iterator it =
                     pCtxData->ltdata.begin();
                 it != pCtxData->ltdata.end();
                 it++)
            {
                blt_object(pCtxData, *it);
            }

            pCtxData->has_text = false;
        }

        if (pCtxData->is_small_frame)
        {
            /* This is a small frame. */
            for (std::vector<struct image_buf>::iterator it =
                     pCtxData->sfdata.begin();
                 it != pCtxData->sfdata.end();
                 it++)
            {
                struct image_buf &image = *it;

                if (IMAGE_OP_SMALL_FRAME_BITMAP == image.lossless_op)
                {
                    /* Apply any small frame objects to the frame buffer. */

                    blt_object(pCtxData, image);
                    /* As we've copied the data onto the frame buffer, we
                     * can now free it.
                     */

                    free_lossless_object(image);
                }
                else if (IMAGE_OP_SMALL_FRAME_SOLID_FILL
                             == image.lossless_op)
                {
                    unsigned char *loc;

                    loc = (unsigned char *)pCtxData->obuf +
                              (image.dst_y * pCtxData->obuf_stride) +
                              (image.dst_x * 4);
                    for (unsigned int y = 0; y < image.height; y++)
                    {
                        unsigned int *start = (unsigned int *)loc;

                        for (unsigned int x = 0; x < image.width; x++)
                        {
                            *start++ = image.col;
                        }
                        loc += pCtxData->obuf_stride;
                    }
                }
            }
            /* Clear all small frame objects. */
            pCtxData->sfdata.clear();
            pCtxData->is_small_frame = false;
        }

    }

#ifdef X11_SUPPORT
    /* Show composed frame buffer. */
    pushXImage(pCtxData->fb,
               windows,
               num_windows,
               pCtxData->dirty_rects,
               pCtxData->num_rects,
               wait);
#endif /* X11_SUPPORT */

    if (pushed)
    {
        *pushed = true;
    }

    return true;
}

static void reset_dirty_rect(V2_CANVAS_STRUCT *canvas)
{
    canvas->dirty_rect.left = canvas->width;
    canvas->dirty_rect.top = canvas->height;
    canvas->dirty_rect.right = 0;
    canvas->dirty_rect.bottom = 0;
}

/* V2 partial H.264 support entry points. */

static CANV_context create_canvas(int width, int height, LLPixelFormat pix_format, int x_off, int y_off)
{
    REFERENCE_VAR(pix_format);

    CANV_context cxt = next_canvas_context++;

    V2_CANVAS_STRUCT *canvas = &canvasses[cxt];

    canvas->width = width;
    canvas->height = height;
    canvas->x_off = x_off;
    canvas->y_off = y_off;

    reset_dirty_rect(canvas);

    /* Set up the canvas' XImage. */
#ifdef X11_SUPPORT
    canvas->fb = setupXImage(&canvas->shm_info, width, height);

    if (!canvas->fb)
    {
        return CANV_INVALID_CONTEXT;
    }

    canvas->bits = (unsigned char *)canvas->fb->data;
    canvas->stride = canvas->fb->bytes_per_line;
#endif /* X11_SUPPORT */

    return cxt;
}

static H264_context create_h264_context(CANV_context cxt, SIGNED_RECT dest, ChromaFormat fmt)
{
    REFERENCE_VAR(fmt);

    H264_context h264_cxt;

    int width = dest.right - dest.left;
    int height = dest.bottom - dest.top;

    /* Create a H.264 context, supplying canvas ID. */
    h264_cxt = open_context_for_canvas(width,
                                       height,
                                       NULL,
                                       0,
                                       0,
                                       cxt,
                                       dest.left,
                                       dest.top);

    return h264_cxt;
}

static unsigned char *get_pointer_for_image(CANV_context cxt, SIGNED_RECT dest, int *stride)
{
    unsigned char *ptr = NULL;

    V2_CANVAS_STRUCT *canvas = &canvasses[cxt];

    if (canvas->bits)
    {
        ptr = (canvas->bits + (dest.top * canvas->stride) + (dest.left * 4));
        *stride = canvas->stride;
    }

    return ptr;
}

static bool copy_image(CANV_context cxt, SIGNED_RECT dest,  struct image_buf *source)
{
    bool ret = false;
    int stride;

    unsigned char *dest_ptr = get_pointer_for_image(cxt, dest, &stride);

    if (dest_ptr)
    {
        unsigned char *source_ptr = (unsigned char *)source->bits
                                    + (source->src_y * source->stride)
                                    + (source->src_x * 4);

        int bytes_to_copy = (dest.right - dest.left) * 4;

        for (int y = dest.top; y < dest.bottom; y++)
        {
            memcpy(dest_ptr, source_ptr, bytes_to_copy);
            dest_ptr += stride;
            source_ptr += source->stride;
        }

        update_canvas_dirty_rect(cxt, &dest);
        ret = true;
    }

    return ret;
}

static bool copy_rect(CANV_context dest_cxt, SIGNED_RECT dest, CANV_context src_cxt, SIGNED_RECT source)
{
    bool ret = false;
    int src_stride = 0;
    int dest_stride = 0;

    unsigned char *dest_ptr = get_pointer_for_image(dest_cxt, dest, &dest_stride);
    unsigned char *src_ptr = get_pointer_for_image(src_cxt, source, &src_stride);

    if (   (dest_ptr && dest_stride)
        && (src_ptr && src_stride)
        && (dest_ptr != src_ptr)
       )
    {
        int height = dest.bottom - dest.top;

        if ((dest_cxt == src_cxt) && (dest.top > source.top))
        {
            /* Copy bottom to top. */
            src_ptr += ((height - 1) * src_stride);
            dest_ptr += ((height - 1) * dest_stride);
            src_stride = -src_stride;
            dest_stride = -dest_stride;
        }

        int bytes_to_copy = (dest.right - dest.left) * 4;
        int dist = dest_ptr - src_ptr;

        if (dist < -bytes_to_copy || dist >= bytes_to_copy)
        {
            for (int i = 0; i < height; i++)
            {
                memcpy(dest_ptr, src_ptr, bytes_to_copy);
                dest_ptr += dest_stride;
                src_ptr += src_stride;
            }
        }
        else
        {
            for (int i = 0; i < height; i++)
            {
                memmove(dest_ptr, src_ptr, bytes_to_copy);
                dest_ptr += dest_stride;
                src_ptr += src_stride;
            }
        }

        update_canvas_dirty_rect(dest_cxt, &dest);
        ret = true;
    }

    return ret;
}

static bool fill_rect(CANV_context cxt, SIGNED_RECT rect, unsigned int rgb)
{
    bool ret = false;
    int stride;

    unsigned char *dest_ptr = get_pointer_for_image(cxt, rect, &stride);

    if (dest_ptr)
    {
        int height = rect.bottom - rect.top;

        /* Very simple solid fill, unoptimized. */
        for (int y = 0; y < height; y++)
        {
            /* Assumes 4 bytes per pixel. */
            unsigned int *line = (unsigned int *)(dest_ptr + (y * stride));

            for (int x = rect.left; x < rect.right; x++)
            {
                *line++ = rgb;
            }
        }

        update_canvas_dirty_rect(cxt, &rect);
        ret = true;
    }

    return ret;
}

static bool push_canvas(CANV_context cxt, struct window_info windows[], unsigned int num_windows)
{
    V2_CANVAS_STRUCT *canvas = &canvasses[cxt];

#ifdef X11_SUPPORT
    /* Push canvas to screen. */
    pushXImage(canvas->fb,
               windows,
               num_windows,
               &canvas->dirty_rect,
               1,
               true);
#endif /* X11_SUPPORT */

    reset_dirty_rect(canvas);

    return true;
}

static void destroy_canvas(CANV_context cxt)
{
    V2_CANVAS_STRUCT *canvas = &canvasses[cxt];

#ifdef X11_SUPPORT
    /* Destroy any associated H.264 contexts. */

    /* Destroy canvas XImage. */
    if (g_bUsingXShmExtension)
    {
        XShmDetach(disp, &canvas->shm_info);
        /* Free shared memory. */
        shmdt(canvas->shm_info.shmaddr);
        shmctl(canvas->shm_info.shmid, IPC_RMID, 0);
    }

    if (canvas->fb)
    {
        XDestroyImage(canvas->fb);
        canvas->fb = NULL;
    }
#endif /* X11_SUPPORT */
}

