// MicroGlut is a stripped-down reimplementation of the classic GLUT/FreeGLUT library. By Ingemar Ragnemalm 2012-2015.
// This is the Linux version. There is also a Mac version (and drafts for a Windows version).

// Why do we use MicroGlut?
// 1) Transparency! Including it as a single source file makes it easy to see what it does.
// 2) Editability. Missing something? Want something to work a bit different? Just hack it in.
// 3) No obsolete stuff! The old GLUT has a lot of functions that are inherently obsolete. Avoid!

// Please note that this is still in an early stage. A lot of functionality is still missing.
// If you add/change something of interest, especially if it follows the GLUT API, please sumbit
// to me so I can consider adding that to the official version.

// 2012: Made a first draft by extracting context creation from other code.
// 130131: Fixed bugs in keyboard and update events. Seems to work pretty well! Things are missing, GL3 untested, but otherwise it runs stable with glutgears.
// 130206: Timers now tested and working. CPU load kept decent when not intentionally high.
// 130907: Bug fixes, test for non-existing gReshape, glutKeyboardUpFunc corrected.
// 130916: Fixed the event bug. Cleaned up most comments left from debugging.
// 130926: Cleaned up warnings, added missing #includes.
// 140130: A bit more cleanup for avoiding warnings (_BSD_SOURCE below).
// 140401: glutKeyIsDown and glutWarpPointer added and got an extra round of testing.
// 150205: glutRepeatingTimer new, better name for glutRepeatingTimerFunc
// Added a default glViewport call when the window is resized.
// Made a bug fix in the event processing so that mouse drag events won't stack up.
// Somewhere here I added a kind of full-screen support (but without removing window borders).
// 150216: Added proper OpenGL3 initalization. (Based on a patch by Sebastian Parborg.)
// 150223: Finally, decent handling on the GLUT configuration!
// 150227: Resize triggers an update!
// 150302: Window position, multisample, even better config
// 150618: Added glutMouseIsDown() (not in the old GLUT API but a nice extension!).
// Added #ifdefs to produce errors if compiled on the wrong platform!
// 150909: Added glutExit.
// 150924: Added support for special keys.
// 160302: Added glutShowCursor and glutHideCursor.
// 170405: Made some globals static.
// 170406: Added "const" to string arguments to make C++ happier. glutSpecialFunc and glutSpecialUpFunc are now officially supported - despite being deprecated. (I recommend that you use the same keyboard func for everything.) Added support for multiple mouse buttons (right and left).
// 170410: Modified glutWarpPointer to make it more robust. Commended out some unused variables to avoid warnings.
// 180124: Modifications to make it work better on recent MESA, which seems to have introduced some changes. Adds glFlush() in glutSwapBuffers and a timer when starting glutMain to invoke an update after 100 ms.
// 180208: Added GLUT_WINDOW_WIDTH, GLUT_WINDOW_HEIGHT, GLUT_MOUSE_POSITION_X and GLUT_MOUSE_POSITION_Y to GlutGet. They were already in the Mac version, so let's converge the versions a bit.

#define _DEFAULT_SOURCE
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <GL/gl.h>
#define GLX_GLXEXT_PROTOTYPES
#include <GL/glext.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <GL/glx.h>
#include "MicroGlut.h"
#include <sys/time.h>
#include <unistd.h>

#ifndef M_PI
#define M_PI 3.14159265
#endif

// If this is compiled on the Mac or Windows, tell me!
#ifdef __APPLE__
	ERROR! This is NOT the Mac version of MicroGlut and will not work on the Mac!
#endif
#ifdef _WIN32
	ERROR! This is NOT the Windows version of MicroGlut and will not work on Windows!
#endif

static unsigned int winWidth = 300, winHeight = 300;
static unsigned int winPosX = 40, winPosY = 40;
//static int mode = GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH;
static int gContextVersionMajor = 0;
static int gContextVersionMinor = 0;

