
/* Handle clipboard text and data in arbitrary formats */

#include <stdio.h>
#include <limits.h>

#ifdef WIN32
#include <SDL.h>
#include <SDL_syswm.h>
#else
#include <SDL/SDL.h>
#include <SDL/SDL_syswm.h>
#endif
#include "scrap.h"

/* Miscellaneous defines */
#define PUBLIC
#define PRIVATE	static

/* Determine what type of clipboard we are using */
#if defined(__unix__) && !defined(__QNXNTO__)
    #define X11_SCRAP
#elif defined(__WIN32__)
    #define WIN_SCRAP
#elif defined(__QNXNTO__)
    #define QNX_SCRAP
#else
    #error Unknown window manager for clipboard handling
#endif /* scrap type */

/* System dependent data types */
#if defined(X11_SCRAP)
/* * */
typedef Atom scrap_type;

#elif defined(WIN_SCRAP)
/* * */
typedef UINT scrap_type;

#elif defined(QNX_SCRAP)
/* * */
typedef uint32_t scrap_type;
#define Ph_CL_TEXT T('T', 'E', 'X', 'T')

#endif /* scrap type */

/* System dependent variables */
#if defined(X11_SCRAP)
/* * */
static Display *SDL_Display;
static Window SDL_Window;
static void (*Lock_Display)(void);
static void (*Unlock_Display)(void);

#elif defined(WIN_SCRAP)
/* * */
static HWND SDL_Window;

#elif defined(QNX_SCRAP)
/* * */
static unsigned short InputGroup;

#endif /* scrap type */

#define FORMAT_PREFIX	"SDL_scrap_0x"

PRIVATE scrap_type
convert_format(int type)
{
  switch (type)
    {

    case T('T', 'E', 'X', 'T'):
#if defined(X11_SCRAP)
/* * */
      return XA_STRING;

#elif defined(WIN_SCRAP)
/* * */
      return CF_TEXT;

#elif defined(QNX_SCRAP)
/* * */
      return Ph_CL_TEXT;

#endif /* scrap type */

    default:
      {
        char format[sizeof(FORMAT_PREFIX)+8+1];

        sprintf(format, "%s%08lx", FORMAT_PREFIX, (unsigned long)type);

#if defined(X11_SCRAP)
/* * */
        return XInternAtom(SDL_Display, format, False);

#elif defined(WIN_SCRAP)
/* * */
        return RegisterClipboardFormat(format);

#endif /* scrap type */
      }
    }
}

/* Convert internal data to scrap format */
PRIVATE int
convert_data(int type, char *dst, char *src, int srclen)
{
  int dstlen;

  dstlen = 0;
  switch (type)
    {
    case T('T', 'E', 'X', 'T'):
      if ( dst )
        {
          while ( --srclen >= 0 )
            {
#if defined(__unix__)
              if ( *src == '\r' )
                {
                  *dst++ = '\n';
                  ++dstlen;
                }
              else
#elif defined(__WIN32__)
              if ( *src == '\r' )
                {
                  *dst++ = '\r';
                  ++dstlen;
                  *dst++ = '\n';
                  ++dstlen;
                }
              else
#endif
                {
                  *dst++ = *src;
                  ++dstlen;
                }
              ++src;
            }
            *dst = '\0';
            ++dstlen;
        }
      else
        {
          while ( --srclen >= 0 )
            {
#if defined(__unix__)
              if ( *src == '\r' )
                {
                  ++dstlen;
                }
              else
#elif defined(__WIN32__)
              if ( *src == '\r' )
                {
                  ++dstlen;
                  ++dstlen;
                }
              else
#endif
                {
                  ++dstlen;
                }
              ++src;
            }
            ++dstlen;
        }
      break;

    default:
      if ( dst )
        {
          *(int *)dst = srclen;
          dst += sizeof(int);
          memcpy(dst, src, srclen);
        }
      dstlen = sizeof(int)+srclen;
      break;
    }
    return(dstlen);
}

