//qcc -o ~/bin/vmclipshare vmclipshare.c -lph -lfont;if [ $? == 0 ];then usemsg ~/bin/vmclipshare vmclipshare.c;fi
//
// V1.4

#ifdef __USAGE
%C - Shares the text clipboard between host and QNX6 running under
vmware. Also implements cursor-movement-based ungrab/grab, and time sync to
within 10 seconds of host.

Disable/enable specific functions using the following VMware preferences
on the Input tab of the Edit -> Preferences dialog:
- "Enable copy and paste to and from virtual machine"
- "Ungrab when cursor leaves window"

Note: For extra clipboard reliability, you need to have the "vmtool" executable
      in $PATH. Otherwise setting the host clipboard may fail in some cases.
#endif

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <Ph.h>
#include <Pt.h>
#include "photon/PhSystem.h"

#define BUF_SIZE 1024

#define SHORT_INTERVAL 100
#define LONG_INTERVAL 1000
int timer_interval=SHORT_INTERVAL;

/*
 * The following info from,
 * 
 * chitchat.at.infoseek.co.jp/vmware/vmtools.html
 * Author: Ken Kato
 * chitchat-lj@infoseek.jp
 * 
 * You may freely use, modify, redistribute or trash them in whatever way
 * you like. Success or bug reports, suggestions, encouragement and
 * compliments are greatly appreciated though. Complaints, accusation and
 * abuse are not.
 */
#define VMWARE_MAGIC        0x564D5868      /* Backdoor magic number        */
#define VMWARE_PORT         0x5658          /* Backdoor port number         */

#define VMCMD_APM_FUNCTION  0x02            /* APM function?                */
#define VMCMD_GET_CURSOR    0x04            /* Get cursor position          */
#define VMCMD_SET_CURSOR    0x05            /* Set cursor position          */
#define VMCMD_GET_CLIPLEN   0x06            /* host->guest copy length      */
#define VMCMD_GET_CLIPDATA  0x07            /* host->guest copy data        */
#define VMCMD_SET_CLIPLEN   0x08            /* guest->host copy length      */
#define VMCMD_SET_CLIPDATA  0x09            /* guest->host copy data        */
#define VMCMD_GET_VERSION   0x0a            /* Get version number           */
#define VMCMD_GET_DEVICE    0x0b            /* Get device information       */
#define VMCMD_SET_DEVICE    0x0c            /* Set device state             */
#define VMCMD_GET_SETTING   0x0d            /* Get preference settings      */
#define VMCMD_SET_SETTING   0x0e            /* Set Preference settings      */
#define VMCMD_GET_HOSTRES   0x0f            /* Get host's screen size       */
#define VMCMD_POPUP_DIALOG  0x12            /* OS not found message popup   */
#define VMCMD_GET_VMCONFIG  0x14            /* Get VM config information    */
#define VMCMD_GET_TIME      0x17            /* Get host's time (GMT)        */
#define VMCMD_TRANSFER      0x1e            /* Generic data transfer        */

/*
 *  Preference bitmask (for VMCMD_GET_SETTING, VMCMD_SET_SETTING)
 */
#define VMPREF_GRAB_MOVE    0x0001          /* grab by cursor movement      */
#define VMPREF_UNGRAB_MOVE  0x0002          /* ungrab by cursor movement    */
#define VMPREF_SCROLL_MOVE  0x0004          /* scroll by cursor movement    */
#define VMPREF_COPY_PASTE   0x0010          /* copy and paste               */
#define VMPREF_FULLSCREEN   0x0040          /* full screen (read only)      */
#define VMPREF_ENTER_FULL   0x0080          /* full screen (write only)     */
#define VMPREF_SYNCRONIZE   0x0400          /* time syncronization          */

#define VMCLIP_MAX_LENGTH   65355           /* maximum copy & paste length  */

