/*   
Copyright 2010 - 2011 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "linux_kvm.h"
#include "meshcore/meshdefines.h"
#include "microstack/ILibParsers.h"
#include "microstack/ILibAsyncSocket.h"
#include "microstack/ILibAsyncServerSocket.h"
#include "microstack/ILibProcessPipe.h"
#include <sys/wait.h>
#include <limits.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#include <X11/keysym.h>
#include <dlfcn.h>

#include "linux_events.h"
#include "linux_compression.h"

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

int SCREEN_NUM = 0;
int SCREEN_WIDTH = 0;
int SCREEN_HEIGHT = 0;
int SCREEN_DEPTH = 0;
int TILE_WIDTH = 0;
int TILE_HEIGHT = 0;
int TILE_WIDTH_COUNT = 0;
int TILE_HEIGHT_COUNT = 0;
int COMPRESSION_RATIO = 0;
int SCALING_FACTOR = 1024;		// Scaling factor, 1024 = 100%
int SCALING_FACTOR_NEW = 1024;	// Desired scaling factor, 1024 = 100%
int FRAME_RATE_TIMER = 0;
struct tileInfo_t **g_tileInfo = NULL;
pthread_t kvmthread = (pthread_t)NULL;
Display *eventdisplay = NULL;
int g_remotepause = 0;
int g_pause = 0;
int g_restartcount = 0;
int g_totalRestartCount = 0;
int g_shutdown = 0;
int change_display = 0;
unsigned short current_display = 0;
pid_t g_slavekvm = 0;
int master2slave[2];
int slave2master[2];
FILE *logFile = NULL;
int g_enableEvents = 0;

ILibQueue g_messageQ;

extern void* tilebuffer;
extern char **environ;

typedef struct x11ext_struct
{
	void *xext_lib;
	Bool(*XShmDetach)(Display *d, XShmSegmentInfo *si);
	Bool(*XShmGetImage)(Display *dis, Drawable d, XImage *image, int x, int y, unsigned long plane_mask);
	Bool(*XShmAttach)(Display *d, XShmSegmentInfo *si);
	XImage*(*XShmCreateImage)(Display *display, Visual *visual, unsigned int depth, int format, char *data, XShmSegmentInfo *shminfo, unsigned int width, unsigned int height);
}x11ext_struct;
x11ext_struct *x11ext_exports = NULL;
extern x11tst_struct *x11tst_exports;

typedef struct x11_struct
{
	void *x11_lib;
	Display*(*XOpenDisplay)(char *display_name);
	int(*XCloseDisplay)(Display *d);
	int(*XFlush)(Display *d);
	KeyCode(*XKeysymToKeycode)(Display *d, KeySym keysym);
	Bool(*XQueryExtension)(Display *d, char *name, int* maj, int *firstev, int *firsterr);
}x11_struct;
x11_struct *x11_exports = NULL;

void kvm_send_error(char *msg)
{
	int msgLen = strnlen_s(msg, 255);
	char *buffer = (char*)ILibMemory_SmartAllocate(msgLen + 4);

	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_ERROR);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)(msgLen + 4));	// Write the size
	memcpy_s(buffer + 4, msgLen, msg, msgLen);

	ILibQueue_Lock(g_messageQ);
	ILibQueue_EnQueue(g_messageQ, buffer);
	ILibQueue_UnLock(g_messageQ);
}

void kvm_send_resolution()
{
	char *buffer = (char*)ILibMemory_SmartAllocate(8);

	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_KVM_SCREEN);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)8);				// Write the size
	((unsigned short*)buffer)[2] = (unsigned short)htons((unsigned short)SCREEN_WIDTH);		// X position
	((unsigned short*)buffer)[3] = (unsigned short)htons((unsigned short)SCREEN_HEIGHT);	// Y position

	ILibQueue_Lock(g_messageQ);
	ILibQueue_EnQueue(g_messageQ, buffer);
	ILibQueue_UnLock(g_messageQ);
}

void kvm_send_display()
{
	char* buffer = (char*)ILibMemory_SmartAllocate(5);

	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_KVM_SET_DISPLAY);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)5);					// Write the size
	buffer[4] = current_display;																// Display number

	ILibQueue_Lock(g_messageQ);
	ILibQueue_EnQueue(g_messageQ, buffer);
	ILibQueue_UnLock(g_messageQ);
}

#define BUFSIZE 65535

int kvm_server_inputdata(char* block, int blocklen);
void* kvm_mainloopinput(void* parm)
{
	int ptr = 0;
	int ptr2 = 0;
	int len = 0;
	char* pchRequest2[30000];
	ssize_t cbBytesRead = 0;

	while (!g_shutdown)
	{
		//fprintf(logFile, "Reading from master in kvm_mainloopinput\n");
		cbBytesRead = read(master2slave[0], pchRequest2 + len, 30000 - len);
		//fprintf(logFile, "Read %d bytes from master in kvm_mainloopinput\n", cbBytesRead);
		if (cbBytesRead == -1 || cbBytesRead == 0 || g_shutdown) { /*ILIBMESSAGE("KVMBREAK-K1\r\n");*/ g_shutdown = 1; break; }
		len += cbBytesRead;
		ptr2 = 0;
		while ((ptr2 = kvm_server_inputdata((char*)pchRequest2 + ptr, cbBytesRead - ptr)) != 0) { ptr += ptr2; }
		if (ptr == len) { len = 0; ptr = 0; }
		// TODO: else move the reminder.
	}

	return 0;
}