/* Convert scrap data to internal format */
PRIVATE int
convert_scrap(int type, char *dst, char *src, int srclen)
{
  int dstlen;

  dstlen = 0;
  switch (type)
    {
    case T('T', 'E', 'X', 'T'):
      {
        if ( srclen == 0 )
          srclen = strlen(src);
        if ( dst )
          {
            while ( --srclen >= 0 )
              {
#if defined(__WIN32__)
                if ( *src == '\r' )
                  /* drop extraneous '\r' */;
                else
#endif
                if ( *src == '\n' )
                  {
                    *dst++ = '\r';
                    ++dstlen;
                  }
                else
                  {
                    *dst++ = *src;
                    ++dstlen;
                  }
                ++src;
              }
              *dst = '\0';
              ++dstlen;
          }
        else
          {
            while ( --srclen >= 0 )
              {
#if defined(__WIN32__)
                if ( *src == '\r' )
                  /* drop extraneous '\r' */;
                else
#endif
                ++dstlen;
                ++src;
              }
              ++dstlen;
          }
        }
      break;

    default:
      dstlen = *(int *)src;
      if ( dst )
        {
          if ( srclen == 0 )
            memcpy(dst, src+sizeof(int), dstlen);
          else
            memcpy(dst, src+sizeof(int), srclen-sizeof(int));
        }
      break;
    }
  return dstlen;
}

#if defined(X11_SCRAP)
/* The system message filter function -- handle clipboard messages */
PRIVATE int clipboard_filter(const SDL_Event *event);
#endif

PUBLIC int
init_scrap(void)
{
  SDL_SysWMinfo info;
  int retval;

  /* Grab the window manager specific information */
  retval = -1;
  SDL_SetError("SDL is not running on known window manager");

  SDL_VERSION(&info.version);
  if ( SDL_GetWMInfo(&info) )
    {
      /* Save the information for later use */
#if defined(X11_SCRAP)
/* * */
      if ( info.subsystem == SDL_SYSWM_X11 )
        {
          SDL_Display = info.info.x11.display;
          SDL_Window = info.info.x11.window;
          Lock_Display = info.info.x11.lock_func;
          Unlock_Display = info.info.x11.unlock_func;

          /* Enable the special window hook events */
          SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
          SDL_SetEventFilter(clipboard_filter);

          retval = 0;
        }
      else
        {
          SDL_SetError("SDL is not running on X11");
        }

#elif defined(WIN_SCRAP)
/* * */
      SDL_Window = info.window;
      retval = 0;

#elif defined(QNX_SCRAP)
/* * */
      InputGroup=PhInputGroup(NULL);
      retval = 0;

#endif /* scrap type */
    }
  return(retval);
}

PUBLIC int
lost_scrap(void)
{
  int retval;

#if defined(X11_SCRAP)
/* * */
  Lock_Display();
  retval = ( XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window );
  Unlock_Display();

#elif defined(WIN_SCRAP)
/* * */
  retval = ( GetClipboardOwner() != SDL_Window );

#elif defined(QNX_SCRAP)
/* * */
  retval = ( PhInputGroup(NULL) != InputGroup );

#endif /* scrap type */

  return(retval);
}