void vm_cmd(unsigned p[4]) {
#ifndef __QNXNTO__
	extern void _vm_in(void);
#pragma aux _vm_in = \
	"mov eax, p[0]" \
	"mov ebx, p[1]" \
	"mov ecx, p[2]" \
	"mov edx, p[3]" \
	"in eax, dx" \
	"mov p[0], eax" \
	"mov p[1], ebx" \
	"mov p[2], ecx" \
	"mov p[3], edx" \
	__modify [eax ebx ecx edx];
	_vm_in();
#else
        __asm__  ("in %%dx, %%eax"
        : "=a"(p[0]), "=b"(p[1]), "=c"(p[2]), "=d"(p[3])
        : "a"(p[0]), "b"(p[1]), "c"(p[2]), "d"(p[3])
        );
#endif
}

void 
segfault()
{
	printf("vmclipshare: Not running inside VMware.\n");
	exit(1);
}

unsigned int get_settings()
{
	unsigned int    regs[4];
	regs[0] = VMWARE_MAGIC;
	regs[1] = 0xc0de;	/* don't care */
	regs[2] = VMCMD_GET_SETTING;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	return regs[0];
}

unsigned int get_time()
{
	unsigned int    regs[4];
	regs[0] = VMWARE_MAGIC;
	regs[1] = 0xc0de;	/* don't care */
	regs[2] = VMCMD_GET_TIME;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	return regs[0];
}

int have_focus(unsigned int *reg0)
{
	int retc;
	unsigned int regs[4];

	regs[0] = VMWARE_MAGIC;
	regs[1] = 0xc0de;	/* don't care */
	regs[2] = VMCMD_GET_CURSOR;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	if ( (short)(regs[0]>>16) == -100 && (short)(regs[0]&0xFFFF) == -100 )
		retc = 0;
	else
		retc = 1;
	if (reg0)
		*reg0 = regs[0];
	return retc;
}

int set_cursor(unsigned short x, unsigned short y)
{
	unsigned int regs[4];

	regs[0] = VMWARE_MAGIC;
	regs[1] = (x<<16) | y;
	regs[2] = VMCMD_SET_CURSOR;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	return 0;
}

char *get_clipboard(int do_nonsense)
{
	int             i,bi=0,first=1;
	unsigned int    regs[4];
	int             length;
	unsigned char   *text;
	static char     buf[VMCLIP_MAX_LENGTH + 1];

	if (!(get_settings() & VMPREF_COPY_PASTE)) {
		printf("vmclipshare: clipboard is disabled.\n");
		return 0;
	}
	length = 0;
	regs[0] = VMWARE_MAGIC;
	regs[1] = 0xc0de;	/* don't care */
	regs[2] = VMCMD_GET_CLIPLEN;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	length = min((int)regs[0], VMCLIP_MAX_LENGTH);
	buf[0] = '\0';
	if (length > 0) {
		for (;;) {
			regs[0] = VMWARE_MAGIC;
			regs[1] = 0xc0de;	/* don't care */
			regs[2] = VMCMD_GET_CLIPDATA;
			regs[3] = VMWARE_PORT;
			vm_cmd(regs);
			text = (unsigned char *)&regs[0];
			if (length < 4) {
				text[length] = '\0';
			}
			for (i = 0; i < 4; i++) {
				if (text[i]) {
					if (do_nonsense) {
						if (1 || first) {
							putchar(text[i]);
							first = 0;
						}
					}
					buf[bi++] = text[i];
				} else {
					length = 0;
					break;
				}
			}
			if (length <= 4) {
				break;
			}
			length -= 4;
		}
		buf[bi] = '\0';
		return buf;
	} else 
		return 0;
}

int set_clipboard(char *text)
{
	int             i;
	unsigned int    regs[4];
	int             length = 0, maxlen = VMCLIP_MAX_LENGTH;

#if 1
	//
	// Need to do a get cliplen and get clipdata for setting to
	// work. No clue why. And on top of this, it needs to be done
	// by vmtool itself. Very strange but I'm not going to argue.
	// The case that needs this is if a large clipboard buffer bigger
	// than the max length is selected in Windows. I just load a big
	// C file like services/phrelay/worker.c into wordpad and select
	// all and ctrl-C. Trying to set the hosts clipboard does not work
	// after this unless we do a frickin' vmtool -g first. 
	//
	//get_clipboard(1);
	system("vmtool -g >/dev/null");
#endif
	if (!(get_settings() & VMPREF_COPY_PASTE)) {
		printf("vmclipshare: clipboard is disabled.\n");
		return 1;
	}
	length = min(strlen(text), maxlen);
	if (length) {
		int             i = 0;


		regs[0] = VMWARE_MAGIC;
		regs[1] = length;
		regs[2] = VMCMD_SET_CLIPLEN;
		regs[3] = VMWARE_PORT;
		vm_cmd(regs);
		while (i < length) {
			unsigned int   *pi = (unsigned int *) &text[i];
			regs[0] = VMWARE_MAGIC;
			regs[1] = *pi;
			regs[2] = VMCMD_SET_CLIPDATA;
			regs[3] = VMWARE_PORT;
			vm_cmd(regs);
			i += 4;
		}
	}
}