int lockfileCheckFn(const struct dirent *ent) {
	if (ent == NULL) {
		return 0;
	}

	if (!strncmp(ent->d_name, ".X", 2) && strcmp(ent->d_name, ".X11-unix") && strcmp(ent->d_name, ".XIM-unix")) {
		return 1;
	}

	return 0;
}

void getAvailableDisplays(unsigned short **array, int *len) {
	DIR *dir = NULL;
	struct dirent **ent = NULL;
	int i;
	*array = NULL;
	*len = 0;

	dir = opendir("/tmp/");
	if (dir != NULL) {
		*len = scandir("/tmp/", &ent, lockfileCheckFn, alphasort);

		if ((*array = (unsigned short *)malloc((*len)*sizeof(unsigned short))) == NULL) ILIBCRITICALEXIT(254);

		for (i = 0; i < *len; i++) {
			int dispNo = 0;

			sscanf(ent[i]->d_name, ".X%d-lock", &dispNo);
			(*array)[i] = (unsigned short)dispNo;
		}
	}
}

int getNextDisplay() {
	DIR *dir = NULL;
	struct dirent **ent = NULL;
	int i, dispNo;

	dir = opendir("/tmp/");
	if (dir != NULL) {
		int numDisplays = scandir("/tmp/", &ent, lockfileCheckFn, alphasort);
		if (numDisplays == 0) { return -1; }

		for (i = 0; i < numDisplays; i++) {

			sscanf(ent[i]->d_name, ".X%d-lock", &dispNo);

			if (dispNo == (int)current_display) {
				break;
			}
		}

		if (i == numDisplays) {
			i = 0;
		}
		else {
			i = (i + 1) % numDisplays;
		}

		sscanf(ent[i]->d_name, ".X%d-lock", &dispNo);
		current_display = (unsigned short) dispNo;
		closedir(dir);
	}
	else {
		current_display = 0;
	}

	//fprintf(logFile, "getNextDisplay() => %d\n", current_display);
	return 0;
}

void kvm_send_display_list()
{
	unsigned short *displays = NULL;
	int len = 0;
	char* buffer;
	int totalSize = 0;
	int i;

	getAvailableDisplays(&displays, &len);
	totalSize = 2 /*Type*/ + 2 /*length of packet*/ + 2 /*length of data*/ + (len * 2) /*Data*/ + 2 /* Current display */;
	buffer = (char*)ILibMemory_SmartAllocate(totalSize);

	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_KVM_GET_DISPLAYS);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)totalSize);			// Write the size
	((unsigned short*)buffer)[2] = (unsigned short)htons((unsigned short)len);					// Length
	for (i = 0; i < len; i++) {
		((unsigned short*)buffer)[i + 3] = (unsigned short)htons(displays[i]);
	}
	((unsigned short*)buffer)[i + 3] = (unsigned short)htons((unsigned short)current_display);	// Current display

	ILibQueue_Lock(g_messageQ);
	ILibQueue_EnQueue(g_messageQ, buffer);
	ILibQueue_UnLock(g_messageQ);
}