PUBLIC void
put_scrap(int type, int srclen, char *src)
{
  scrap_type format;
  int dstlen;
  char *dst;

  format = convert_format(type);
  dstlen = convert_data(type, NULL, src, srclen);

#if defined(X11_SCRAP)
/* * */
  dst = (char *)malloc(dstlen);
  if ( dst != NULL )
    {
      Lock_Display();
      convert_data(type, dst, src, srclen);
      XChangeProperty(SDL_Display, DefaultRootWindow(SDL_Display),
        XA_CUT_BUFFER0, format, 8, PropModeReplace, dst, dstlen);
      free(dst);
      if ( lost_scrap() )
        XSetSelectionOwner(SDL_Display, XA_PRIMARY, SDL_Window, CurrentTime);
      Unlock_Display();
    }

#elif defined(WIN_SCRAP)
/* * */
  if ( OpenClipboard(SDL_Window) )
    {
      HANDLE hMem;

      hMem = GlobalAlloc((GMEM_MOVEABLE|GMEM_DDESHARE), dstlen);
      if ( hMem != NULL )
        {
          dst = (char *)GlobalLock(hMem);
          convert_data(type, dst, src, srclen);
          GlobalUnlock(hMem);
          EmptyClipboard();
          SetClipboardData(format, hMem);
        }
      CloseClipboard();
    }

#elif defined(QNX_SCRAP)
/* * */
  #if (_NTO_VERSION < 620) /* before 6.2.0 releases */
  {
     PhClipHeader clheader={Ph_CLIPBOARD_TYPE_TEXT, 0, NULL};
     int* cldata;
     int status;

     dst = (char *)malloc(dstlen+4);
     if (dst != NULL)
     {
        cldata=(int*)dst;
        *cldata=type;
        convert_data(type, dst+4, src, srclen);
        clheader.data=dst;
        if (dstlen>65535)
        {
           clheader.length=65535; /* maximum photon clipboard size :( */
        }
        else
        {
           clheader.length=dstlen+4;
        }
        status=PhClipboardCopy(InputGroup, 1, &clheader);
        if (status==-1)
        {
           fprintf(stderr, "Photon: copy to clipboard was failed !\n");
        }
        free(dst);
     }
  }
  #else /* 6.2.0 and 6.2.1 and future releases */
  {
     PhClipboardHdr clheader={Ph_CLIPBOARD_TYPE_TEXT, 0, NULL};
     int* cldata;
     int status;

     dst = (char *)malloc(dstlen+4);
     if (dst != NULL)
     {
        cldata=(int*)dst;
        *cldata=type;
        convert_data(type, dst+4, src, srclen);
        clheader.data=dst;
        clheader.length=dstlen+4;
        status=PhClipboardWrite(InputGroup, 1, &clheader);
        if (status==-1)
        {
           fprintf(stderr, "Photon: copy to clipboard was failed !\n");
        }
        free(dst);
     }
  }
  #endif
#endif /* scrap type */
}

PUBLIC void
get_scrap(int type, int *dstlen, char **dst)
{
  scrap_type format;

  *dstlen = 0;
  format = convert_format(type);

#if defined(X11_SCRAP)
/* * */
  {
    Window owner;
    Atom selection;
    Atom seln_type;
    int seln_format;
    unsigned long nbytes;
    unsigned long overflow;
    char *src;

    Lock_Display();
    owner = XGetSelectionOwner(SDL_Display, XA_PRIMARY);
    Unlock_Display();
    if ( (owner == None) || (owner == SDL_Window) )
      {
        owner = DefaultRootWindow(SDL_Display);
        selection = XA_CUT_BUFFER0;
      }
    else
      {
        int selection_response = 0;
        SDL_Event event;

        owner = SDL_Window;
        Lock_Display();
        selection = XInternAtom(SDL_Display, "SDL_SELECTION", False);
        XConvertSelection(SDL_Display, XA_PRIMARY, format,
                                        selection, owner, CurrentTime);
        Unlock_Display();
        while ( ! selection_response )
          {
            SDL_WaitEvent(&event);
            if ( event.type == SDL_SYSWMEVENT )
              {
                XEvent xevent = event.syswm.msg->event.xevent;

                if ( (xevent.type == SelectionNotify) &&
                     (xevent.xselection.requestor == owner) )
                    selection_response = 1;
              }
          }
      }
    Lock_Display();
    if ( XGetWindowProperty(SDL_Display, owner, selection, 0, INT_MAX/4,
                            False, format, &seln_type, &seln_format,
                       &nbytes, &overflow, (unsigned char **)&src) == Success )
      {
        if ( seln_type == format )
          {
            *dstlen = convert_scrap(type, NULL, src, nbytes);
            *dst = (char *)realloc(*dst, *dstlen);
            if ( *dst == NULL )
              *dstlen = 0;
            else
              convert_scrap(type, *dst, src, nbytes);
          }
        XFree(src);
      }
    }
    Unlock_Display();

#elif defined(WIN_SCRAP)
/* * */
  if ( IsClipboardFormatAvailable(format) && OpenClipboard(SDL_Window) )
    {
      HANDLE hMem;
      char *src;

      hMem = GetClipboardData(format);
      if ( hMem != NULL )
        {
          src = (char *)GlobalLock(hMem);
          *dstlen = convert_scrap(type, NULL, src, 0);
          *dst = (char *)realloc(*dst, *dstlen);
          if ( *dst == NULL )
            *dstlen = 0;
          else
            convert_scrap(type, *dst, src, 0);
          GlobalUnlock(hMem);
        }
      CloseClipboard();
    }
#elif defined(QNX_SCRAP)
/* * */
  #if (_NTO_VERSION < 620) /* before 6.2.0 releases */
  {
     void* clhandle;
     PhClipHeader* clheader;
     int* cldata;

     clhandle=PhClipboardPasteStart(InputGroup);
     if (clhandle!=NULL)
     {
        clheader=PhClipboardPasteType(clhandle, Ph_CLIPBOARD_TYPE_TEXT);
        if (clheader!=NULL)
        {
           cldata=clheader->data;
           if ((clheader->length>4) && (*cldata==type))
           {
              *dstlen = convert_scrap(type, NULL, (char*)clheader->data+4, clheader->length-4);
              *dst = (char *)realloc(*dst, *dstlen);
              if (*dst == NULL)
              {
                 *dstlen = 0;
              }
              else
              {
                 convert_scrap(type, *dst, (char*)clheader->data+4, clheader->length-4);
              }
           }
        }
        PhClipboardPasteFinish(clhandle);
     }
  }
  #else /* 6.2.0 and 6.2.1 and future releases */
  {
     void* clhandle;
     PhClipboardHdr* clheader;
     int* cldata;

     clheader=PhClipboardRead(InputGroup, Ph_CLIPBOARD_TYPE_TEXT);
     if (clheader!=NULL)
     {
        cldata=clheader->data;
        if ((clheader->length>4) && (*cldata==type))
        {
           *dstlen = convert_scrap(type, NULL, (char*)clheader->data+4, clheader->length-4);
           *dst = (char *)realloc(*dst, *dstlen);
           if (*dst == NULL)
           {
              *dstlen = 0;
           }
           else
           {
              convert_scrap(type, *dst, (char*)clheader->data+4, clheader->length-4);
           }
        }
     }
  }
  #endif
#endif /* scrap type */
}

