/*
 *  SOCX11_plug.c - limited "McVities" plug-in implementation for SoC V2.
 *
 *  The purpose of this file is to demonstrate the memory allocation and draw
 *  API introduced with SoC V2. First, a bit of history: V1 allocated it's
 *  LVB (internal frame buffer) and bitmap cache using shmget(), using unique
 *  keys to identify each allocation. This allowed partners to provide an SoC
 *  device specific shmget() which could allocate "special memory" e.g., 
 *  64-bit aligned physically contiguous memory.
 *  
 *  Moving forwards with V2, use of shmget() has been replaced by a formal
 *  "special memory" allocation interface. This means that V1 JPEG decoders 
 *  may not work with V2, unless this new interface is implemented (#).
 *
 *  In addition to memory allocation, this interface also exposes Receiver's
 *  X11 "draw to screen" command, allowing partners to replace this with a more
 *  optimal version (if available). "draw()" is called when Receiver wants a
 *  a frame to be shown on screen.
 *
 *  The code contained within demonstrates how this interface can be used
 *  for an X11 environment. Three functions have been implemented:
 *
 *      (1) special_allocate()
 *          Allocates "special" memory based on the input type (see mainloop.h
 *          for more details on memory types).
 *
 *      (2) special_free()
 *          Frees "special" memory allocated using special_allocate().
 *
 *      (3) draw()
 *          Pushes the internal frame buffer to screen.
 *
 *  X11's MIT-SHM extension is used to display the internal frame buffer
 *  (allocated using special_allocate()).  
 *
 *  It is strongly recommended that partners move to this new interface as the
 *  old shmget() technique is no longer supported by Receiver. Once implemented,
 *  the resulting shared library should be called "KVMEPlugin.so", as Receiver 
 *  will look for the following file on start-up:
 *
 *      $(ICAROOT)/KVMEPlugin.so
 *
 *  If this file exists, Receiver will attempt to load and extract the relevant
 *  entry points mentioned above. If successful, Receiver will then call the
 *  functions as and when required. Note that "KVME" stands for Keyboard, Video,
 *  Mouse and Events.
 *
 *  This example plug-in has been compiled and built as:
 *
 *      $(ICAROOT)/SOCX11plugin_compat.so
 *
 *  (#) The observant reader will note that this example implementation uses 
 *  shmget() (with V1 key values) to allocate memory on behalf of Receiver.
 *  It is therefore possible to "restore" V1 behaviour by copying/renaming this
 *  file to "KVMEPlugin.so" e.g.,
 *
 *      cp $(ICAROOT)/SOCX11plugin_compat.so $(ICAROOT)/KVMEPlugin.so
 *
 *  Any existing V1 device specific shmget() implementations will then operate 
 *  in the same way. Again, this is not the recommended approach but may be
 *  useful during test or development of the new interface.
 *  
 *  Author: Muhammad Dawood
 *  Copyright 2012-2014 Citrix Systems, Inc. All rights reserved.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include "citrix.h"

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

#define X11_SUPPORT
#include "mainloop.h"
#include "subwindow.h"

/* Forward declarations. */
static void special_allocate(struct special_buffer *description);
static void special_free(struct special_buffer *description);
static void draw(struct Draw_rect *dip, BOOLEAN flush);

/* Global variables. */
static Display *disp = NULL;
static Screen *scr = NULL;
static XImage *fb = NULL;
static XShmSegmentInfo shm_info;

/* SoC V1 shm key values, specified according to special_mem_type enum. */
static key_t special_mem_type_shm_key[] = 
{   
    0xD262BFA8,     /* (session_image) LVB. */
    0,              /* (image_output) Offscreen surfaces. */      
    0x1B91A2F0,     /* (image_cache) HBC. */
    0,              /* (image_input) Thinwire input cache. */
    0,              /* (overlay) */
    0               /* (small_bitmap) */
};

void Plugin_init(struct mainloop_plugin *plug)
{
    /* Set up our entry points and interface data. */
    plug->major_version = KVME_MAJOR_VERSION;
    plug->minor_version = KVME_MINOR_VERSION;
    plug->plugin_flags = PLUGIN_SUPPORTS_X11;

    plug->draw = &draw;
    plug->special_allocate = &special_allocate;
    plug->special_free = &special_free;

    /* Save the client's Display pointer. This should only be used for
     * "read-only" purposes and not be intefered with in any way.
     */
    disp = plug->disp;
    scr = DefaultScreenOfDisplay(disp);
}

