common/Linux/MicroGlut.c
b23636c9
 // 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;
 }