/*******************************************************************************
**3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 
**      10        20        30        40        50        60        70        80
**
** program:
**    rgba-glx
**
** author:
**    Mirco "MacSlow" Mueller <macslow@bangang.de>
**
** created:
**    8.4.2006 
**
** last change:
**    10.4.2006
**
** notes:
**    Creates a spinning sphere "floating" on the desktop. You need to run Xorg
**    7.0 in a Composite-enabled environment, xcompmgr, an nvidia card, the new
**    1.0-8756 driver and...
**
**       Option "AddARGBGLXVisuals" "true"
**
**    in the screen-section of your /etc/X11/xorg.conf. This is all different
**    from using Xgl, compiz and xwinwrap. You don't need those for it to work.
**    I would say that it will _not_ work under Xgl/compiz at all. You can move
**    it around with your typical window-managers combo <Alt>-LMB-drag and quit
**    with ESC.
**
** how to compile:
**    gcc -Wall -g -DXK_MISCELLANY -lXi -lGL -lGLU -lXrender -lglut \
**    rgba-glx.c -o rgba-glx
**
** what you need to compile:
**    The whole set of development files for...
**
**        Xorg 7.0 (including the Render-extension)
**        OpenGL (obviously from nvidia)
**        glut (I used freeglut)
**
** ideas:
**    spinning 3D-logos for machines running on fairs for the "show off"-effect
**    the most funky desktop-"widgets" you can think of
**    a 3D-version of cairo-clock maybe ;)
**    a sick gstreamer/GL-based video-player
**    most dope-ish dock-like programs
**    stylishly animated slash-screens for programs
**    (add... better yet implement your own stuff here)
**
** license:
**    I place this under the LGPL in the hope to see it getting leveraged in
**    as many places as possible (e.g. gtkglext/mm), because this is some very
**    cool feature, which should be useable as easy as possible!
**
** warranty/disclaimer:
**    This code is provided "as is" take it or leave it. It is unsupported! It
**    works for me but might not work for you. I will not take you by the hand
**    and talk you through getting this thing compiled and running on your box.
**    While I tried my best to keep it sane and save I cannot be held
**    responsible if compiling/runnings this code will cause your pants to catch
**    fire, your kitten dying a horrible death or else. Use it at your own risk!
**    You have been warned!
**
*******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/keysymdef.h>
#include <X11/extensions/Xrender.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>
#include <GL/glut.h>

#define WIN_WIDTH 450
#define WIN_HEIGHT 450
#define FOV 45.0f
#define Z_NEAR 3.0f

/* this struct and the two defines are needed to be able to get rid of the wm
** decorations, if there's any simpler way to achieve this with fewer code I'll
** be glad to hear that, taken from gtk+ sources */
typedef struct {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long input_mode;
    unsigned long status;
} MotifWmHints, MwmHints;

#define MWM_HINTS_FUNCTIONS     (1L << 0)
#define MWM_HINTS_DECORATIONS   (1L << 1)

/* this here is from the hints nvidia gives in their driver docs for utilizing
** the new AddARGBGLXVisuals option for xorg.conf */
int doubleBufferAttributes[] = {
	GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
	GLX_RENDER_TYPE,   GLX_RGBA_BIT,
	GLX_DOUBLEBUFFER,  True,
	GLX_RED_SIZE,      1,
	GLX_GREEN_SIZE,    1,
	GLX_BLUE_SIZE,     1,
	GLX_ALPHA_SIZE,    1,
	GLX_DEPTH_SIZE,    1,
	None
};

float g_fAngleZ = 0.0f;

/* this one tests if our window was already mapped (appeared on the display) */
Bool wait_for_notify (Display* pDisplay,
					  XEvent* pEvent,
					  XPointer arg)
{
	return (pEvent->type == MapNotify) && (pEvent->xmap.window == (Window) arg);
}