typedef struct {
	int ig;
	int x;
	int y;
} cabs_t;

void *move_cursor_abs( void *v )
	{
	cabs_t *cabs = (cabs_t *)v;
	//
	// We make sure the position is always odd. Fixes an ungrab problem when
	// moving the cursor off the right or bottom edge.
	//
	PhMoveCursorAbs(cabs->ig, cabs->x + (cabs->x%2 ? 0:1), cabs->y + (cabs->y%2 ? 0:1));
	//printf("PhMoveCursorAbs %d,%d\n", cabs->x, cabs->y);

	}

int
system_event_callback( PtWidget_t *widget, void *cdata, PtCallbackInfo_t *cbinfo )

{
	PhRegion_t region;
	PhRect_t rect;
	PhRegionDataHdr_t *data, *cliphdr;
	unsigned int len;
    PhEvent_t *event = cbinfo->event;
	PhSystemEvent_t *sedata=PhGetData(event);
	static int in_focus = 0;
	static int skip = 0;
	static int five_s_ctr = 0;

	if (event->type == Ph_EV_SYSTEM && event->subtype == Ph_SYSTEM_REGION_CHANGE) {
		data = (PhRegionDataHdr_t *) calloc(1, BUF_SIZE);
		PhRegionQuery(sedata->RegionChange.rid, &region, &rect, data, BUF_SIZE);
		cliphdr = PhRegionDataFindType(&region, data, Ph_RDATA_CLIPBOARD);
		if (cliphdr) {
			char *text;

    		//printf("vmclipshare: Got Region Change event for rid %d ig %d, clipboard regiondata flag present.\n", sedata->RegionChange.rid, sedata->RegionChange.input_group);
			if (!skip) {
				text = PhClipboardPasteString(sedata->RegionChange.input_group);
				if (text)
					set_clipboard(text);
			} else
				skip--;
		}
		free((char *)data);
	} else if (event->type == Ph_EV_TIMER) {
		int cur_focus;
		char *text;
		int ig = PhInputGroup(event);
		unsigned int cursor_pos;

		five_s_ctr++;
		if (five_s_ctr >= 5*1000/timer_interval) {
			unsigned int host_time;
			unsigned int vm_time;
			char ftime[40];
			char command[80];

			five_s_ctr = 0;
			host_time = get_time();
			vm_time = time(NULL);
			//
			// If our clock is out more than 10 seconds, adjust it.
			//
			if (abs(vm_time - host_time) > 10) {

				strftime(ftime, sizeof(ftime), "%C%y%m%d%H%M.%S", localtime(&host_time));
				printf("vmclipshare: Setting time to %s\n", ftime);
				sprintf(command, "op date %s", ftime);
				system(command);
			}
		}

		cur_focus = have_focus(&cursor_pos);
		//printf("Got cursor pos (%d,%d)\n", (short)(cursor_pos>>16), (short)(cursor_pos&0xFFFF));
		if (!in_focus && cur_focus) {
			//
			// We just came into focus. Set the clipboard based on the host's.
			//
			//printf("Got focus\n");
			text = get_clipboard(0);
			if (text) {
				skip = 1;
				//
				// The following line will generate a clipboard changed region
				// event that will make us end up in the code above. Since this
				// is redundant, we have set the "skip" variable to skip it.
				// 
				PhClipboardCopyString(ig, text);
				have_focus(&cursor_pos); // Update cursor pos
			}
		}
		if (cur_focus) {
			unsigned int settings = get_settings();
			//
			// The act of getting the cursor position also makes vmware expect
			// that we are going to do something with it like set it according
			// to our virtual machine's cursor position, so it does automatic
			// cursor movement based grab/ungrab. I.E. if the user has
			// "ungrab based on cursor movement" set, we MUST do this cursor
			// position processing.
			//
			//printf("x: %d, y: %d\n", regs[0]>>16, regs[0]&0xFFFF);
			if ((settings & VMPREF_UNGRAB_MOVE) && !(settings & VMPREF_FULLSCREEN)) {
				PhCursorInfo_t cursor_info;

				PhWindowQueryVisible( Ph_QUERY_CONSOLE, 0, ig, &rect );
				if (!in_focus) {
					cabs_t cabs;
					pthread_t tid;
					//
					// Just came into focus. Move the vm's cursor to the host
					// cursor's entry point so we can smoothly take over.
					// Note that if a process is being debugged PhMoveCursorAbs
					// may hang because the PhEmit will be reply blocked.
					// So we will start it in a separate thread and time it out
					// after one second.
					//
					cabs.ig = ig;
					cabs.x = rect.ul.x+(cursor_pos>>16);
					cabs.y = rect.ul.y+(cursor_pos&0xFFFF);
					if (pthread_create(&tid, NULL, move_cursor_abs, &cabs) == EOK) {
						struct timespec time;
						int retc;
						clock_gettime( CLOCK_REALTIME, &time);
						time.tv_sec++;
						retc = pthread_timedjoin(tid, NULL, &time);
						if (retc == ETIMEDOUT) {
							//printf("PhMoveCursorAbs timed out\n");
							pthread_cancel(tid);
							pthread_detach(tid);
						}
					}
				} else if (PhQueryCursor(ig, &cursor_info) == 0) {
					//
					// While in focus we just make sure the host's cursor tracks
					// the vm's cursor, so that it goes to the correct spot
					// for the ungrab.
					//
					int xx, yy;
					xx = cursor_info.pos.x-rect.ul.x;
					yy = cursor_info.pos.y-rect.ul.y;
					if (xx > 0)
						xx++;
					if (yy > 0)
						yy++;
					if (xx % 2)
						xx++;
					if (yy % 2)
						yy++;
					//printf("set_cursor %d,%d\n", xx, yy);
					set_cursor(xx, yy);
				}
				//
				// Choose a value to trade off smooth cursor grab/ungrab and
				// performance. Performance is an issue because we are running
				// under vmware.
				//
				timer_interval = SHORT_INTERVAL;
			} else {
				//
				// We are not moving the host cursor so don't bother with a 
				// performance-draining timer interval
				//
				timer_interval = LONG_INTERVAL;
			}
		}
		in_focus = cur_focus;
		PtTimerArm(widget, timer_interval);
	}

    return( Pt_CONTINUE );

}

