/**
 * Display/snoop the z/stencil/back/front buffers of another app's window.
 * Also, an example of the need for shared ancillary renderbuffers.
 *
 * Hint: use 'xwininfo' to get a window's ID.
 *
 * Brian Paul
 * 11 Oct 2007
 */

#define GL_GLEXT_PROTOTYPES

#include <GL/gl.h>
#include <GL/glx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/keysym.h>


#define Z_BUFFER 1
#define STENCIL_BUFFER 2
#define BACK_BUFFER 3
#define FRONT_BUFFER 4


static int Buffer = BACK_BUFFER;
static int WindowID = 0;
static const char *DisplayName = NULL;
static GLXContext Context = 0;
static int Width, Height;


/**
 * Grab the z/stencil/back/front image from the srcWin and display it
 * (possibly converted to grayscale) in the dstWin.
 */
static void
redraw(Display *dpy, Window srcWin, Window dstWin )
{
   GLubyte *image = malloc(Width * Height * 4);

   glXMakeCurrent(dpy, srcWin, Context);
   glPixelStorei(GL_PACK_ALIGNMENT, 1);
   if (Buffer == BACK_BUFFER) {
      glReadBuffer(GL_BACK);
      glReadPixels(0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, image);
   }
   else if (Buffer == FRONT_BUFFER) {
      glReadBuffer(GL_FRONT);
      glReadPixels(0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, image);
   }
   else if (Buffer == Z_BUFFER) {
      GLfloat *z = malloc(Width * Height * sizeof(GLfloat));
      int i;
      glReadPixels(0, 0, Width, Height, GL_DEPTH_COMPONENT, GL_FLOAT, z);
      for (i = 0; i < Width * Height; i++) {
         image[i*4+0] =
         image[i*4+1] =
         image[i*4+2] = (GLint) (255.0 * z[i]);
         image[i*4+3] = 255;
      }
      free(z);
   }
   else if (Buffer == STENCIL_BUFFER) {
      GLubyte *sten = malloc(Width * Height * sizeof(GLubyte));
      int i, min = 100, max = -1;
      float step;
      int sz;
      glGetIntegerv(GL_STENCIL_BITS, &sz);
      glReadPixels(0, 0, Width, Height,
                   GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, sten);
      /* find min/max for converting stencil to grayscale */
      for (i = 0; i < Width * Height; i++) {
         if (sten[i] < min)
            min = sten[i];
         if (sten[i] > max)
            max = sten[i];
      }
      if (min == max)
         step = 0;
      else
         step = 255.0 / (float) (max - min);
      for (i = 0; i < Width * Height; i++) {
         image[i*4+0] =
         image[i*4+1] =
         image[i*4+2] = (GLint) ((sten[i] - min) * step);
         image[i*4+3] = 255;
      }
      free(sten);
   }

   glXMakeCurrent(dpy, dstWin, Context);
   glWindowPos2iARB(0, 0);
   glDrawBuffer(GL_FRONT);
   glDrawPixels(Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, image);
   glFlush();

   free(image);
}


static void
set_window_title(Display *dpy, Window win, const char *title)
{
   XSizeHints sizehints;
   sizehints.flags = 0;
   XSetStandardProperties(dpy, win, title, title,
                          None, (char **)NULL, 0, &sizehints);
}


static Window
make_gl_window(Display *dpy, XVisualInfo *visinfo, int width, int height)
{
   int scrnum;
   XSetWindowAttributes attr;
   unsigned long mask;
   Window root;
   Window win;
   int x = 0, y = 0;
   char *name = NULL;

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

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

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

   /* 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);
   }

   return win;
}


static void
update_window_title(Display *dpy, Window win)
{
   char title[1000], *buf;

   switch (Buffer) {
   case Z_BUFFER:
      buf = "Z";
      break;
   case STENCIL_BUFFER:
      buf = "Stencil";
      break;
   case BACK_BUFFER:
      buf = "Back";
      break;
   case FRONT_BUFFER:
      buf = "Front";
      break;
   default:
      buf = "";
   }

   sprintf(title, "glxsnoop window 0x%x (%s buffer)", (int) WindowID, buf);

   set_window_title(dpy, win, title);
}


static void
keypress(Display *dpy, Window win, char key)
{
   switch (key) {
   case 27:
      /* escape */
      exit(0);
      break;
   case 's':
      Buffer = STENCIL_BUFFER;
      break;
   case 'z':
      Buffer = Z_BUFFER;
      break;
   case 'f':
      Buffer = FRONT_BUFFER;
      break;
   case 'b':
      Buffer = BACK_BUFFER;
      break;
   default:
      return;
   }

   update_window_title(dpy, win);
   redraw(dpy, WindowID, win);
}