/* resize the GL-context/viewport */
void resize_gl (int iWidth,
				int iHeight)
{
	glViewport (0, 0, iWidth, iHeight);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective (FOV, (float) (iWidth) / (float) (iHeight), 0.1f, 100.0f);
	glMatrixMode (GL_MODELVIEW);
}

/* setup some initial states for our GL-context */
void init_gl (int iWidth,
			  int iHeight)
{
	float fBGAlpha = 0.0f; /* background-opacity */
	float afBGColor[] = {0.0f, 0.0f, 0.0f}; /* background-color itself */

	/* the Render-extention expects premultiplied alpha colors */
	glClearColor (fBGAlpha * afBGColor[0],
				  fBGAlpha * afBGColor[1],
				  fBGAlpha * afBGColor[2],
				  fBGAlpha);

    glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable (GL_DEPTH_TEST);
	glLineWidth (1.5f);
	glEnable (GL_LIGHTING);
	glEnable (GL_LIGHT0);
	glPolygonMode (GL_BACK, GL_FILL);
	glPolygonMode (GL_FRONT, GL_LINE);
	glDisable (GL_CULL_FACE);
	glLightModelf (GL_LIGHT_MODEL_TWO_SIDE, 1.0f);
	glShadeModel (GL_FLAT);

	resize_gl (iWidth, iHeight);

	glLoadIdentity ();
	gluLookAt (0.0f, 0.0f, Z_NEAR,
			   0.0f, 0.0f, 0.0f,
			   0.0f, 1.0f, 0.0f);
	glTranslatef (0.0f, 0.0f, -Z_NEAR);
}

/* the main function where the whole drawing takes place */
void draw_gl (Display* pDisplay,
			  Window window,
			  int iWhichGimmick)
{
	GLfloat afFrontDiffuseMat[] = {1.0f, 0.5f, 0.25f, 1.0f};
	GLfloat afBackDiffuseMat[] = {0.25f, 0.5f, 1.0f, 0.9f};
	GLfloat afFrontDiffuseMat2[] = {0.5f, 0.25f, 1.0f, 1.0f};
	GLfloat afBackDiffuseMat2[] = {1.0f, 0.25f, 0.5f, 0.9f};

	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (iWhichGimmick == 1)
	{
		glPushMatrix ();
		glTranslatef (0.0f, 0.0f, 3.0f);
		glRotatef (g_fAngleZ, 0.0f, 0.0f, 1.0f);
		glRotatef (0.5f * g_fAngleZ, 0.0f, 1.0f, 0.0f);
		glRotatef (135.0f, 1.0f, 0.0f, 0.0f);
		glMaterialfv (GL_FRONT, GL_DIFFUSE, afFrontDiffuseMat);
		glMaterialfv (GL_BACK, GL_DIFFUSE, afBackDiffuseMat);
		glutSolidSphere (1.0f, 24, 12);
		glPopMatrix ();
	}
	else
	{
		glPushMatrix ();
		glTranslatef (0.0f, 0.0f, 3.0f);
		glRotatef (g_fAngleZ, 0.0f, 0.0f, 1.0f);
		glRotatef (0.5f * g_fAngleZ, 0.0f, 1.0f, 0.0f);
		glRotatef (135.0f, 1.0f, 0.0f, 0.0f);
		glPolygonMode (GL_FRONT, GL_LINE);
		glPolygonMode (GL_BACK, GL_FILL);
		glMaterialfv (GL_FRONT, GL_DIFFUSE, afFrontDiffuseMat);
		glMaterialfv (GL_BACK, GL_DIFFUSE, afBackDiffuseMat);
		glScalef (0.5f, 0.5f, 0.5f);
		glutSolidDodecahedron();
		glPopMatrix ();

		glMaterialfv (GL_FRONT, GL_DIFFUSE, afFrontDiffuseMat2);
		glMaterialfv (GL_BACK, GL_DIFFUSE, afBackDiffuseMat2);
		glPushMatrix ();
		glTranslatef (0.0f, 0.0f, 3.0f);
		glRotatef (1.2f * g_fAngleZ, 0.0f, 1.0f, 0.0f);
		glRotatef (0.75f * g_fAngleZ, 1.0f, 1.0f, 0.0f);
		glRotatef (221.0f, 1.0f, 0.0f, 0.0f);
		glPolygonMode (GL_FRONT, GL_FILL);
		glScalef (0.5f, 0.5f, 0.5f);
		glutSolidOctahedron();
		glPopMatrix ();
	}

	glXSwapBuffers (pDisplay, window);
}