static Display *dpy;
static Window win;
static GLXContext ctx;
//static char *dpyName = NULL;
int gMode; // NOT YET USED
static char animate = 1; // Use for glutNeedsRedisplay?
struct timeval timeStart;
static Atom wmDeleteMessage; // To handle delete msg
static char gKeymap[256];
static char gRunning = 1;

void glutInit(int *argc, char *argv[])
{
	gettimeofday(&timeStart, NULL);
	memset(gKeymap, 0, sizeof(gKeymap));
}
void glutInitDisplayMode(unsigned int mode)
{
	gMode = mode; // NOT YET USED
}
void glutInitWindowSize(int w, int h)
{
	winWidth = w;
	winHeight = h;
}

void glutInitWindowPosition (int x, int y)
{
	winPosX = x;
	winPosY = y;
}

static void checktimers();

/*
 * Create an RGB, double-buffered window.
 * Return the window and context handles.
 */

static void
make_window( Display *dpy, const char *name,
             int x, int y, int width, int height,
             Window *winRet, GLXContext *ctxRet)
{
   int scrnum;
   XSetWindowAttributes attr;
   unsigned long mask;
   Window root;
   Window win;
   GLXContext ctx;
   XVisualInfo *visinfo;

   scrnum = DefaultScreen( dpy );
   root = RootWindow( dpy, scrnum );

// 3.2 support
//#ifdef glXCreateContextAttribsARB
	if (gContextVersionMajor > 2)
	{
// We asked for OpenGL3+, but can we do it?

		typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);

		// Verify GL driver supports glXCreateContextAttribsARB()
		glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;

		// Verify that GLX implementation supports the new context create call
		if ( strstr( glXQueryExtensionsString( dpy, scrnum ), 
			"GLX_ARB_create_context" ) != 0 )
		glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
			glXGetProcAddress( (const GLubyte *) "glXCreateContextAttribsARB" );

		if ( !glXCreateContextAttribsARB )
		{
			printf( "Can't create new-style GL context\n" );
		}

// We need this for OpenGL3
		int elemc;
		GLXFBConfig *fbcfg;

	   int attribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT,
                     GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, // ?
                     GLX_RED_SIZE, 1, // 1 = prefer high precision
                     GLX_GREEN_SIZE, 1,
                     GLX_BLUE_SIZE, 1,
                     GLX_ALPHA_SIZE, 1,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None };

		int i = 12;
		if (gMode & GLUT_DOUBLE)
		{
			attribs[i++] = GLX_DOUBLEBUFFER;
			attribs[i++] = 1;
		}
		if (gMode & GLUT_DEPTH)
		{
			attribs[i++] = GLX_DEPTH_SIZE;
			attribs[i++] = 1;
		}
		if (gMode & GLUT_STENCIL)
		{
			attribs[i++] = GLX_STENCIL_SIZE;
			attribs[i++] = 8; // Smallest available, at least 8. Configurable setting needed!
		}
		if (gMode & GLUT_MULTISAMPLE)
		{
			attribs[i++] = GLX_SAMPLE_BUFFERS;
			attribs[i++] = 1;
			attribs[i++] = GLX_SAMPLES;
			attribs[i++] = 4;
		}

		fbcfg = glXChooseFBConfig(dpy, scrnum, attribs, &elemc);
		if (!fbcfg)
		{
			fbcfg = glXChooseFBConfig(dpy, scrnum, NULL, &elemc);
		}
		if (!fbcfg)
			printf("Couldn't get FB configs\n");

		int gl3attr[] =
		{
			GLX_CONTEXT_MAJOR_VERSION_ARB, gContextVersionMajor,
			GLX_CONTEXT_MINOR_VERSION_ARB, gContextVersionMinor,
//			GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
			GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB,
			GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
			None
		};
		ctx = glXCreateContextAttribsARB(dpy, fbcfg[0], NULL, 1, gl3attr);
		if (ctx == NULL) printf("No ctx!\n");

        visinfo = glXGetVisualFromFBConfig(dpy, fbcfg[0]);
        if (!visinfo)
            printf("Error: couldn't create OpenGL window with this pixel format.\n");

	}
	else // old style