static void
event_loop(Display *dpy, Window win)
{
   XEvent event;

   while (1) {
      XNextEvent( dpy, &event );

      switch (event.type) {
      case Expose:
         redraw(dpy, WindowID, win);
         break;
      case ConfigureNotify:
         /*resize( event.xconfigure.width, event.xconfigure.height );*/
         break;
      case KeyPress:
         {
            char buffer[10];
            int code;
            code = XLookupKeysym(&event.xkey, 0);
            if (code == XK_Left) {
            }
            else {
               XLookupString(&event.xkey, buffer, sizeof(buffer),
                             NULL, NULL);
               keypress(dpy, win, buffer[0]);
            }
         }
      default:
         /* nothing */
         ;
      }
   }
}


static VisualID
get_window_visualid(Display *dpy, Window win)
{
   XWindowAttributes attr;

   if (XGetWindowAttributes(dpy, win, &attr)) {
      return attr.visual->visualid;
   }
   else {
      return 0;
   }
}


static void
get_window_size(Display *dpy, Window win, int *w, int *h)
{
   XWindowAttributes attr;

   if (XGetWindowAttributes(dpy, win, &attr)) {
      *w = attr.width;
      *h = attr.height;
   }
   else {
      *w = *h = 0;
   }
}


static XVisualInfo *
visualid_to_visualinfo(Display *dpy, VisualID vid)
{
   XVisualInfo *vinfo, templ;
   long mask;
   int n;

   templ.visualid = vid;
   mask = VisualIDMask;

   vinfo = XGetVisualInfo(dpy, mask, &templ, &n);
   return vinfo;
}


static void
key_usage(void)
{
   printf("Keyboard:\n");
   printf("  z - display Z buffer\n");
   printf("  s - display stencil buffer\n");
   printf("  f - display front color buffer\n");
   printf("  b - display back buffer\n");
}


static void
usage(void)
{
   printf("Usage: glxsnoop [-display dpy] windowID\n");
   key_usage();
}


static void
parse_opts(int argc, char *argv[])
{
   int i;

   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "-h") == 0) {
         usage();
         exit(0);
      }
      else if (strcmp(argv[i], "-display") == 0) {
         DisplayName = argv[i + 1];
         i++;
      }
      else {
         if (argv[i][0] == '0' && argv[i][1] == 'x') {
            /* hex */
            WindowID = strtol(argv[i], NULL, 16);
         }
         else {
            WindowID = atoi(argv[i]);
         }
         break;
      }
   }

   if (!WindowID) {
      usage();
      exit(0);
   }
}


int
main( int argc, char *argv[] )
{
   Display *dpy;
   VisualID vid;
   XVisualInfo *visinfo;
   Window win;

   parse_opts(argc, argv);

   key_usage();

   dpy = XOpenDisplay(DisplayName);

   /* find the VisualID for the named window */
   vid = get_window_visualid(dpy, WindowID);
   get_window_size(dpy, WindowID, &Width, &Height);

   visinfo = visualid_to_visualinfo(dpy, vid);

   Context = glXCreateContext( dpy, visinfo, NULL, True );
   if (!Context) {
      printf("Error: glXCreateContext failed\n");
      exit(1);
   }

   win = make_gl_window(dpy, visinfo, Width, Height);
   XMapWindow(dpy, win);
   update_window_title(dpy, win);

   event_loop( dpy, win );

   return 0;
}