int main (int argc,
		  char** argv)
{
	Display* pDisplay = NULL;
	Window window;
	XEvent event;
	XVisualInfo* pVisInfo;
	XSetWindowAttributes windowAttribs;
	GLXFBConfig* pFBConfigs;
	GLXFBConfig renderFBConfig;
	GLXContext glxContext;
	GLXWindow glxWindow;
	int windowAttribsMask;
	int iNumOfFBConfigs;
	int iRenderEventBase;
	int iRenderErrorBase;
	int i;
	XRenderPictFormat* pPictFormat = NULL;
	Bool bKeepGoing = False;
	Atom hintsAtom = None;
	unsigned char* pucData;
	MotifWmHints newHints;
	Atom typeAtom;
	int iFormat;
	unsigned long ulItems;
	unsigned long ulBytesAfter;
	Bool bGotEvent = False;
	Bool bLMBPressed = False;
	int iWhichGimmick = 1;

	bzero ((void*) &newHints, sizeof (MotifWmHints));

	/* check which object-gimmick should be drawn */
	if (argc == 2)
		iWhichGimmick = atoi (argv[1]);

	/* open a connection to the X server */
	pDisplay = XOpenDisplay (NULL);
	if (pDisplay == NULL)
	{
		fprintf (stderr, "Unable to open a connection to the X server!\n");
		return 1;
	}

	/* test for the RENDER extension */
	if (!XRenderQueryExtension (pDisplay, &iRenderEventBase, &iRenderErrorBase))
	{
		fprintf (stderr, "Damn, no RENDER extension found!\n");
		return 2;
	}

	/* look for a framebuffer configuration, only try to get a double
	** buffered config */
	pFBConfigs = glXChooseFBConfig (pDisplay,
									DefaultScreen (pDisplay),
									doubleBufferAttributes,
									&iNumOfFBConfigs);

	if (pFBConfigs == NULL)
	{
		fprintf (stderr, "Argl, we could not get an ARGB-visual!\n");
		return 3;
	}

	/* try to find a GLX visual with alpha from the list of fb-configs */
	for (i = 0; i < iNumOfFBConfigs; i++)
	{
		pVisInfo = glXGetVisualFromFBConfig (pDisplay, pFBConfigs[i]);
        if (!pVisInfo)
			continue;

        pPictFormat = XRenderFindVisualFormat (pDisplay, pVisInfo->visual);
        if (!pPictFormat)
			continue;

		if (pPictFormat->direct.alphaMask > 0)
		{
			fprintf (stderr, "Strike, found a GLX visual with alpha-support!\n");
			pVisInfo = glXGetVisualFromFBConfig (pDisplay, pFBConfigs[i]);
			renderFBConfig = pFBConfigs[i];
			bKeepGoing = True;
			break;
		}

		XFree (pVisInfo);
    }

	if (!bKeepGoing)
	{
		fprintf (stderr, "Crap, was not able to acquire a Render-compatible GLX-visual!\n");
		return 4;
	}

	/* set some window-attributes before... */
	windowAttribs.border_pixel = 0;
	windowAttribs.event_mask = StructureNotifyMask |
							   ButtonPressMask |
							   ButtonReleaseMask |
							   KeyPressMask |
							   PointerMotionMask;
	windowAttribs.colormap = XCreateColormap (pDisplay,
											  RootWindow (pDisplay,
														  pVisInfo->screen),
											  pVisInfo->visual,
											  AllocNone);

	windowAttribsMask = CWBorderPixel | CWColormap | CWEventMask;

	/* ... creating our application window */
	window = XCreateWindow (pDisplay,
							RootWindow (pDisplay, pVisInfo->screen),
							0,
							0,
							WIN_WIDTH,
							WIN_HEIGHT,
							0,
							pVisInfo->depth,
							InputOutput,
							pVisInfo->visual,
							windowAttribsMask,
							&windowAttribs);

	/* create a GLX context for OpenGL rendering */
	glxContext = glXCreateNewContext (pDisplay,
									  renderFBConfig,
									  GLX_RGBA_TYPE,
									  NULL,
									  True);

	/* make sure to get a GLX window with the fb-config with render-support */
	glxWindow = glXCreateWindow (pDisplay, renderFBConfig, window, NULL);

	/* get rid of any window decorations */
	hintsAtom = XInternAtom (pDisplay, "_MOTIF_WM_HINTS", True);
	XGetWindowProperty (pDisplay,
						window,
						hintsAtom,
						0,
						sizeof (MotifWmHints) / sizeof (long),
						False,
						AnyPropertyType,
						&typeAtom,
						&iFormat,
						&ulItems,
						&ulBytesAfter,
						&pucData);

	newHints.flags = MWM_HINTS_DECORATIONS;
	newHints.decorations = 0;

	XChangeProperty (pDisplay,
					 window,
					 hintsAtom,
					 hintsAtom,
					 32,
					 PropModeReplace,
					 (unsigned char *) &newHints,
					 sizeof (MotifWmHints) / sizeof (long));

	/* show the window on the display... */
	XMapWindow (pDisplay, window);

	/* ... and wait for it to appear */
	XIfEvent (pDisplay, &event, wait_for_notify, (XPointer) window);

	/* ensure that our GL-context is the one we'll draw to */
	glXMakeContextCurrent (pDisplay, glxWindow, glxWindow, glxContext);

	/* freeglut needs this, we later use glutSolidSphere() in draw_gl() */
	glutInit (&argc, argv);

	/* our custom setup-functions for GL */
	init_gl (WIN_WIDTH, WIN_HEIGHT);

	while (bKeepGoing)
	{
		/* check for events _and_ don't block! */
		bGotEvent = XCheckWindowEvent (pDisplay,
									   window,
									   StructureNotifyMask |
									   ButtonPressMask |
									   ButtonReleaseMask |
									   KeyPressMask |
									   PointerMotionMask,
									   &event);

		/* in case of an event handle it... */
		if (bGotEvent)
		{
			switch (event.type)
			{
				/* only test for the ESC-key and exit */
				case KeyPress :
					if (XLookupKeysym (&event.xkey, 0) == XK_Escape)
						bKeepGoing = False;
				break;

				/* some mouse button was pressed */
				case ButtonPress:
					if (event.xbutton.button == 1)
						bLMBPressed = True;
				break;

				/* some mouse button was released */
				case ButtonRelease:
					if (event.xbutton.button == 1)
						bLMBPressed = False;
				break;

				/* our window was moved or resized */
				case ConfigureNotify :
					resize_gl (event.xconfigure.width, event.xconfigure.height);
				break;

				/* move the window on mouse-motion */
				case MotionNotify :
					/* still need to finish this */
					/*if (bLMBPressed)
						XMoveWindow (pDisplay,
									 window,
									 event.xmotion.x_root,
									 event.xmotion.y_root);*/
				break;
			}
		}
		/* ... otherwise just draw/animate our sphere and wait a bit */
		else
		{
			g_fAngleZ += 1.0f;
			draw_gl (pDisplay, glxWindow, iWhichGimmick);
			usleep (50000);
		}
	}

	/* clean up */
	XUnmapWindow (pDisplay, window);
	XDestroyWindow (pDisplay, window);
	XCloseDisplay (pDisplay);

	return 0;
}