//#endif
	{
	   int attribs[] = { GLX_RGBA,
                     GLX_RED_SIZE, 1, // 1 = prefer high precision
                     GLX_GREEN_SIZE, 1,
                     GLX_BLUE_SIZE, 1,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None,
                     None };

		int i = 7;
		if (gMode & GLUT_DOUBLE)
			attribs[i++] = GLX_DOUBLEBUFFER;
		if (gMode & GLUT_DEPTH)
		{
			attribs[i++] = GLX_DEPTH_SIZE;
			attribs[i++] = 1;
		}
		if (gMode & GLUT_STENCIL)
		{
			attribs[i++] = GLX_STENCIL_SIZE;
			attribs[i++] = 8; // Smallest available, at least 8. Configurable setting needed!
		}
    	visinfo = glXChooseVisual( dpy, scrnum, attribs );
		if (!visinfo)
		{
			printf("Error: couldn't get a visual according to settings\n");
			exit(1);
		}

		ctx = glXCreateContext( dpy, visinfo, 0, True );
		if (ctx == NULL) printf("No ctx!\n");
	}

   /* window attributes */
   attr.background_pixel = 0;
   attr.border_pixel = 0;
   attr.colormap = XCreateColormap( dpy, root, visinfo->visual, AllocNone);
   attr.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPress | ButtonReleaseMask | Button1MotionMask | PointerMotionMask;
   attr.override_redirect = 0;
   mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect;

   win = XCreateWindow( dpy, root, x, y, width, height,
		        0, visinfo->depth, InputOutput,
		        visinfo->visual, mask, &attr );

// Register delete!
	wmDeleteMessage = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	XSetWMProtocols(dpy, win, &wmDeleteMessage, 1); // Register

   /* set hints and properties */
      XSizeHints sizehints;
      sizehints.x = x;
      sizehints.y = y;
      sizehints.width  = width;
      sizehints.height = height;
      sizehints.flags = USSize | USPosition;
      XSetNormalHints(dpy, win, &sizehints);
      XSetStandardProperties(dpy, win, name, name,
                              None, (char **)NULL, 0, &sizehints);

   if (!ctx)
   {
      printf("Error: glXCreateContext failed\n");
      exit(1);
   }

   XFree(visinfo);

   *winRet = win;
   *ctxRet = ctx;
}

void glutCreateWindow(const char *windowTitle)
{
   dpy = XOpenDisplay(NULL);
   if (!dpy)
   {
      printf("Error: couldn't open display %s\n",
	     windowTitle ? windowTitle : getenv("DISPLAY"));
   }

   make_window(dpy, windowTitle, winPosX, winPosY, winWidth, winHeight, &win, &ctx);
   
   XMapWindow(dpy, win);
   glXMakeCurrent(dpy, win, ctx);
}

void (*gDisplay)(void);
void (*gReshape)(int width, int height);
void (*gIdle)(void);
void (*gKey)(unsigned char key, int x, int y);
void (*gKeyUp)(unsigned char key, int x, int y);
void (*gSpecialKey)(unsigned char key, int x, int y);
void (*gSpecialKeyUp)(unsigned char key, int x, int y);
void (*gMouseMoved)(int x, int y);
void (*gMouseDragged)(int x, int y);
void (*gMouseFunc)(int button, int state, int x, int y);
int gLastMousePositionX, gLastMousePositionY; // Avoids a problem with glutWarpPointer

// Maybe I should just drop these for simplicity
//void (*gSpecialKey)(unsigned char key, int x, int y) = NULL;
//void (*gSpecialKeyUp)(unsigned char key, int x, int y) = NULL;


