/* 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"
#include "rfb/rfbconfig.h"

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

/* System dependent data types */
#if defined(X11_SCRAP)
typedef Atom scrap_type;
static Atom XA_TARGETS, XA_TEXT, XA_COMPOUND_TEXT, XA_UTF8_STRING;
#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')
#else
typedef int scrap_type;
#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);
static Atom XA_UTF8_STRING;
#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"

static scrap_type convert_format(int type)
{
	switch (type) {
	case T('T', 'E', 'X', 'T'):
#if defined(X11_SCRAP)
		return XA_UTF8_STRING ? XA_UTF8_STRING : 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 */
static int convert_data(int type, char *dst, const 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 */
static 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__)
				/* drop extraneous '\r' */;
				if (*src != '\r')
#endif
					++dstlen;
				++src;
			}
			++dstlen;
		}
		break;
	}
	default:
		dstlen = *(int *)src;
		if (dst)
			memcpy(dst, src + sizeof(int),
				srclen ? srclen - sizeof(int) : dstlen);
		break;
	}
	return dstlen;
}

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);

			XA_TARGETS = XInternAtom(SDL_Display, "TARGETS", False);
			XA_TEXT = XInternAtom(SDL_Display, "TEXT", False);
			XA_COMPOUND_TEXT = XInternAtom(SDL_Display,
					"COMPOUND_TEXT", False);
			XA_UTF8_STRING = XInternAtom(SDL_Display,
					"UTF8_STRING", False);

			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);
}

int lost_scrap(void)
{
	int retval;

#if defined(X11_SCRAP)
	if (Lock_Display)
		Lock_Display();
	retval = (XGetSelectionOwner(SDL_Display, XA_PRIMARY) != SDL_Window);
	if (Unlock_Display)
		Unlock_Display();
#elif defined(WIN_SCRAP)
	retval = (GetClipboardOwner() != SDL_Window);
#elif defined(QNX_SCRAP)
	retval = (PhInputGroup(NULL) != InputGroup);
#endif /* scrap type */

	return(retval);
}

void put_scrap(int type, int srclen, const 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) {
		if (Lock_Display)
			Lock_Display();
		convert_data(type, dst, src, srclen);
		XChangeProperty(SDL_Display, DefaultRootWindow(SDL_Display),
				XA_CUT_BUFFER0, format, 8, PropModeReplace,
				(unsigned char *)dst, dstlen);
		free(dst);
		if (lost_scrap())
			XSetSelectionOwner(SDL_Display, XA_PRIMARY,
					SDL_Window, CurrentTime);
		if (Unlock_Display)
			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 */
#define PhClipboardHdr PhClipHeader
#endif
	{
		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;
#if (_NTO_VERSION < 620) /* before 6.2.0 releases */
			if (dstlen > 65535)
				/* maximum photon clipboard size :(*/
				clheader.length = 65535;
			else
#endif
				clheader.length = dstlen+4;
			status = PhClipboardCopy(InputGroup, 1, &clheader);
			if (status == -1)
				fprintf(stderr,
					"Photon: copy to clipboard failed!\n");
			free(dst);
		}
	}
#endif /* scrap type */
}

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;

		if (Lock_Display)
			Lock_Display();
		owner = XGetSelectionOwner(SDL_Display, XA_PRIMARY);
		if (Unlock_Display)
			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;
			if (Lock_Display)
				Lock_Display();
			selection = XInternAtom(SDL_Display, "SDL_SELECTION",
					False);
			XConvertSelection(SDL_Display, XA_PRIMARY, format,
					selection, owner, CurrentTime);
			if (Unlock_Display)
				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;
				}
			}
		}
		if (Lock_Display)
			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);
		}
	}
	if (Unlock_Display)
		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 */
}

int clipboard_filter(const SDL_Event *event)
{
#if defined(X11_SCRAP)
	/* 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;
		if (req->target == XA_TARGETS) {
			Atom supported[] = {
				XA_TEXT, XA_COMPOUND_TEXT, XA_UTF8_STRING,
				XA_TARGETS, XA_STRING
			};
			XEvent response;

			XChangeProperty(SDL_Display, req->requestor,
				req->property, req->target, 32, PropModeReplace,
				(unsigned char*)supported,
				sizeof(supported) / sizeof(supported[0]));
			response.xselection.property=None;
			response.xselection.type= SelectionNotify;
			response.xselection.display= req->display;
			response.xselection.requestor= req->requestor;
			response.xselection.selection=req->selection;
			response.xselection.target= req->target;
			response.xselection.time = req->time;
			XSendEvent (SDL_Display, req->requestor,0,0,&response);
			XFlush (SDL_Display);
			return 1;
		}

		sevent.xselection.type = SelectionNotify;
		sevent.xselection.display = req->display;
		sevent.xselection.selection = req->selection;
		sevent.xselection.target = None;
		sevent.xselection.property = req->property;
		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 &&
						nbytes > 0 &&
						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 */
#endif /* X11_SCRAP */
	return(1);
}