char Location_X11LIB[NAME_MAX];
char Location_X11TST[NAME_MAX];
char Location_X11EXT[NAME_MAX];
void kvm_set_x11_locations(char *libx11, char *libx11tst, char *libx11ext)
{
	if (libx11 != NULL) { strcpy_s(Location_X11LIB, sizeof(Location_X11LIB), libx11); } else { strcpy_s(Location_X11LIB, sizeof(Location_X11LIB), "libX11.so"); }
	if (libx11tst != NULL) { strcpy_s(Location_X11TST, sizeof(Location_X11TST), libx11tst); } else { strcpy_s(Location_X11TST, sizeof(Location_X11TST), "libXtst.so"); }
	if (libx11ext != NULL) { strcpy_s(Location_X11EXT, sizeof(Location_X11EXT), libx11ext); } else { strcpy_s(Location_X11EXT, sizeof(Location_X11EXT), "libXext.so"); }
}

int kvm_init(int displayNo)
{
	//fprintf(logFile, "kvm_init called\n"); fflush(logFile);
	int old_height_count = TILE_HEIGHT_COUNT;
	int count = 0;
	int dummy1, dummy2, dummy3;
	char displayString[256] = "";

	if (x11ext_exports == NULL)
	{
		x11ext_exports = ILibMemory_SmartAllocate(sizeof(x11ext_struct));
		x11ext_exports->xext_lib = dlopen(Location_X11EXT, RTLD_NOW);
		if (x11ext_exports->xext_lib)
		{
			((void**)x11ext_exports)[1] = (void*)dlsym(x11ext_exports->xext_lib, "XShmDetach");
			((void**)x11ext_exports)[2] = (void*)dlsym(x11ext_exports->xext_lib, "XShmGetImage");
			((void**)x11ext_exports)[3] = (void*)dlsym(x11ext_exports->xext_lib, "XShmAttach");
			((void**)x11ext_exports)[4] = (void*)dlsym(x11ext_exports->xext_lib, "XShmCreateImage");
		}
	}
	if (x11tst_exports == NULL)
	{
		x11tst_exports = ILibMemory_SmartAllocate(sizeof(x11tst_struct));
		x11tst_exports->x11tst_lib = dlopen(Location_X11TST, RTLD_NOW);
		if (x11tst_exports->x11tst_lib)
		{
			((void**)x11tst_exports)[1] = (void*)dlsym(x11tst_exports->x11tst_lib, "XTestFakeMotionEvent");
			((void**)x11tst_exports)[2] = (void*)dlsym(x11tst_exports->x11tst_lib, "XTestFakeButtonEvent");
			((void**)x11tst_exports)[3] = (void*)dlsym(x11tst_exports->x11tst_lib, "XTestFakeKeyEvent");
		}
	}
	if (x11_exports == NULL)
	{
		x11_exports = ILibMemory_SmartAllocate(sizeof(x11_struct));
		x11_exports->x11_lib = dlopen(Location_X11LIB, RTLD_NOW);
		if (x11_exports->x11_lib)
		{
			((void**)x11_exports)[1] = (void*)dlsym(x11_exports->x11_lib, "XOpenDisplay");
			((void**)x11_exports)[2] = (void*)dlsym(x11_exports->x11_lib, "XCloseDisplay");
			((void**)x11_exports)[3] = (void*)dlsym(x11_exports->x11_lib, "XFlush");
			((void**)x11_exports)[4] = (void*)dlsym(x11_exports->x11_lib, "XKeysymToKeycode");
			((void**)x11_exports)[5] = (void*)dlsym(x11_exports->x11_lib, "XQueryExtension");

			((void**)x11tst_exports)[4] = (void*)x11_exports->XFlush;
			((void**)x11tst_exports)[5] = (void*)x11_exports->XKeysymToKeycode;
		}
	}

	sprintf(displayString, ":%d", (int)displayNo);

	if (count == 10) { return -1; }
	count = 0;
	eventdisplay = x11_exports->XOpenDisplay(displayString);
	fprintf(logFile, "XAUTHORITY is %s\n", getenv("XAUTHORITY")); fflush(logFile);
	fprintf(logFile, "DisplayString is %s\n", displayString); fflush(logFile);

	if (eventdisplay == NULL)
	{
		char tmpBuff[1024];
		sprintf_s(tmpBuff, sizeof(tmpBuff), "XOpenDisplay(%s) failed, using XAUTHORITY: %s", displayString, getenv("XAUTHORITY"));
		//fprintf(logFile, "DisplayString=%s\n", displayString);
		//fprintf(logFile, "XAUTHORITY is %s", getenv("XAUTHORITY")); fflush(logFile);
		//fprintf(logFile, "Error calling XOpenDisplay()\n"); fflush(logFile);
		kvm_send_error(tmpBuff);
	}

	if (eventdisplay != NULL) { current_display = (unsigned short)displayNo; }

	while (eventdisplay == NULL && count++ < 100) 
	{
		if (getNextDisplay() == -1) { return -1; }
		sprintf(displayString, ":%d", (int)current_display);
		eventdisplay = x11_exports->XOpenDisplay(displayString);
	}

	if (count == 100 && eventdisplay == NULL) { return -1; }

	g_enableEvents = x11_exports->XQueryExtension(eventdisplay, "XTEST", &dummy1, &dummy2, &dummy3)? 1 : 0;
	if (!g_enableEvents) { printf("FATAL::::Fake motion is not supported.\n\n\n"); }

	SCREEN_NUM = DefaultScreen(eventdisplay);
	SCREEN_HEIGHT = DisplayHeight(eventdisplay, SCREEN_NUM);
	SCREEN_WIDTH = DisplayWidth(eventdisplay, SCREEN_NUM);
	SCREEN_DEPTH = DefaultDepth(eventdisplay, SCREEN_NUM);

	if (SCREEN_DEPTH < 15) {
		// fprintf(stderr, "kvm_init: We do not support display depth < 15.");
		return -1;
	}

	// Some magic numbers.
	TILE_WIDTH = 32;
	TILE_HEIGHT = 32;
	COMPRESSION_RATIO = 50;
	FRAME_RATE_TIMER = 100;

	TILE_HEIGHT_COUNT = SCREEN_HEIGHT / TILE_HEIGHT;
	TILE_WIDTH_COUNT = SCREEN_WIDTH / TILE_WIDTH;
	if (SCREEN_WIDTH % TILE_WIDTH) { TILE_WIDTH_COUNT++; }
	if (SCREEN_HEIGHT % TILE_HEIGHT) { TILE_HEIGHT_COUNT++; }

	kvm_send_resolution();
	kvm_send_display();

	reset_tile_info(old_height_count);

	return 0;
}