void glutReshapeFunc(void (*func)(int width, int height))
{
	gReshape = func;
}
void glutDisplayFunc(void (*func)(void))
{
	gDisplay = func;
}
void glutIdleFunc(void (*func)(void))
{gIdle = func;}

void glutKeyboardFunc(void (*func)(unsigned char key, int x, int y))
{
	gKey = func;
}
void glutKeyboardUpFunc(void (*func)(unsigned char key, int x, int y))
{
	gKeyUp = func;
}
void glutSpecialFunc(void (*func)(unsigned char key, int x, int y))
{
	gSpecialKey = func;
}

void glutSpecialUpFunc(void (*func)(unsigned char key, int x, int y))
{
	gSpecialKeyUp = func;
}

void glutMouseFunc(void (*func)(int button, int state, int x, int y))
{gMouseFunc = func;}
void glutMotionFunc(void (*func)(int x, int y))
{gMouseDragged = func;}
void glutPassiveMotionFunc(void (*func)(int x, int y))
{gMouseMoved = func;}

char gButtonPressed[10] = {0,0,0,0,0,0,0,0,0,0};

void doKeyboardEvent(XEvent event, void (*keyProc)(unsigned char key, int x, int y), void (*specialKeyProc)(unsigned char key, int x, int y), int keyMapValue)
{
	char buffer[10];
//	int r; // code;
	
	int code = ((XKeyEvent *)&event)->keycode;
	
//	r = 
	XLookupString(&event.xkey, buffer, sizeof(buffer), NULL, NULL);
	char raw = buffer[0]; // Before remapping
	switch(code)
	{
		case 111: buffer[0] = GLUT_KEY_UP; break;
		case 114: buffer[0] = GLUT_KEY_RIGHT; break;
		case 116: buffer[0] = GLUT_KEY_DOWN; break;
		case 113: buffer[0] = GLUT_KEY_LEFT; break;
		case 67: buffer[0] = GLUT_KEY_F1; break;
		case 68: buffer[0] = GLUT_KEY_F2; break;
		case 69: buffer[0] = GLUT_KEY_F3; break;
		case 70: buffer[0] = GLUT_KEY_F4; break;
		case 71: buffer[0] = GLUT_KEY_F5; break;
		case 72: buffer[0] = GLUT_KEY_F6; break;
		case 73: buffer[0] = GLUT_KEY_F7; break;
		case 112: buffer[0] = GLUT_KEY_PAGE_UP; break;
		case 117: buffer[0] = GLUT_KEY_PAGE_DOWN; break;
		case 110: buffer[0] = GLUT_KEY_HOME; break;
		case 115: buffer[0] = GLUT_KEY_END; break;
		case 118: buffer[0] = GLUT_KEY_INSERT; break;

		case 50: buffer[0] = GLUT_KEY_LEFT_SHIFT; break;
		case 62: buffer[0] = GLUT_KEY_RIGHT_SHIFT; break;
		case 37:case 105: buffer[0] = GLUT_KEY_CONTROL; break;
		case 64:case 108: buffer[0] = GLUT_KEY_ALT; break;
		case 133:case 134: buffer[0] = GLUT_KEY_COMMAND; break;

// Keypad
		case 90: buffer[0] = GLUT_KEY_INSERT; break;
		case 87: buffer[0] = GLUT_KEY_END; break;
		case 88: buffer[0] = GLUT_KEY_DOWN; break;
		case 89: buffer[0] = GLUT_KEY_PAGE_DOWN; break;
		case 83: buffer[0] = GLUT_KEY_LEFT; break;
//		case 84: buffer[0] = GLUT_KEY_KEYPAD_5; break;
		case 85: buffer[0] = GLUT_KEY_RIGHT; break;
		case 79: buffer[0] = GLUT_KEY_HOME; break;
		case 80: buffer[0] = GLUT_KEY_UP; break;
		case 81: buffer[0] = GLUT_KEY_PAGE_UP; break;
		case 82: buffer[0] = 127; break;
//		case 77: buffer[0] = GLUT_KEY_KEYPAD_NUMLOCK; break;
	}	
	
	// If we asked for a separate callback for special ketys, call it. Otherwise call the standard one.
	// I am considering removing the special callback for simplicity!
	if (raw == 0)
	{
		if (specialKeyProc)
			specialKeyProc(buffer[0], 0, 0);
		else
			if (keyProc)
				keyProc(buffer[0], 0, 0);
	}
	else
		if (keyProc)
			keyProc(buffer[0], 0, 0);
	gKeymap[(int)buffer[0]] = keyMapValue;
	
//	printf("%c %d %d %d\n", buffer[0], buffer[0], r, code);

//	      			if (event.type == KeyPress)
//		      		{	if (gKey) gKey(buffer[0], 0, 0); gKeymap[(int)buffer[0]] = 1;}
//		      		else
//		      		{	if (gKeyUp) gKeyUp(buffer[0], 0, 0); gKeymap[(int)buffer[0]] = 0;}
}