#if defined(X11_SCRAP)
PRIVATE int clipboard_filter(const SDL_Event *event)
{
  /* Post all non-window manager specific events */
  if ( event->type != SDL_SYSWMEVENT ) {
    return(1);
  }

  /* Handle window-manager specific clipboard events */
  switch (event->syswm.msg->event.xevent.type) {
    /* Copy the selection from XA_CUT_BUFFER0 to the requested property */
    case SelectionRequest: {
      XSelectionRequestEvent *req;
      XEvent sevent;
      int seln_format;
      unsigned long nbytes;
      unsigned long overflow;
      unsigned char *seln_data;

      req = &event->syswm.msg->event.xevent.xselectionrequest;
      sevent.xselection.type = SelectionNotify;
      sevent.xselection.display = req->display;
      sevent.xselection.selection = req->selection;
      sevent.xselection.target = None;
      sevent.xselection.property = None;
      sevent.xselection.requestor = req->requestor;
      sevent.xselection.time = req->time;
      if ( XGetWindowProperty(SDL_Display, DefaultRootWindow(SDL_Display),
                              XA_CUT_BUFFER0, 0, INT_MAX/4, False, req->target,
                              &sevent.xselection.target, &seln_format,
                              &nbytes, &overflow, &seln_data) == Success )
        {
          if ( sevent.xselection.target == req->target )
            {
              if ( sevent.xselection.target == XA_STRING )
                {
                  if ( seln_data[nbytes-1] == '\0' )
                    --nbytes;
                }
              XChangeProperty(SDL_Display, req->requestor, req->property,
                sevent.xselection.target, seln_format, PropModeReplace,
                                                      seln_data, nbytes);
              sevent.xselection.property = req->property;
            }
          XFree(seln_data);
        }
      XSendEvent(SDL_Display,req->requestor,False,0,&sevent);
      XSync(SDL_Display, False);
    }
    break;
  }

  /* Post the event for X11 clipboard reading above */
  return(1);
}
#endif /* X11_SCRAP */
