// gcc -Wall -ansi -pedantic grid-pointer.cc -o grid-pointer -L/usr/X11R6/lib -lX11 -lXtst -lstdc++

/*

  Window Configuration
    Geometry
      x, y, width, height

  Window Attributes

     background-pixmap
     background-pixel
     border-pixmap
     border-pixel

     bit-gravity
     win-gravity
     backing-store
     backing-planes
     backing-pixel
     save-under
     event-mask
     do-not-propagate-mask
     override-redirect

     colormap

     cursor

  To change any combination of them: XChangeWindowAttributes

  Specific functions:

    XSetWindowBackground
    XSetWindowBackgroundPixmap
    XSetWindowBorder
    XSetWindowBorderPixmap

    XSetWindowColormap

    XDefineCursor
    XUndefineCursor

  Window properties

*/

#include <X11/Xlib.h> // Every Xlib program must include this
#include <X11/keysym.h>
#include <unistd.h>
#include <X11/extensions/XTest.h>



struct geometry {
  int x;
  int y;
  // X expects to place width and height values *into* unsigned ints
  unsigned int w;
  unsigned int h;
};
//
geometry start, last, current;


Window ws[8];
bool showing = false;
GC gc;



enum {CLICK, DBL_CLICK, DRAG_START, DRAG_END};
bool dragging = false;



void redraw(Display* dpy) {

  // X doesn't like 0 width or height. So don't do anything in that case.
  //   Consider unmapping in this case (can we still get key events?).
  if (current.w != 0 && current.h != 0) {

    // Reshape grid.
    //
    XMoveResizeWindow(dpy, ws[0], current.x + current.w / 3, current.y, 1, current.h);
    XMoveResizeWindow(dpy, ws[1], current.x + 2 * current.w / 3, current.y, 1, current.h);
    XMoveResizeWindow(dpy, ws[2], current.x, current.y + current.h / 3, current.w, 1);
    XMoveResizeWindow(dpy, ws[3], current.x, current.y + 2 * current.h / 3, current.w, 1);
    // The box.
    XMoveResizeWindow(dpy, ws[4], current.x, current.y, 1, current.h);
    XMoveResizeWindow(dpy, ws[5], current.x + current.w - 1, current.y, 1, current.h);
    XMoveResizeWindow(dpy, ws[6], current.x, current.y, current.w, 1);
    XMoveResizeWindow(dpy, ws[7], current.x, current.y + current.h - 1, current.w, 1);

    // If weren't showing grid, show now on top of all windows and note it's now showing.
    //
    if (!showing) {
      for (int i = 0; i < 8; ++i) {
        // Unmapping first brings it back on top of non-siblings as well
        //  (e.g. a menu we just popped up).
	//
	XMapRaised(dpy, ws[i]);
      }
      showing = true;
    }

    // Make the grid lines dotted.
    //   In X, must draw foreground after mapping, and redraw after moving.
    for (int i = 0; i < 4; ++i) {
      XDrawLine(dpy, ws[i], gc, 0, 0, 0, current.h);
      XDrawLine(dpy, ws[i], gc, 0, 0, current.w, 0);
    }
  
    // Position pointer in middle of grid.
    if (!dragging) {
      XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0,
		   current.x + current.w / 2, current.y + current.h / 2);
    }

  }

}


void reset(Display* dpy) {
  for (int i = 0; i < 8; ++i) {
    XUnmapWindow(dpy, ws[i]);
  }
  current = start;
  showing = false;
}

void click(Display* dpy, int button, int click_type) {

  XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0,
	       current.x + current.w / 2, current.y + current.h / 2);
  reset(dpy);
  if (click_type == DRAG_START) {
    XTestFakeButtonEvent(dpy, button, True, CurrentTime);
  } else if (click_type == DRAG_END) {
    XTestFakeButtonEvent(dpy, button, False, CurrentTime);
  } else {
    XTestFakeButtonEvent(dpy, button, True, CurrentTime);
    XTestFakeButtonEvent(dpy, button, False, CurrentTime);
    if (click_type == DBL_CLICK) {
      XTestFakeButtonEvent(dpy, button, True, CurrentTime);
      XTestFakeButtonEvent(dpy, button, False, CurrentTime);
    }
  }
  XFlush(dpy); // important so events don't get lost when we end program?

}


KeySym keysym(Display* dpy, XEvent e) {
  if (e.type == KeyPress) {
    XKeyPressedEvent* k = (XKeyPressedEvent*)&e;
    return XKeycodeToKeysym(dpy, k->keycode, 0);
  } else {
    return None;
  }
}