static BOOLEAN init_ximage(struct special_buffer *desc)
{
    if (desc) {
        struct image_buffer_params *ibp = &desc->params.image_buffer;

        if (fb) {
            XDestroyImage(fb);
            fb = NULL;
        }

        /* Create the shared memory image structure. */

        fb = XShmCreateImage(disp, DefaultVisualOfScreen(scr),
                  DefaultDepthOfScreen(scr),
                  ZPixmap, NULL, &shm_info,
                  ibp->width, ibp->height);

        if (fb) {
            shm_info.shmseg = None;
            shm_info.readOnly = False;
            shm_info.shmid = ibp->shm_id;
            shm_info.shmaddr = desc->buf_addr;

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

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

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

                /* Sync before removal. */
                XSync(disp, False);

                shmctl(shm_info.shmid, IPC_RMID, 0);
                shm_info.shmid = 0;
                return TRUE;
            }
        }
    }

    return FALSE;
}

static void draw(struct Draw_rect *dip, BOOLEAN flush)
{
    static GC gc = None;

    if (dip) {
        if (gc == None) {
            /* Create a graphics context. */
            gc = XCreateGC(disp, dip->dest_win, 0, 0);
        }
        
        /* Show the internal frame buffer on screen. */
        XShmPutImage(disp, dip->dest_win, gc,
                  fb,
                  dip->x, dip->y, 
                  dip->target_x, dip->target_y,
                  dip->width, dip->height,
                  flush);

        if (flush) {
#ifdef VCTHREAD_SUPPORT
            /* Exported by the main process. */
            extern UINT32 SHM_FramesPushed;
            SHM_FramesPushed++;
#endif
            /* XSync() removed: WaitForShmCompletion() is always called after
             * the LVB is flushed (tw2root.c), and an XSync() is done then.
             */
            XFlush(disp);   /* Needs completion event. */
        }
    }
}

static void special_allocate(struct special_buffer *description)
{
    if (description) {
        key_t key = special_mem_type_shm_key[description->type];
        unsigned int size = 0;

        description->buf_addr = 0;

        if (    image_input == description->type 
            ||  image_cache == description->type) {
            /* Use the input size. The plug-in may wish to over-allocate for
             * performance reasons, where description->params.memory_buffer.size
             * should be set accordingly.
             */
            size = description->params.memory_buffer.size;
        } else {
            /* Work out allocation size. The plug-in may wish to over-allocate
             * for performance reasons, where description->params.image_buffer.width
             * should be set accordingly.
             */
            size = description->params.image_buffer.width *
                description->params.image_buffer.height *               
                description->params.image_buffer.pixel_size;            
            if (overlay == description->type &&
                (description->params.overlay_buffer.flags & MM_DOUBLE_BUFFER)){
                /* Double the size for double-buffering. */

                size <<= 1;
            }
        }

        if (key) {
            /* Attempt to allocate using shared memory. */
            int shm_id = shmget(key, size, IPC_CREAT | 0600);

            if (-1 != shm_id) {
                void *addr = shmat(shm_id, 0, 0);
                if (addr) {
                    /* Store the memory address. */
                    description->buf_addr = addr;

                    if (session_image == description->type) {
                        description->params.image_buffer.shm_id = shm_id;
                        /* Initialize the shared memory ximage. */
                        if (!init_ximage(description)) {
                            /* Failed to create and attach the ximage. Fail
                             * gracefully and fallback to malloc().
                             */
                            special_free(description);
                            /* description->buf_addr set to NULL. */
                        }
                    } else {
                        /* Store shm_id in private field for "image_cache". */
                        *((int *)(&description->priv)) = shm_id;
                    }
                }
               
                /* Mark for delete now. */
                shmctl(shm_id, IPC_RMID, 0);
            }
        }

        if (!description->buf_addr) {
            /* Fallback/non-shared memory case (malloc()). */
            description->buf_addr = malloc(size);
        }
    }
}

static void special_free(struct special_buffer *description)
{
    if (description) {
        if (description->buf_addr) {
            int shm_id = -1;

            switch (description->type) {
                case session_image:
                    shm_id = description->params.image_buffer.shm_id;
                    /* Destroy the ximage if there is one. */
                    if (fb) {
                        XShmDetach(disp, &shm_info);
                        XDestroyImage(fb);
                        fb = NULL;
                    }
                    break;

                case image_cache:
                    /* shm_id stored in private. */
                    shm_id = *((int *)(&description->priv));
                    break;

                default:
                    break;
            }

            if (-1 != shm_id) {
                /* Shared memory. */
                shmdt(description->buf_addr);
                shmctl(shm_id, IPC_RMID, 0);
            } else {
                /* Return memory to the heap. */
                free(description->buf_addr);
            }
            description->buf_addr = NULL;
        }
    }
}