void internaltimer(int x)
{
	glutPostRedisplay();
}

void glutMainLoop()
{
	char pressed = 0;
	int i;

	XAllowEvents(dpy, AsyncBoth, CurrentTime);

// 2018-01-24: An attempt to patch over the problem that recent MESA tends to fail the first update.
	glutTimerFunc(100, internaltimer, 0);

	while (gRunning)
	{
//      int op = 0;
      while (XPending(dpy) > 0)
      {
         XEvent event;
         XNextEvent(dpy, &event);

         switch (event.type)
         {
         	case ClientMessage:
         		if (event.xclient.data.l[0] == wmDeleteMessage) // quit!
         			gRunning = 0;
	         	break;
         	case Expose: 
//			op = 1; 
				break; // Update event! Should do draw here.
         	case ConfigureNotify:
				if (gReshape)
	      			gReshape(event.xconfigure.width, event.xconfigure.height);
				else
				{
					glViewport(0, 0, event.xconfigure.width, event.xconfigure.height);
				}
				animate = 1;
				winWidth = event.xconfigure.width;
				winHeight = event.xconfigure.height;
      			break;
      		case KeyPress:
				doKeyboardEvent(event, gKey, gSpecialKey, 1);break;
      		case KeyRelease:
				doKeyboardEvent(event, gKeyUp, gSpecialKeyUp, 0);break;
			case ButtonPress:
				gButtonPressed[event.xbutton.button] = 1;
				if (gMouseFunc != NULL)
				switch (event.xbutton.button)
				{
				case Button1:
					gMouseFunc(GLUT_LEFT_BUTTON, GLUT_DOWN, event.xbutton.x, event.xbutton.y);break;
				case Button2:
					gMouseFunc(GLUT_MIDDLE_BUTTON, GLUT_DOWN, event.xbutton.x, event.xbutton.y);break;
				case Button3:
					gMouseFunc(GLUT_RIGHT_BUTTON, GLUT_DOWN, event.xbutton.x, event.xbutton.y);break;
				}
				break;
			case ButtonRelease:
				gButtonPressed[event.xbutton.button] = 0;
				if (gMouseFunc != NULL)
				switch (event.xbutton.button)
				{
				case Button1:
					gMouseFunc(GLUT_LEFT_BUTTON, GLUT_UP, event.xbutton.x, event.xbutton.y);break;
				case Button2:
					gMouseFunc(GLUT_MIDDLE_BUTTON, GLUT_UP, event.xbutton.x, event.xbutton.y);break;
				case Button3:
					gMouseFunc(GLUT_RIGHT_BUTTON, GLUT_UP, event.xbutton.x, event.xbutton.y);break;
				}
				break;
		case MotionNotify:
				pressed = 0;
				for (i = 0; i < 5; i++)
					if (gButtonPressed[i]) pressed = 1;

				// Saving the last known position in order to avoid problems for glutWarpPointer
				// If we try warping to this position, don't!
				gLastMousePositionX = event.xbutton.x;
				gLastMousePositionY = event.xbutton.y;

				if (pressed && gMouseDragged)
					gMouseDragged(event.xbutton.x, event.xbutton.y);
				else
				if (gMouseMoved)
					gMouseMoved(event.xbutton.x, event.xbutton.y);
				break;

		default:
			break;
         }
      }
      
      if (animate)
      {
      	animate = 0;
		if (gDisplay)
		  	gDisplay();
		else
			printf("No display function!\n");
//      	op = 0;
      }
		else
		if (gIdle) gIdle();
      checktimers();
   }

	glXMakeCurrent(dpy, None, NULL);
   glXDestroyContext(dpy, ctx);
   XDestroyWindow(dpy, win);
   XCloseDisplay(dpy);
}