int
main ( int argc, char *argv[] )

{
	PtWidget_t  *wdw;
	PhImage_t *pic;
	PhPoint_t    p = { 0, 0 };
	PtArg_t     args[10];
	int i;

	signal(SIGSEGV, segfault);
	get_settings();
	signal(SIGSEGV, SIG_DFL);
	i = 0;
	PtSetArg( &args[i++], Pt_ARG_WINDOW_TITLE, "VM Clipboard Share", 0 );
	PtSetArg( &args[i++], Pt_ARG_WINDOW_STATE, Ph_WM_STATE_ISHIDDEN, Ph_WM_STATE_ISHIDDEN );
	PtSetArg( &args[i++], Pt_ARG_WINDOW_MANAGED_FLAGS, Ph_WM_NO_FOCUS_LIST, 0xFFFFFFFF );
	wdw = PtAppInit( NULL, &argc, argv, i, args );

	if (!wdw) {
		printf("vmclipshare: Could not attach to Photon.\n");
		exit(1);
	}

	PtAddEventHandler(wdw, Ph_EV_SYSTEM|Ph_EV_TIMER, system_event_callback, NULL);

	PtRealizeWidget( wdw );

	PtTimerArm(wdw, timer_interval);


	/* Loop until killed (user cannot quit app window since it's hidden and
	   not on taskbar) */
	PtMainLoop( );
	PtExit( 0 );

	return 0;
}