void CheckDesktopSwitch(int checkres)
{
	if (change_display) {
		kvm_init(current_display);
		change_display = 0;
		return;
	}
}

int kvm_server_inputdata(char* block, int blocklen)
{
	unsigned short type, size;
	CheckDesktopSwitch(0);

	// Decode the block header
	if (blocklen < 4) return 0;
	type = ntohs(((unsigned short*)(block))[0]);
	size = ntohs(((unsigned short*)(block))[1]);
	if (size > blocklen) return 0;

	switch (type)
	{
	case MNG_KVM_KEY: // Key
		{
			if (size != 6) break;
			if (g_enableEvents) KeyAction(block[5], block[4], eventdisplay);
			break;
		}
	case MNG_KVM_MOUSE: // Mouse
		{
			int x, y;
			short w = 0;
			if (size == 10 || size == 12)
			{
				x = ((int)ntohs(((unsigned short*)(block))[3]));
				y = ((int)ntohs(((unsigned short*)(block))[4]));
				if (size == 12) w = ((short)ntohs(((short*)(block))[5]));
				// printf("x:%d, y:%d, b:%d, w:%d\n", x, y, block[5], w);
				if (g_enableEvents) MouseAction(x, y, (int)(unsigned char)(block[5]), w, eventdisplay);
			}
			break;
		}
	case MNG_KVM_COMPRESSION: // Compression
		{
			if (size >= 10) { int fr = ((int)ntohs(((unsigned short*)(block + 8))[0])); if (fr >= 20 && fr <= 5000) FRAME_RATE_TIMER = fr; }
			if (size >= 8) { int ns = ((int)ntohs(((unsigned short*)(block + 6))[0])); if (ns >= 64 && ns <= 4096) SCALING_FACTOR_NEW = ns; }
			if (size >= 6) { set_tile_compression((int)block[4], (int)block[5]); }
			COMPRESSION_RATIO = 100;
			break;
		}
	case MNG_KVM_REFRESH: // Refresh
		{
			kvm_send_resolution();

			int row, col;
			if (size != 4) break;
			if (g_tileInfo == NULL) {
				if ((g_tileInfo = (struct tileInfo_t **) malloc(TILE_HEIGHT_COUNT * sizeof(struct tileInfo_t *))) == NULL) ILIBCRITICALEXIT(254);
				for (row = 0; row < TILE_HEIGHT_COUNT; row++) {
					if ((g_tileInfo[row] = (struct tileInfo_t *) malloc(TILE_WIDTH_COUNT * sizeof(struct tileInfo_t))) == NULL) ILIBCRITICALEXIT(254);
				}
			}
			for (row = 0; row < TILE_HEIGHT_COUNT; row++) {
				for (col = 0; col < TILE_WIDTH_COUNT; col++) {
					g_tileInfo[row][col].crc = 0xFF;
					g_tileInfo[row][col].flag = 0;
				}
			}
			break;
		}
	case MNG_KVM_PAUSE: // Pause
		{
			if (size != 5) break;
			g_remotepause = block[4];
			break;
		}
	case MNG_KVM_FRAME_RATE_TIMER:
		{
			int fr = ((int)ntohs(((unsigned short*)(block))[2]));
			if (fr >= 20 && fr <= 5000) FRAME_RATE_TIMER = fr;
			break;
		}
	case MNG_KVM_GET_DISPLAYS:
		{
			kvm_send_display_list();
			break;
		}
	case MNG_KVM_SET_DISPLAY:
		{
			if (ntohs(((unsigned short*)(block))[2]) == current_display) { break; } // Don't do anything
			current_display = ntohs(((unsigned short*)(block))[2]);
			change_display = 1;
			break;
		}
	}
	return size;
}


