/*
 * sedwm: a tiny window manager with virtual screens and some bugs.
 * Hey, this is just a hack, you surely don't need it (but I do,
 * fvwm is bloated).
 * http://sed.free.fr
 * Released in the public domain.
 * Known problems:
 *   - the 1px border of the screen is unavailable, so if you click to move
 *     a window there it won't work. The click will be caught by
 *     the [left/right/top/bottom]_border windows. The focus won't go
 *     to you window as well. Hey, we could unmap the border window when
 *     it is of no use!
 *   - a lot of communication is done with the X server, much too much
 *   - a resize/remove eats a hell lot of CPU because it's done
 *     interactively instead of preview and done only when button released
 *     (but it would be much more code to do it the efficient way)
 *   - the X errors are not handled properly. We abandon the current action
 *     but the Xlib may well report an error from a previous action so
 *     maybe we just should ignore errors and simply reset the state
 *     (mostly mousetype) between events.
 * TODO:
 * - grab keys to move/resize/raise/lower window (but hell, too much
 *   state to handle)
 * - local store of windows IDs to avoid some server message exchange
 * - change cursor shape when move/resize?
 * - use 'info' to configure sedwm, like releasing grabbed keys for example
 * - unmap unused border windows (for example for virt screen 1x1 no need
 *   for top and left border)
 */

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

/* I need a global and a function to handle errors */
/* errors are unavoidable because of the asynchronous nature of X.
 * You may handle an event dealing with a resource that does
 * not exist anymore when you handle the event, so we have
 * to catch them up
 */
/* we also should catch IO errors, but hey, I don't use this
 * stuff over the Internet, only with an unix socket. There should
 * be not too much problems.
 */
int is_error;
int on_error(Display *d, XErrorEvent *e)
{
  fprintf(stderr, "X error: %d\n", e->error_code);
  fprintf(stderr, "    operation failed: %d\n", e->request_code);
  fprintf(stderr, "    resource ID: %lu (0x%lx)\n",
          e->resourceid, e->resourceid);
  is_error = 1;
  /* the spec (xlib) says return code is ignored, so let's return PI
   * (yeah PI is 0x314, believe me!)
   */
  return 0x314;
}

/* a little macro to manage errors */
/* maybe we should empty the events' queue, do some XFlush or something
 * like that, no?
 */
#define ERR if (is_error) { mousex = -1; mousetype = 0; is_error = 0; break; }

#define INFO \
  do { \
    char c[64]; \
    sprintf(c, "%dx%d", posx+1, posy+1); \
    XClearWindow(d, info); \
    XDrawString(d, info, gc, 5, 25, c, strlen(c)); \
  } while (0)