void glutSwapBuffers()
{
	glFlush(); // Added 2018-01-24, part of a fix for new MESA
	glXSwapBuffers(dpy, win);
}

void glutPostRedisplay()
{
	animate = 1;
}

int glutGet(int type)
{
	struct timeval tv;
	
	switch (type)
	{
		case GLUT_ELAPSED_TIME:
		gettimeofday(&tv, NULL);
		return (tv.tv_usec - timeStart.tv_usec) / 1000 + (tv.tv_sec - timeStart.tv_sec)*1000;
		case GLUT_WINDOW_WIDTH:
		return winWidth;
		case GLUT_WINDOW_HEIGHT:
		return winHeight;
		case GLUT_MOUSE_POSITION_X:
		return gLastMousePositionX;
		case GLUT_MOUSE_POSITION_Y:
		return gLastMousePositionY;
	}
	return 0;
}

// NOTE: The timer is not designed with any multithreading in mind!
typedef struct TimerRec
{
	int arg;
	int time;
	int repeatTime;
	void (*func)(int arg);
	char repeating;
	struct TimerRec *next;
	struct TimerRec *prev;
} TimerRec;

TimerRec *gTimers = NULL;

void glutTimerFunc(int millis, void (*func)(int arg), int arg)
{
	TimerRec *t	= (TimerRec *)malloc(sizeof(TimerRec));
	t->arg = arg;
	t->time = millis + glutGet(GLUT_ELAPSED_TIME);
	t->repeatTime = 0;
	t->repeating = 0;
	t->func = func;
	t->next = gTimers;
	t->prev = NULL;
	if (gTimers != NULL)
		gTimers->prev = t;
	gTimers = t;
}

// Added by Ingemar
// void glutRepeatingTimerFunc(int millis)
void glutRepeatingTimer(int millis)
{
	TimerRec *t	= (TimerRec *)malloc(sizeof(TimerRec));
	t->arg = 0;
	t->time = millis + glutGet(GLUT_ELAPSED_TIME);
	t->repeatTime = millis;
	t->repeating = 1;
	t->func = NULL;
	t->next = gTimers;
	t->prev = NULL;
	if (gTimers != NULL)
		gTimers->prev = t;
	gTimers = t;
}

static void checktimers()
{
	if (gTimers != NULL)
	{
		TimerRec *t, *firethis = NULL;
		int now = glutGet(GLUT_ELAPSED_TIME);
		int nextTime = now + 1000; // Distant future, 1 second
		t = gTimers;
		for (t = gTimers; t != NULL; t = t->next)
		{
			if (t->time < nextTime) nextTime = t->time; // Time for the next one
			if (t->time < now) // See if this is due to fire
			{
				firethis = t;
			}
		}
		if (firethis != NULL)
		{
		// Fire the timer
			if (firethis->func != NULL)
				firethis->func(firethis->arg);
			else
				glutPostRedisplay();
		// Remove the timer if it was one-shot, otherwise update the time
			if (firethis->repeating)
			{
				firethis->time = now + firethis->repeatTime;
			}
			else
			{
				if (firethis->prev != NULL)
					firethis->prev->next = firethis->next;
				else
					gTimers = firethis->next;
                if (firethis->next != NULL)
					firethis->next->prev = firethis->prev;
				free(firethis);
			}
		}
		// Otherwise, sleep until any timer should fire
        if (!animate)
			if (nextTime > now)
            {
		usleep((nextTime - now)*1000);
            }
	}
    else
// If no timer and no update, sleep a little to keep CPU load low
        if (!animate)
            usleep(10);
}