int kvm_relay_feeddata(char* buf, int len)
{
	ssize_t written = 0;

	// Write the reply to the pipe.
	//fprintf(logFile, "Writing to slave in kvm_relay_feeddata\n");
	written = write(
		master2slave[1],			// handle to pipe
		buf,			// buffer to write from
		len);
	fsync(master2slave[1]);
	//fprintf(logFile, "Written %d bytes to slave in kvm_relay_feeddata\n", written);

	if (written == -1) return 0;
	if (len != (int)written) return written;
	return len;
}

// Set the KVM pause state
void kvm_pause(int pause)
{
	g_pause = pause;
}

void kvm_server_jpegerror(char *msg)
{
	int msgLen = strnlen_s(msg, 255);
	char *buffer = (char*)ILibMemory_SmartAllocate(msgLen + 4);

	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_ERROR);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)(msgLen + 4));	// Write the size
	memcpy_s(buffer + 4, msgLen, msg, msgLen);

	ILibQueue_Lock(g_messageQ);
	ILibQueue_EnQueue(g_messageQ, buffer);
	ILibQueue_UnLock(g_messageQ);
}

void* kvm_server_mainloop(void* parm)
{
	int x, y, height, width, r, c, count = 0;
	long long desktopsize = 0;
	long long tilesize = 0;
	//long long prev_timestamp = 0;
	//long long cur_timestamp = 0;
	//long long time_diff = 50;
	//struct timeb tp;
	void *desktop = NULL;
	XImage *image = NULL;
	eventdisplay = NULL;
	Display *imagedisplay = NULL;
	void *buf = NULL;
	char displayString[256] = "";
	int screen_height, screen_width, screen_depth, screen_num;
	ssize_t written;
	XShmSegmentInfo shminfo;
	default_JPEG_error_handler = kvm_server_jpegerror;


	fprintf(logFile, "Checking $DISPLAY\n");
	for (char **env = environ; *env; ++env)
	{
		int envLen = (int)strnlen_s(*env, INT_MAX);
		int i = ILibString_IndexOf(*env, envLen, "=", 1);
		if (i > 0)
		{
			if (i == 7 && strncmp("DISPLAY", *env, 7) == 0)
			{
				current_display = (unsigned short)atoi(*env + i + 2);
				fprintf(logFile, "ENV[DISPLAY] = %s\n", *env + i + 2);
				break;
			}
		}
	}

	g_messageQ = ILibQueue_Create();

	// Init the kvm
	//fprintf(logFile, "Before kvm_init.\n"); fflush(logFile);
	if (kvm_init(current_display) != 0) { return (void*)-1; }
	kvm_send_display_list();
	//fprintf(logFile, "After kvm_init.\n"); fflush(logFile);

	g_shutdown = 0;
	pthread_create(&kvmthread, NULL, kvm_mainloopinput, parm);
	//fprintf(logFile, "Created the kvmthread.\n"); fflush(logFile);

	while (!g_shutdown) 
	{
		// Check if there are pending messages to be sent
		ILibQueue_Lock(g_messageQ);
		while (ILibQueue_IsEmpty(g_messageQ) == 0)
		{
			if ((buf = (char*)ILibQueue_DeQueue(g_messageQ)) != NULL)
			{
				written = write(slave2master[1], buf, ILibMemory_Size(buf));
				fsync(slave2master[1]);
				ILibMemory_Free(buf);
			}
		}
		ILibQueue_UnLock(g_messageQ);

		for (r = 0; r < TILE_HEIGHT_COUNT; r++) {
			for (c = 0; c < TILE_WIDTH_COUNT; c++) {
				g_tileInfo[r][c].flag = TILE_TODO;
			}
		}
		//fprintf(logFile, "Before CheckDesktopSwitch.\n"); fflush(logFile);
		CheckDesktopSwitch(1);
		//fprintf(logFile, "After CheckDesktopSwitch.\n"); fflush(logFile);

		sprintf(displayString, ":%d", (int)current_display);
		imagedisplay = x11_exports->XOpenDisplay(displayString);

		count = 0;

		if (imagedisplay == NULL && count++ < 100) {
			change_display = 1;
			if (getNextDisplay() == -1) { return (void*)-1; }
			//fprintf(logFile, "Before kvm_init1.\n"); fflush(logFile);
			kvm_init(current_display);
			//fprintf(logFile, "After kvm_init1.\n"); fflush(logFile);
			change_display = 0;
			if (image != NULL) { XDestroyImage(image); image = NULL; }
			continue;
		}

		if (count == 100 && imagedisplay == NULL) { g_shutdown = 1; break; }

		screen_num = DefaultScreen(imagedisplay);
		screen_height = DisplayHeight(imagedisplay, screen_num);
		screen_width = DisplayWidth(imagedisplay, screen_num);
		screen_depth = DefaultDepth(imagedisplay, screen_num);

		if (screen_depth <= 15) {
			//fprintf(logFile, "We do not support display depth %d < 15.\n", screen_depth); fflush(logFile);
			//fprintf(stderr, "We do not support display depth <= 15.");
			break;
		}

		if ((SCREEN_HEIGHT != screen_height || SCREEN_WIDTH != screen_width || SCREEN_DEPTH != screen_depth || SCREEN_NUM != screen_num)) 
		{
			kvm_init(current_display);
			if (image != NULL) { XDestroyImage(image); image = NULL; }
			continue;
		}


		image = x11ext_exports->XShmCreateImage(imagedisplay,
			DefaultVisual(imagedisplay, screen_num), // Use a correct visual. Omitted for brevity     
			screen_depth,
			ZPixmap, NULL, &shminfo, screen_width, screen_height);
		shminfo.shmid = shmget(IPC_PRIVATE,
			image->bytes_per_line * image->height,
			IPC_CREAT | 0777);
		shminfo.shmaddr = image->data = shmat(shminfo.shmid, 0, 0);
		shminfo.readOnly = False;
		x11ext_exports->XShmAttach(imagedisplay, &shminfo);
		
		x11ext_exports->XShmGetImage(imagedisplay,
			RootWindowOfScreen(DefaultScreenOfDisplay(imagedisplay)),
			image,
			0,
			0,
			AllPlanes);

		//image = XGetImage(imagedisplay,
		//		RootWindowOfScreen(DefaultScreenOfDisplay(imagedisplay))
		//		, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, AllPlanes, ZPixmap);


		if (image == NULL) {
			g_shutdown = 1;
		}
		else {
			getScreenBuffer((char **)&desktop, &desktopsize, image);
			for (y = 0; y < TILE_HEIGHT_COUNT; y++) {
				for (x = 0; x < TILE_WIDTH_COUNT; x++) {
					height = TILE_HEIGHT * y;
					width = TILE_WIDTH * x;

					if (g_shutdown) { x = TILE_WIDTH_COUNT; y = TILE_HEIGHT_COUNT; break; }

					if (g_tileInfo[y][x].flag == TILE_SENT || g_tileInfo[y][x].flag == TILE_DONT_SEND) {
						continue;
					}

					getTileAt(width, height, &buf, &tilesize, desktop, desktopsize, y, x);

					if (buf && !g_shutdown)
					{
						// Write the reply to the pipe.
						//fprintf(logFile, "Writing to master in kvm_server_mainloop\n");
						written = write(slave2master[1], buf, tilesize);
						fsync(slave2master[1]);
						//fprintf(logFile, "Wrote %d bytes to master in kvm_server_mainloop\n", written);
						free(buf);
						if (written == -1) { /*ILIBMESSAGE("KVMBREAK-K2\r\n");*/ g_shutdown = 1; height = SCREEN_HEIGHT; width = SCREEN_WIDTH; break; }
					}
				}
			}
		}
		
		x11ext_exports->XShmDetach(imagedisplay, &shminfo);
		XDestroyImage(image); image = NULL;
		shmdt(shminfo.shmaddr);
		shmctl(shminfo.shmid, IPC_RMID, 0);
		
		if (imagedisplay != NULL) 
		{
			x11_exports->XCloseDisplay(imagedisplay);
			imagedisplay = NULL;
		}

		// We can't go full speed here, we need to slow this down.
		height = FRAME_RATE_TIMER;
		while (!g_shutdown && height > 0) { if (height > 50) { height -= 50; usleep(50000); } else { usleep(height * 1000); height = 0; } }
	}

	close(slave2master[1]);
	close(master2slave[0]);
	slave2master[1] = 0;
	master2slave[0] = 0;

	x11_exports->XCloseDisplay(eventdisplay);
	eventdisplay  = NULL;
	pthread_join(kvmthread, NULL);
	kvmthread = (pthread_t)NULL;
	if (g_tileInfo != NULL)
	{
		for (r = 0; r < TILE_HEIGHT_COUNT; r++) { free(g_tileInfo[r]); }
		free(g_tileInfo);
		g_tileInfo = NULL;
	}
	if(tilebuffer != NULL) { free(tilebuffer); tilebuffer = NULL; }
	ILibQueue_Destroy(g_messageQ);
	return (void*)0;
}