int main(void)
{
  Atom wm_s0;
  Window sel_window;
  unsigned int width;
  unsigned int height;
  Window left_border;
  Window right_border;
  Window top_border;
  Window bottom_border;
  Window info;
  /* the current displayed virtual screen */
  int posx = 0;
  int posy = 0;
  /* the size of the virtual display, here 5x5 */
  int posw = 5;
  int posh = 5;
  /* the pos of mouse when pressing button */
  int mousex = -1;
  int mousey;
  /* pos of window when clicking it */
  int windowx;
  int windowy;
  /* mousetype set when pressing button and not released yet
   * mousetype is at 0 when no button pressed
   * 0 : nothing
   * 1 : top of window (to move/raise)
   * 2 : not top (to resize)
   */
  int mousetype = 0;
  /* colors and GC */
  XColor cols[3];
  GC gc;

  Display *d = XOpenDisplay(0);
  if (!d) {
    fprintf(stderr, "X not there\n");
    return 1;
  }

  /* is there already a wm? */
  {
    Atom  t;
    t = XInternAtom(d, "WM_S0", True);
    if (t != None) {
      Window w = XGetSelectionOwner(d, t);
      if (w != None) {
        fprintf(stderr, "A window manager is there, aborting\n");
        return 1;
      }
    }
  }

  /* let's register an atom and acquire the selection */
  wm_s0 = XInternAtom(d, "WM_S0", False);
  if (wm_s0 == None) {
    fprintf(stderr, "Error registering WM_S0\n");
    return 1;
  }
  sel_window = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1,
                                   0, 0, 0);

  /* to acquire the selection, we must have a valid timestamp */
  {
    XSetWindowAttributes a;
    XEvent ev;
    Time t;
    Window w;
    /* tell X11 we want to be notified of a property change */
    a.event_mask = PropertyChangeMask;
    XChangeWindowAttributes(d, sel_window, CWEventMask, &a);
    /* dummy change property to get a timestamp */
    XChangeProperty(d, sel_window, XA_WM_CLASS, XA_STRING, 8, PropModeAppend,
                    NULL, 0);
    /* now wait the event back */
    XWindowEvent(d, sel_window, PropertyChangeMask, &ev);
    t = ev.xproperty.time;
    /* don't give a shit anymore to property change */
    a.event_mask = NoEventMask;
    XChangeWindowAttributes(d, sel_window, CWEventMask, &a);
    /* acquire selection */
    XSetSelectionOwner(d, wm_s0, sel_window, t);
    /* check we get it */
    w = XGetSelectionOwner(d, wm_s0);
    if (w != sel_window) {
      fprintf(stderr, "error getting selection\n");
      return 1;
    }
  }

  /* manage colors & GC */
  /* three colors used:
   * cols[0] background of win when active
   * cols[1] background of win when inactive
   * cols[2] color of text and close button
   */
  cols[0].red = 0x9b00;
  cols[0].green = 0x8e00;
  cols[0].blue = 0xcf00;

  cols[1].red = 0x6b00;
  cols[1].green = 0x5e00;
  cols[1].blue = 0x9f00;

  cols[2].red = 0x2b00;
  cols[2].green = 0x1e00;
  cols[2].blue = 0x5f00;

  if (!XAllocColor(d, DefaultColormap(d, 0), &cols[0]) ||
      !XAllocColor(d, DefaultColormap(d, 0), &cols[1]) ||
      !XAllocColor(d, DefaultColormap(d, 0), &cols[2])) {
    fprintf(stderr, "cannot allocate colors\n");
    return 1;
  }

  gc = XCreateGC(d, DefaultRootWindow(d), 0, NULL);
  XCopyGC(d, DefaultGC(d, 0), -1L, gc);
  {
     XGCValues v;
     v.foreground = cols[2].pixel;
     XChangeGC(d, gc, GCForeground, &v);
  }

  /* get width and height of screen */
  {
    Window ret;
    int dum;
    unsigned int dummy;
    XGetGeometry(d, DefaultRootWindow(d), &ret, &dum, &dum,
                 &width, &height, &dummy, &dummy);
    fprintf(stderr, "screen width=%u height=%u\n", width, height);
  }

  /* create border windows to "switch" virtual screens */
  {
    /* left border */
    left_border = XCreateWindow(d, DefaultRootWindow(d), 0, 0,
                                1, height, 0, 0, InputOnly,
                                DefaultVisual(d, 0), 0, NULL);
    XMapWindow(d, left_border);

    /* right border */
    right_border = XCreateWindow(d, DefaultRootWindow(d), width-1, 0,
                                 1, height, 0, 0, InputOnly,
                                 DefaultVisual(d, 0), 0, NULL);
    XMapWindow(d, right_border);

    /* top border */
    top_border = XCreateWindow(d, DefaultRootWindow(d), 1, 0,
                               width-2, 1, 0, 0, InputOnly,
                               DefaultVisual(d, 0), 0, NULL);
    XMapWindow(d, top_border);

    /* bottom border */
    bottom_border = XCreateWindow(d, DefaultRootWindow(d), 1, height-1,
                                  width-2, 1, 0, 0, InputOnly,
                                  DefaultVisual(d, 0), 0, NULL);
    XMapWindow(d, bottom_border);

    /* Select Inputs for those, we only care about enterwindow */
    XSelectInput(d, left_border, EnterWindowMask);
    XSelectInput(d, right_border, EnterWindowMask);
    XSelectInput(d, top_border, EnterWindowMask);
    XSelectInput(d, bottom_border, EnterWindowMask);
  }

  /* create the info window */
  info = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, height-30, 300, 30,
                             0, 0, 0);
  XMapWindow(d, info);
  XLowerWindow(d, info);

  /* we want to know when the root has a new child */
  /* no need to give KeypressMask, the grab doesn't care
   * of that
   */
  {
    XSelectInput(d, DefaultRootWindow(d), SubstructureRedirectMask);
  }

  /* let's grab our keys */
  /* I don't know what value pass for the last 3 args, False, Async, Async
   * seems to work
   */
  /* alt+F1 to launch an xterm */
  XGrabKey(d, XKeysymToKeycode(d, XK_F1), Mod1Mask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  /* ctrl+left/right/up/down to change virtual screen */
  XGrabKey(d, XKeysymToKeycode(d, XK_Left), ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Right), ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Up), ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Down), ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  /* alt+shift+left/right/up/down to move the mouse a lot */
  XGrabKey(d, XKeysymToKeycode(d, XK_Left), ShiftMask|Mod1Mask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Right), ShiftMask|Mod1Mask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Up), ShiftMask|Mod1Mask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Down), ShiftMask|Mod1Mask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  /* control+shift+left/right/up/down to move the mouse a bit */
  XGrabKey(d, XKeysymToKeycode(d, XK_Left), ShiftMask|ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Right), ShiftMask|ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Up), ShiftMask|ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);
  XGrabKey(d, XKeysymToKeycode(d, XK_Down), ShiftMask|ControlMask,
           DefaultRootWindow(d), False, GrabModeAsync, GrabModeAsync);

  /* the root wants the focus (well, I am not sure) */
  XSetInputFocus(d, PointerRoot, RevertToNone, CurrentTime);

  /* from now on we may get errors (imagine a client creates a
   * window but destroys it so quick that any request on it
   * will fail.
   * Let's register an error handler that will just set a variable
   * to 1 and after a call to an XLib function we just test this
   * variable and do nothing if it is 1.
   * So no need for XFlush or whatever and the code remains
   * quiet clean (IMO).
   */
  XSetErrorHandler(on_error);

  /* we are the boss now, let's wait for some children of the root
   * and well, do everything
   */
  while (1) {
    XEvent ev;
    XNextEvent(d, &ev);
    /* may XNextEvent throw an error? if yes, what happens when
     * we come here? is the event valid?
     */
    if (is_error) is_error = 0;
    fprintf(stderr, "event %d %lu\n", ev.type, ev.xany.window);
    switch (ev.type) {
      case ConfigureRequest: {
        XWindowChanges c;
        fprintf(stderr, "configureRequest ");
        fprintf(stderr, "x=%d x=%d width=%d height=%d\n",
                ev.xconfigurerequest.x, ev.xconfigurerequest.y,
                ev.xconfigurerequest.width, ev.xconfigurerequest.height);
        c.width = ev.xconfigurerequest.width;
        c.height = ev.xconfigurerequest.height;
        c.border_width = 0;
        /* we want to set width and height only if the client ask for that */
        XConfigureWindow(d, ev.xconfigurerequest.window,
                         CWBorderWidth|
                         ((CWWidth|CWHeight)&ev.xconfigurerequest.value_mask),
                         &c);
        ERR;
        break;
      }
      case MapRequest: {
        unsigned int width;
        unsigned int height;
        Window ret;
        Window ws[5];
        int dum;
        unsigned int dummy;
        Window w;
        XWindowChanges c;
        fprintf(stderr, "mapRequest\n");
        XGetGeometry(d, ev.xmap.window, &ret, &dum, &dum,
                     &width, &height, &dummy, &dummy);
        ERR;
        /* force border to be 0 */
        c.border_width = 0;
        XConfigureWindow(d, ev.xmap.window, CWBorderWidth, &c);
        ERR;
        w = XCreateSimpleWindow(d, DefaultRootWindow(d),
                                0, 0, width+6, height+23, 0, 0, 0);
#define ERR2 if (is_error) goto delete_win
        ERR2;
        XDefineCursor(d, w, XCreateFontCursor(d, XC_left_ptr));
        ERR2;
        XSetWindowBackground(d, w, cols[1].pixel);
        ERR2;
        XReparentWindow(d, ev.xmap.window, w, 3, 20);
        ERR2;
        /* we need enter/leave, mouse press/release, mouse button 1/3 motion
         * and expose do display the name of the window
         * substructurenotify is also required to track the subwindow
         * changes
         */
        XSelectInput(d, w, EnterWindowMask|LeaveWindowMask|
                     ButtonPressMask|ButtonReleaseMask|Button1MotionMask|
                     Button3MotionMask|
                     ExposureMask|SubstructureNotifyMask);
        ERR2;
        /* we must track window's name's */
        XSelectInput(d, ev.xmap.window, PropertyChangeMask);
        ERR2;
        XMapWindow(d, w);
        ERR2;
        XMapWindow(d, ev.xmap.window);
        ERR2;
        /* we want border windows to be always on top */
        ws[0] = left_border;
        ws[1] = right_border;
        ws[2] = top_border;
        ws[3] = bottom_border;
        ws[4] = w;
        XRestackWindows(d, ws, 5);
        ERR2;
        break;
delete_win:
        /* well, I am not sure if it's the right thing to do,
         * but let's destroy the window for now
         */
        XDestroyWindow(d, w);
        ERR;
        break;
#undef ERR2
      }
      case EnterNotify: {
        Window ret;
        Window *children;
        unsigned int n;
        unsigned int i;
        if (ev.xcrossing.detail == NotifyInferior)
          /* not a real enter since we come from down in hierarchy */
          break;
        fprintf(stderr, "enterNotify %lu\n", ev.xcrossing.window);
        /* XQueryTree is a bit barbarian, we could keep a list
         * of current windows
         */
        if (ev.xcrossing.window == left_border) {
          XWindowAttributes a;
          if (posx == 0) break;
          posx--;
          XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
          if (is_error && n) XFree(children);
          ERR;
          for (i = 0; i < n; i++) {
            if (children[i] == left_border ||
                children[i] == right_border ||
                children[i] == top_border ||
                children[i] == bottom_border ||
                children[i] == info) continue;
            XGetWindowAttributes(d, children[i], &a);
            if (is_error && n) XFree(children);
            ERR;
            XMoveWindow(d, children[i], a.x+width, a.y);
            if (is_error && n) XFree(children);
            ERR;
          }
          INFO;
          if (n) XFree(children);
          XWarpPointer(d, None, None, 0, 0, 0, 0, width-2, 0);
          ERR;
        } else if (ev.xcrossing.window == right_border) {
          XWindowAttributes a;
          if (posx == posw-1) break;
          posx++;
          XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
          if (is_error && n) XFree(children);
          ERR;
          for (i = 0; i < n; i++) {
            if (children[i] == left_border ||
                children[i] == right_border ||
                children[i] == top_border ||
                children[i] == bottom_border ||
                children[i] == info) continue;
            XGetWindowAttributes(d, children[i], &a);
            if (is_error && n) XFree(children);
            ERR;
            XMoveWindow(d, children[i], a.x-width, a.y);
            if (is_error && n) XFree(children);
            ERR;
          }
          INFO;
          if (n) XFree(children);
          XWarpPointer(d, None, None, 0, 0, 0, 0, -(width-2), 0);
          ERR;
        } else if (ev.xcrossing.window == top_border) {
          XWindowAttributes a;
          if (posy == 0) break;
          posy--;
          XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
          if (is_error && n) XFree(children);
          ERR;
          for (i = 0; i < n; i++) {
            if (children[i] == left_border ||
                children[i] == right_border ||
                children[i] == top_border ||
                children[i] == bottom_border ||
                children[i] == info) continue;
            XGetWindowAttributes(d, children[i], &a);
            if (is_error && n) XFree(children);
            ERR;
            XMoveWindow(d, children[i], a.x, a.y+height);
            if (is_error && n) XFree(children);
            ERR;
          }
          INFO;
          if (n) XFree(children);
          XWarpPointer(d, None, None, 0, 0, 0, 0, 0, height-2);
          ERR;
        } else if (ev.xcrossing.window == bottom_border) {
          XWindowAttributes a;
          if (posy == posh-1) break;
          posy++;
          XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
          if (is_error && n) XFree(children);
          ERR;
          for (i = 0; i < n; i++) {
            if (children[i] == left_border ||
                children[i] == right_border ||
                children[i] == top_border ||
                children[i] == bottom_border ||
                children[i] == info) continue;
            XGetWindowAttributes(d, children[i], &a);
            if (is_error && n) XFree(children);
            ERR;
            XMoveWindow(d, children[i], a.x, a.y-height);
            if (is_error && n) XFree(children);
            ERR;
          }
          INFO;
          if (n) XFree(children);
          XWarpPointer(d, None, None, 0, 0, 0, 0, 0, -(height-2));
          ERR;
        } else {
          XSetWindowBackground(d, ev.xcrossing.window, cols[0].pixel);
          ERR;
          XClearArea(d, ev.xcrossing.window, 0, 0, 0, 0, True);
          ERR;
          /* Set the focus to the kid */
          {
            Window ret;
            Window *children;
            unsigned int n;
            XQueryTree(d, ev.xcrossing.window, &ret, &ret, &children, &n);
            if (!n) break;
            /* The spec says not to use CurrentTime. But it does not
             * work the way I want if you don't use it, so here I am.
             * Fuck the spec.
             */
            XSetInputFocus(d, children[0], RevertToParent, CurrentTime);
            XFree(children);
            ERR;
          }
        }
        break;
      }
      case LeaveNotify: {
        if (ev.xcrossing.detail == NotifyInferior)
          /* not a real leave since we go down in hierarchy */
          break;
        fprintf(stderr, "leaveNotify %lu\n", ev.xcrossing.window);
        XSetWindowBackground(d, ev.xcrossing.window, cols[1].pixel);
        ERR;
        XClearArea(d, ev.xcrossing.window, 0, 0, 0, 0, True);
        ERR;
        /* no more focus (the spec says we shouldn't pass RevertToNone
         * or CurrentTime or abandon focus or whatever, but hey, I am
         * used to this behavior. When the pointer is no more in the
         * window, no more focus. And that's it. So, well, fuck the
         * spec. It's bloated anyway. And ununderstandable. And bad
         * because no example. And also because one information is
         * spread across various files. But what to expect from private
         * companies' coders, huh?) (Yes, X is from private companies.
         * And yes, it's a true shit.) (One day we should rewrite a
         * simpler and cleaner stuff.)
         */
        XSetInputFocus(d, PointerRoot, RevertToNone, CurrentTime);
        ERR;
        break;
      }
      case ButtonPress: {
        Window child;
        Atom *atoms;
        Atom delete;
        int n;
        /* are we in the little "window" top left? */
        if (ev.xbutton.x >= 5 && ev.xbutton.x <= 5 + 10 &&
            ev.xbutton.y >= 5 && ev.xbutton.y <= 5 + 10) {
          /* only button 1 does something */
          if (ev.xbutton.button != Button1) break;
          /* has the child window the WM_DELETE property? */
          {
            /* get the child */
            Window ret;
            Window *children;
            unsigned int n;
            XQueryTree(d, ev.xmotion.window, &ret, &ret, &children, &n);
            if (is_error && n) XFree(children);
            ERR;
            if (!n) break;
            child = children[0];
            XFree(children);
          }
          delete = XInternAtom(d, "WM_DELETE_WINDOW", True);
          ERR;
          if (!XGetWMProtocols(d, child, &atoms, &n)) n = 0;
          ERR;
          if (delete != None) {
            int i;
            XEvent ev;
            for (i = 0; i < n; i++)
              if (atoms[i] == delete)
                /* WM_DELETE_WINDOW found */
                break;
            if (i == n) goto no_delete;
            /* yes there is a WM_DELETE_WINDOW, let's send the message */
            ev.xclient.type = ClientMessage;
            ev.xclient.window = child;
            ev.xclient.message_type = XInternAtom(d, "WM_PROTOCOLS", True);
            ev.xclient.format = 32;
            ev.xclient.data.l[0] = delete;
            ev.xclient.data.l[1] = CurrentTime;
            XSendEvent(d, child, False, 0, &ev);
            ERR;
            break;
          }
no_delete:
          /* no WM_DELETE, what to do? let's kill the client */
          XKillClient(d, child);
          ERR;
          break;
        }
        /* button 3 is to lower */
        if (ev.xbutton.button == Button3 && ev.xbutton.y <= 20) {
          XLowerWindow(d, ev.xbutton.window);
          ERR;
          /* info window always on  bottom */
          XLowerWindow(d, info);
          break;
        }
        /* button 3 may move if y > 20 */
        if (ev.xbutton.button == Button3 && ev.xbutton.y > 20) {
          mousex = ev.xbutton.x_root;
          mousey = ev.xbutton.y_root;
          {
            XWindowAttributes a;
            /* get the position of the window */
            XGetWindowAttributes(d, ev.xbutton.window, &a);
            ERR;
            windowx = a.x;
            windowy = a.y;
          }
          mousetype = 1;
          break;
        }
        /* if not button 1 do nothing */
        if (ev.xbutton.button != Button1) break;
        mousex = ev.xbutton.x_root;
        mousey = ev.xbutton.y_root;
        {
          XWindowAttributes a;
          /* get the position of the window */
          XGetWindowAttributes(d, ev.xbutton.window, &a);
          ERR;
          windowx = a.x;
          windowy = a.y;
        }
        /* set mousetype */
        /* top of window? */
        if (ev.xbutton.y <= 20)
          mousetype = 1;
        else
          mousetype = 2;
        break;
      }
      case ButtonRelease: {
        if (mousex == -1) continue; /* might be useless check... */
        if (mousetype == 1) {
          /* move or raise? raise if mouse didn't move much */
          if (abs(ev.xbutton.x_root-mousex) < 6 &&
              abs(ev.xbutton.y_root-mousey) < 6) {
            /* raise */
            /* we want border windows to be always on top */
            Window ws[5];
            ws[0] = left_border;
            ws[1] = right_border;
            ws[2] = top_border;
            ws[3] = bottom_border;
            ws[4] = ev.xbutton.window;
            XRestackWindows(d, ws, 5);
            ERR;
          } else {
            /* nothing to do, the move was done in Motion event */
          }
        } else if (mousetype == 2) {
          /* resize */
          /* nothing to do, all in motion event */
        }
        mousex = -1;
        mousetype = 0;
        break;
      }
      case MotionNotify: {
        /* let's compress events */
        while (XCheckWindowEvent(d, ev.xmotion.window,
                                 Button1MotionMask|Button3MotionMask, &ev))
          /* what to do in case of X error? is the event valid or what? */
          /* let's simply ignore it and do as if all was alright */
          if (is_error) is_error = 0;
        if (mousetype == 1) {
          /* move it */
          XMoveWindow(d, ev.xmotion.window, windowx+ev.xmotion.x_root-mousex,
                      windowy+ev.xmotion.y_root-mousey);
          ERR;
        } else if (mousetype == 2) {
          /* resize it */
          XWindowAttributes a;
          int w;
          int h;
          XGetWindowAttributes(d, ev.xmotion.window, &a);
          ERR;
          w = ev.xmotion.x_root - a.x + 1;
          h = ev.xmotion.y_root - a.y + 1;
          /* minimum width and height */
          if (w < 10) w = 10;
          if (h < 30) h = 30;
          /* resize the top window */
          XResizeWindow(d, ev.xmotion.window, w, h);
          ERR;
          /* we need to resize to child as well */
          {
            Window ret;
            Window *children;
            unsigned int n;
            XQueryTree(d, ev.xmotion.window, &ret, &ret, &children, &n);
            if (is_error && n) XFree(children);
            ERR;
            if (n) {
              XResizeWindow(d, children[0], w - 6, h - 23);
              XFree(children);
              ERR;
            }
          }
        }
        break;
      }
      case Expose: {
        /* barbarian */
        fprintf(stderr, "expose count=%d\n", ev.xexpose.count);
        if (ev.xexpose.count) break;
        /* draw the close 'button' */
        XFillRectangle(d, ev.xexpose.window, gc, 5, 5, 10, 10);
        ERR;
        /* get window name of child and display it */
        {
          Window ret;
          Window *children;
          unsigned int n;
          XQueryTree(d, ev.xmotion.window, &ret, &ret, &children, &n);
          if (is_error && n) XFree(children);
          ERR;
          if (n) {
            char *c;
            if (XFetchName(d, children[0], &c) && c) {
              if (is_error && n) XFree(children);
              if (is_error && c) XFree(c);
              ERR;
              XDrawString(d, ev.xexpose.window, gc, 20, 15, c, strlen(c));
              if (is_error && n) XFree(children);
              if (is_error && c) XFree(c);
              ERR;
              fprintf(stderr, "expose window name=%s\n", c);
              XFree(c);
            }
            XFree(children);
            ERR;
          }
        }
        fprintf(stderr, "expose done, is there a name?\n");
        break;
      }
      case PropertyNotify: {
        /* did the name change? if yes clear the title,
         * the 'expose' event will catch the redraw
         */
        Window ret, dad;
        Window *children;
        unsigned int n;
        if (ev.xproperty.atom != XA_WM_NAME) break;
        fprintf(stderr, "propertyNotify name change\n");
        XQueryTree(d, ev.xmotion.window, &ret, &dad, &children, &n);
        if (n) XFree(children);
        ERR;
        XClearArea(d, dad, 0, 0, 0, 0, True);
        ERR;
        break;
      }
      case DestroyNotify: {
        /* we destroy the dad (the DestroyNotify is sent
         * because we have a SubstructureNotifyMask on the
         * parent of the just destroyed window so the parent ID
         * is in 'event')
         */
        XDestroyWindow(d, ev.xdestroywindow.event);
        ERR;
        break;
      }
      case UnmapNotify: {
        /* same as for DestroyNotify */
        XUnmapWindow(d, ev.xunmap.event);
        ERR;
        break;
      }
      case MapNotify: {
        /* we receive that as soon as a window is mapped
         * this event may be sent in two cases:
         * 1 - the client unmapped the window by itself
         *     and remaps it later, in which case we must
         *     remap the parent
         * 2 - after we call map on the window, in which
         *     case we should do nothing but it does not
         *     hurt to map again (we send a message which
         *     costs a bit, but hey, not that much)
         */
        /* the spec says to ignore override_redirect events */
        if (ev.xmap.override_redirect) break;
        XMapWindow(d, ev.xunmap.event);
        ERR;
        break;
      }
      case ConfigureNotify: {
        /* let's do it the barbarian way:
         * we resize the parent
         * hopefully it is correct
         */
        if (ev.xconfigure.override_redirect) break;
        /* do nothing if we are resizing */
        if (mousetype == 2) break;
        XResizeWindow(d, ev.xconfigure.event, ev.xconfigure.width + 6,
                      ev.xconfigure.height + 23);
        break;
      }
      case KeyPress: {
        KeySym k = XLookupKeysym(&ev.xkey, 0);
        /* our keys */
        switch (k) {
          case XK_F1: {
            int i;
            /* let's launch an xterm */
            /* fork twice for the zombie/orphan stuff */
            i = fork();
            if (i == -1) break;
            if (i == 0) {
              if (fork()) exit(0);
              /* close all fd (up to 10 should be ok, no? Yeah, hacker way */
              for (i=0; i<10; i++) close(i);
              /* let's exec the xterm */
              execlp("xterm", "xterm", "-sb", "-sl", "500", "-j", "-ls", "-rv",
                     NULL);
              /* error if we are here */
              exit(1);
            }
            /* let's wait for the child */
            waitpid(i, NULL, 0);
            break;
          }
          case XK_Left:
            if ((ev.xkey.state & ControlMask) && (ev.xkey.state & ShiftMask)) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, -10, 0);
            } else if (ev.xkey.state & ControlMask) {
              Window ret;
              Window *children;
              unsigned int n;
              int i;
              XWindowAttributes a;
              if (posx == 0) break;
              posx--;
              XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
              if (is_error && n) XFree(children);
              ERR;
              for (i = 0; i < n; i++) {
                if (children[i] == left_border ||
                    children[i] == right_border ||
                    children[i] == top_border ||
                    children[i] == bottom_border ||
                    children[i] == info) continue;
                XGetWindowAttributes(d, children[i], &a);
                if (is_error && n) XFree(children);
                ERR;
                XMoveWindow(d, children[i], a.x+width, a.y);
                if (is_error && n) XFree(children);
                ERR;
              }
              INFO;
              if (n) XFree(children);
            } else if (ev.xkey.state & Mod1Mask) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, -100, 0);
            }
            break;
          case XK_Right:
            if ((ev.xkey.state & ControlMask) && (ev.xkey.state & ShiftMask)) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 10, 0);
            } else if (ev.xkey.state & ControlMask) {
              Window ret;
              Window *children;
              unsigned int n;
              int i;
              XWindowAttributes a;
              if (posx == posw-1) break;
              posx++;
              XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
              if (is_error && n) XFree(children);
              ERR;
              for (i = 0; i < n; i++) {
                if (children[i] == left_border ||
                    children[i] == right_border ||
                    children[i] == top_border ||
                    children[i] == bottom_border ||
                    children[i] == info) continue;
                XGetWindowAttributes(d, children[i], &a);
                if (is_error && n) XFree(children);
                ERR;
                XMoveWindow(d, children[i], a.x-width, a.y);
                if (is_error && n) XFree(children);
                ERR;
              }
              INFO;
              if (n) XFree(children);
            } else if (ev.xkey.state & Mod1Mask) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 100, 0);
            }
            break;
          case XK_Up:
            if ((ev.xkey.state & ControlMask) && (ev.xkey.state & ShiftMask)) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 0, -10);
            } else if (ev.xkey.state & ControlMask) {
              Window ret;
              Window *children;
              unsigned int n;
              int i;
              XWindowAttributes a;
              if (posy == 0) break;
              posy--;
              XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
              if (is_error && n) XFree(children);
              ERR;
              for (i = 0; i < n; i++) {
                if (children[i] == left_border ||
                    children[i] == right_border ||
                    children[i] == top_border ||
                    children[i] == bottom_border ||
                    children[i] == info) continue;
                XGetWindowAttributes(d, children[i], &a);
                if (is_error && n) XFree(children);
                ERR;
                XMoveWindow(d, children[i], a.x, a.y+height);
                if (is_error && n) XFree(children);
                ERR;
              }
              INFO;
              if (n) XFree(children);
            } else if (ev.xkey.state & Mod1Mask) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 0, -100);
            }
            break;
          case XK_Down:
            if ((ev.xkey.state & ControlMask) && (ev.xkey.state & ShiftMask)) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 0, 10);
            } else if (ev.xkey.state & ControlMask) {
              Window ret;
              Window *children;
              unsigned int n;
              int i;
              XWindowAttributes a;
              if (posy == posh-1) break;
              posy++;
              XQueryTree(d, DefaultRootWindow(d), &ret, &ret, &children, &n);
              if (is_error && n) XFree(children);
              ERR;
              for (i = 0; i < n; i++) {
                if (children[i] == left_border ||
                    children[i] == right_border ||
                    children[i] == top_border ||
                    children[i] == bottom_border ||
                    children[i] == info) continue;
                XGetWindowAttributes(d, children[i], &a);
                if (is_error && n) XFree(children);
                ERR;
                XMoveWindow(d, children[i], a.x, a.y-height);
                if (is_error && n) XFree(children);
                ERR;
              }
              INFO;
              if (n) XFree(children);
            } else if (ev.xkey.state & Mod1Mask) {
              XWarpPointer(d, None, None, 0, 0, 0, 0, 0, 100);
            }
            break;
        } /* switch */
        break;
      }
      /* not handled stuff:
       * - CreateNotify: the spec (Xproto) says I should receive it
       *                 the X server I tested with doesn't seem to
       *                 send that
       * - ReparentNotify: I don't know if I should handle that
       * - ConfigureNotify: don't know if I should
       * - GravityNotify: don't know
       * - CirculateNotify: don't know
       * - CirculateRequest: don't know
       * - ClientMessage: maybe I should, no?
       * and some others but I don't know if they are required
       */
      default: fprintf(stderr, "unhandled event %d\n", ev.type); break;
    }
  }

  return 0;
}