void glutInitContextVersion(int major, int minor)
{
	gContextVersionMajor = major;
	gContextVersionMinor = minor;
}

// Based on FreeGlut glutWarpPointer, but with a significant improvement!
/*
 * Moves the mouse pointer to given window coordinates
 */
void glutWarpPointer( int x, int y )
{
    if (dpy == NULL)
    {
      fprintf(stderr, "glutWarpPointer failed: MicroGlut not initialized!\n");
    	return;
    }

	if (x == gLastMousePositionX && y == gLastMousePositionY)
		return; // Don't warp to where we already are - this causes event flooding!

    XWarpPointer(
        dpy, // fgDisplay.Display,
        None,
        win, // fgStructure.CurrentWindow->Window.Handle,
        0, 0, 0, 0,
        x, y
    );
    /* Make the warp visible immediately. */
    XFlush( dpy );
//    XFlush( fgDisplay.Display );
}

// Replaces glutSetMousePointer. This limits us to the two most common cases: None and arrow!
void glutShowCursor()
{
	XUndefineCursor(dpy, win);
}

void glutHideCursor()
{
	if (dpy == NULL) 
	{
	   printf("glutHideCursor failed: MicroGlut not initialized!\n");
   	return;
	}
	
	Cursor invisibleCursor;
	Pixmap bitmapNoData;
	static char noll[] = { 0,0,0};
	bitmapNoData = XCreateBitmapFromData(dpy, win, noll, 1, 1);
	invisibleCursor = XCreatePixmapCursor(dpy,bitmapNoData, bitmapNoData, 
	                                     (XColor *)noll, (XColor *)noll, 0, 0);
	XDefineCursor(dpy,win, invisibleCursor);
	XFreeCursor(dpy, invisibleCursor);
	XFreePixmap(dpy, bitmapNoData);
}



char glutKeyIsDown(unsigned char c)
{
	return gKeymap[(unsigned int)c];
}

// Added by the Risinger/R\8Cberg/Wikstr\9Am project! But... gButtonPressed
// was already here! Did I miss something?
char glutMouseIsDown(unsigned char c)
{
	return gButtonPressed[(unsigned int)c];
}



// These were missing up to 150205

void glutReshapeWindow(int width, int height)
{
	XResizeWindow(dpy, win, width, height);
}
void glutPositionWindow(int x, int y)
{
	XMoveWindow(dpy, win, x, y);
}
void glutSetWindowTitle(char *title)
{
	XStoreName(dpy, win, title);
}

// Not complete full screen mode yet since the window frame and menu are not hidden yet

char gFullScreen = 0;
unsigned int savedHeight, savedWidth;
int savedX, savedY;

void glutFullScreen()
{
	gFullScreen = 1;


	Drawable d;
	unsigned int a, b;

	XGetGeometry(dpy, win, &d, &savedX, &savedY, &savedWidth, &savedHeight, &a, &b);

    int scrnum = DefaultScreen(dpy);
    int width = DisplayWidth( dpy, scrnum );
    int height = DisplayHeight( dpy, scrnum );

	XMoveResizeWindow(dpy, win, 0, 0, width, height);
}

void glutExitFullScreen()
{
	gFullScreen = 0;
	XMoveResizeWindow(dpy, win, savedX, savedY, savedWidth, savedHeight);
}

void glutToggleFullScreen()
{
	if (gFullScreen)
		glutExitFullScreen();
	else
		glutFullScreen();
}

void glutExit()
{
	gRunning = 0;
}