void kvm_relay_readSink(ILibProcessPipe_Pipe sender, char *buffer, int bufferLen, int* bytesConsumed)
{
	ILibKVM_WriteHandler writeHandler = (ILibKVM_WriteHandler)((void**)ILibMemory_Extra(sender))[0];
	void *reserved = ((void**)ILibMemory_Extra(sender))[1];
	unsigned short size;

	if (bufferLen > 4)
	{
		size = ntohs(((unsigned short*)(buffer))[1]);
		if (size <= bufferLen)
		{
			//printf("KVM Data: %u bytes\n", size);
			*bytesConsumed = size;
			writeHandler(buffer, size, reserved);
			return;
		}
	}
	*bytesConsumed = 0;
}
void* kvm_relay_restart(int paused, void *processPipeMgr, ILibKVM_WriteHandler writeHandler, void *reserved, int uid, char* authToken, char *dispid)
{
	int r;
	int count = 0;
	ILibProcessPipe_Pipe slave_out;

	if (g_slavekvm != 0) 
	{
		kill(g_slavekvm, SIGKILL);
		waitpid(g_slavekvm, &r, 0);
		g_slavekvm = 0;
	}

	r = pipe(slave2master);
	r = pipe(master2slave);

	// Two Phase is ok here, because all our fork/vfork calls always happen on the same thread
	fcntl(slave2master[0], F_SETFD, FD_CLOEXEC);
	fcntl(slave2master[1], F_SETFD, FD_CLOEXEC);
	fcntl(master2slave[0], F_SETFD, FD_CLOEXEC);
	fcntl(master2slave[1], F_SETFD, FD_CLOEXEC);

	slave_out = ILibProcessPipe_Pipe_CreateFromExistingWithExtraMemory(processPipeMgr, slave2master[0], 2 * sizeof(void*));	
	((void**)ILibMemory_Extra(slave_out))[0] = writeHandler;
	((void**)ILibMemory_Extra(slave_out))[1] = reserved;

	UNREFERENCED_PARAMETER(r);
	do
	{
		g_slavekvm = fork();
		if (g_slavekvm == -1 && paused == 0) sleep(2); // If we can't launch the child process, retry in a little while.
	}
	while (g_slavekvm == -1 && paused == 0 && ++count < 10);
	if (g_slavekvm == -1) return(NULL);

	if (g_slavekvm == 0) //slave
	{
		close(slave2master[0]);
		close(master2slave[1]);

		logFile = fopen("/tmp/slave", "w");
		if (uid != 0) { ignore_result(setuid(uid)); }

		//fprintf(logFile, "Starting kvm_server_mainloop\n");
		if (authToken != NULL) { setenv("XAUTHORITY", authToken, 1); }
		if (dispid != NULL) { setenv("DISPLAY", dispid, 1); }

		kvm_server_mainloop((void*)0);
		return(NULL);
	}
	else { //master
		close(slave2master[1]);
		close(master2slave[0]);
		logFile = fopen("/tmp/master", "w");

		// We will asyncronously read from the pipe, so we can just return
		ILibProcessPipe_Pipe_AddPipeReadHandler(slave_out, 65535, kvm_relay_readSink);
		return(slave_out);
	}
}


// Setup the KVM session. Return 1 if ok, 0 if it could not be setup.
void* kvm_relay_setup(void *processPipeMgr, ILibKVM_WriteHandler writeHandler, void *reserved, int uid, char *authToken, char *dispid)
{
	if (kvmthread != (pthread_t)NULL || g_slavekvm != 0) return 0;
	g_restartcount = 0;
	return kvm_relay_restart(1, processPipeMgr, writeHandler, reserved, uid, authToken, dispid);
}

// Force a KVM reset & refresh
void kvm_relay_reset()
{
	char buffer[4];
	((unsigned short*)buffer)[0] = (unsigned short)htons((unsigned short)MNG_KVM_REFRESH);	// Write the type
	((unsigned short*)buffer)[1] = (unsigned short)htons((unsigned short)4);				// Write the size
	kvm_relay_feeddata(buffer, 4);
}

// Clean up the KVM session.
void kvm_cleanup()
{
	int code;
	g_shutdown = 1;

	if (master2slave[1] != 0 && g_slavekvm != 0) 
	{ 
		kill(g_slavekvm, SIGKILL); 
		waitpid(g_slavekvm, &code, 0);
		g_slavekvm = 0; 
	}
	g_totalRestartCount = 0;
}