int main() {

  // enum {BL, BM, BR, ML, MM, MR, UL, UM, UR, B1, B2, B3, ABORT, UNDO}

  /*
  KeySym ks[] = {XK_m, XK_comma, XK_period, XK_j, XK_k, XK_l, XK_u, XK_i, XK_o,
		 XK_space, XK_apostrophe, XK_Return, XK_minus, XK_semicolon};
  */

  KeySym ks[] = {XK_KP_End, XK_KP_Down, XK_KP_Page_Down,
  		 XK_KP_Left, XK_KP_Begin, XK_KP_Right,
  		 XK_KP_Home, XK_KP_Up, XK_KP_Page_Up,
    		 XK_KP_Divide, XK_KP_Multiply, XK_KP_Subtract,
                 XK_KP_Add, XK_KP_Enter};


  // Use default screen of display specified by DISPLAY environment variable.
  //
  Display* dpy = XOpenDisplay(NULL); // NULL: use DISPLAY
  int screen = DefaultScreen(dpy);
  Window root = DefaultRootWindow(dpy);

  int blackColor = BlackPixel(dpy, screen);
  int whiteColor = WhitePixel(dpy, screen);

  start.x = 0;
  start.y = 0;
  start.w = DisplayWidth(dpy, screen);
  start.h = DisplayHeight(dpy, screen);

  /*
  Window focus_window;
  int revert_to;
  XGetInputFocus(dpy, &focus_window, &revert_to);

  Window dummy;
  XTranslateCoordinates(dpy, focus_window, root, 0, 0, &start.x, &start.y, &dummy);

  int dummy_int;
  unsigned int dummy_unsigned_int;
  XGetGeometry(dpy, focus_window, &dummy, &dummy_int, &dummy_int, &start.w, &start.h,
	       &dummy_unsigned_int, &dummy_unsigned_int);
  */

  last = current = start;

  // Eight black windows, no internal border.
  //   1 pixel wide with no internal border.
  //
  for (int i = 0; i <= 7; ++i) {
    ws[i] = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1,
				0, blackColor, blackColor);
  }

  // Set windows so they are unplaced and undecorated by wm.
  // Also indicate our interest in hearing when they are finished mapping.
  //
  for (int i = 0; i < 8; ++i) {

    // Not placed or decorated by wm.
    //
    XSetWindowAttributes att;
    att.override_redirect = True;
    XChangeWindowAttributes(dpy, ws[i], CWOverrideRedirect, &att);

    XSelectInput(dpy, ws[i], StructureNotifyMask);
    // XMapRaised(dpy, ws[i]);

  }

  // GC for drawing [in] grid [windows].
  //
  gc = XCreateGC(dpy, ws[0], 0, 0);
  XSetBackground(dpy, gc, blackColor);
  XSetForeground(dpy, gc, whiteColor);
  XSetLineAttributes(dpy, gc, 1, LineOnOffDash, CapButt, JoinMiter);

  // Wait until four (one for each window) maps have occurred.
  //
  //int maps = 0;
  //while (maps < 4) {
  //  XEvent e;
  //  XNextEvent(dpy, &e); // does an XFlush as well
  //  if (e.type == MapNotify) {
  //    ++maps;
  //  }
  //}

  // Care about KeyPresses, use one of the windows to grab all keyboard events.
  // (No longer care about StructureNotify events.)
  //
  //  XSelectInput(dpy, ws[0], KeyPressMask);
  //  XGrabKeyboard(dpy, ws[0], False, GrabModeAsync, GrabModeAsync, CurrentTime);

  //  XTestGrabControl(dpy, True);


  for (int i = 0; i < 14; ++i) {
    XGrabKey(dpy, XKeysymToKeycode(dpy, ks[i]), AnyModifier,
	     root, False, GrabModeAsync, GrabModeAsync);
  }

  redraw(dpy);

  while (true) {

    XEvent e;
    XNextEvent(dpy, &e);
    KeySym s = keysym(dpy, e);

    bool change = false;

    for (int i = 0; i <= 8; ++i) {
      if (s == ks[i]) {
	change = true;
	last = current;
      }
    }
    
    if (s == ks[0] || s == ks[1] || s == ks[2]) {
      current.y += 2 * current.h / 3;
    } else if (s == ks[3] || s == ks[4] || s == ks[5]) {
      current.y += current.h / 3;
    }
    if (s == ks[1] || s == ks[4] || s == ks[7]) {
      current.x += current.w / 3;
    } else if (s == ks[2] || s == ks[5] || s == ks[8]) {
      current.x += 2 * current.w / 3;
    }
    
    if (change) {

      current.w /= 3;
      current.h /= 3;

      redraw(dpy);

    } else if (s == ks[9] || s == ks[10] || s == ks[11] ) {

      int button = 1;
      if (s == ks[10]) {
	button = 2;
      } else if (s == ks[11]) {
	button = 3;
      }

      int click_type = CLICK;
      if (dragging) {
	click_type = DRAG_END;
	dragging = false;
      } else if (((XKeyEvent*)&e)->state & ShiftMask) {
	click_type = DBL_CLICK;
      } else if (((XKeyEvent*)&e)->state & Mod3Mask) {
	click_type = DRAG_START;
	dragging = true;
      }

      click(dpy, button, click_type);
      XSync(dpy, False);
      if (dragging) {
	showing = false;
	redraw(dpy);
      } else {
	return 0;
      }

    } else if (s == ks[12]) {
      if (showing) {
	reset(dpy);
	return 0;
      } else {
	redraw(dpy);
      }
    } else if (s == ks[13]) {
      current = last;
      redraw(dpy);
    }

  }

  return 1; // shouldn't get here!

}

