/* treewm - an X11 window manager.
 * Copyright (c) 2001-2002 Thomas Jäger <TheHunter2000 at web dot de>
 * This code is released under the terms of the GNU GPL.
 * See the included file LICENSE for details.
 */

#include "desktop.h"
#include "manager.h"
#include "icon.h"
#include "uehandler.h"
#include "resmanager.h"
#include "clienttree.h"
#include "sceme.h"
#include "menu.h"
#include "MwmUtil.h"
#include "textdialog.h"
#include "action.h"
#include <unistd.h>
#include <X11/Xmd.h>
#include <X11/Xatom.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <X11/keysym.h>

Manager *man;

Atom wm_state, wm_change_state, wm_protos, wm_delete, wm_cmapwins, wm_client_leader,
     mwm_hints;




int handle_xerror(Display *dpy, XErrorEvent *e) {
  Client *c = ct ? ct->FindClient(e->resourceid, 0) : 0;

  if (e->error_code == BadAccess && e->resourceid == root) {
    fprintf(stderr,"root window unavailible (maybe another wm is running?)\n");
    exit(1);
  } else {
    char msg[80], req[80], number[80];
    XGetErrorText(dpy, e->error_code, msg, sizeof msg - 1);
    msg[79]='\0';
    snprintf(number, sizeof number - 1, "%d", e->request_code);
    number[79] = '\0';
    XGetErrorDatabaseText(dpy, "XRequest", number, "", req, sizeof(req) - 1);
    req[79]='\0';
    if (req[0] == '\0')
      sprintf(req, "<request-code-%d>", e->request_code);
    fprintf(stderr,"XERROR: %s(0x%x): %s\n", req, (unsigned int)e->resourceid, msg);

  }
#ifdef DEBUG
  fprintf(stderr,"\7");
#endif


//  if (c && c->parent)
//    c->parent->RemoveChild(c,R_WITHDRAW);
/*
 *  Here, a very interesting bug happens. If we call an X function during the
 *  processing of an event and an error occurs and we immediately delete the window
 *  we're working with, we cause a lot of shit
 *  therefore, we have to be _very_ careful

 *  it's just because of these crappy programs that change their titles before they
 *  kill themselves
*/
  if (e->error_code == BadWindow && c && c->parent)
    man->AddDeleteClient(c);
  return 0;
}

void Manager::AddDeleteClient(Client *c) {
  if (man->MustBeDeleted) {
    ClientList *cl = man->MustBeDeleted;
    for (;;cl=cl->next) {
      if (cl->client == c)
        break;
      if (!cl->next) {
        cl->next = new ClientList;
        cl = cl->next;
        cl->next = 0;
        cl->client = c;
        break;
      }
    }
  } else {
    man->MustBeDeleted = new ClientList;
    man->MustBeDeleted->client = c;
    man->MustBeDeleted->next = 0;
  }
}

void SigHandler(int signal) {
  switch (signal) {
    case SIGHUP:
//      man->Restart();
//      break;
    case SIGINT:
    case SIGTERM:
      man->Quit();
      exit(0);
      break;
    case SIGCHLD:
      wait(0);
      break;
  }
}


Manager::Manager(int argc, char **argv) {
  man = this;

  XSetWindowAttributes sattr;
  struct sigaction act;

  ct = NULL; // if an error happens, this value is used

  act.sa_handler = SigHandler;
  act.sa_flags = 0;
  sigaction(SIGTERM, &act, NULL);
  sigaction(SIGINT, &act, NULL);
  sigaction(SIGHUP, &act, NULL);
  sigaction(SIGCHLD, &act, NULL);

  int display = 0;
  int config = 0;
  for (int i=1;i!=argc;++i) {
    if (!strcmp(argv[i],"-display") && ++i != argc) {
      display = i;
      continue;
    }
    if (!strcmp(argv[i],"-f") && ++i != argc) {
      config = i;
      continue;
    }
    printf("usage: %s [-display displayname] [-f configfile]\n",argv[0]);
    exit(1);

  }
  dpy = XOpenDisplay(display ? argv[display] : 0);
  if (dpy) {
// thx to Volker Grabsch
    if (display)
      setenv("DISPLAY",argv[display],1);
  }
  else {
    fprintf(stderr,"can't open display %s!\n",display ? argv[display]: 0);
    exit(1);
  }


  XSetErrorHandler(handle_xerror);

  char filename[256];
  snprintf(filename,255,"%s/%s",getenv("HOME"),".cmdtreewm");
  filename[255] = '\0';
  mkfifo(filename,0600);
  fifo = open(filename,O_RDWR | O_NONBLOCK);
  if (!(fifo + 1))
    fprintf(stderr,"warning: couldn't create fifo (~/.cmdtreewm)");

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


  wm_state = XInternAtom(dpy, "WM_STATE", false);
  wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", false);
  wm_protos = XInternAtom(dpy, "WM_PROTOCOLS", false);
  wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", false);
  wm_cmapwins = XInternAtom(dpy, "WM_COLORMAP_WINDOWS", false);
  wm_client_leader = XInternAtom(dpy, "WM_CLIENT_LEADER", false);
  mwm_hints = XInternAtom(dpy, _XA_MWM_HINTS, false);
#ifdef SHAPE
  int dummy;
  shape = XShapeQueryExtension(dpy, &shape_event, &dummy);
#endif

  sattr.event_mask = ChildMask|ColormapChangeMask|ButtonMask;
  XChangeWindowAttributes(dpy, root, CWEventMask, &sattr);

	
	sattr.override_redirect = 1;
	sattr.event_mask = KeyPressMask | KeyReleaseMask;
	input = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
		CopyFromParent, InputOnly, CopyFromParent, CWEventMask | CWOverrideRedirect, &sattr);
	XMapWindow(dpy, input);

//  XSelectInput(dpy,root,sattr.event_mask);

  LastReDraw = 0;
  FirstReDraw  = 0;

// Scan Windows now
  /*rman = */ new ResManager(config ? argv[config] : 0);
  /*UEH = */  new UEHandler();
  /*ct = */   new ClientTree();
  for (InfoListIter il = rman->infos[SE_ACTION]->begin(); il!=rman->infos[SE_ACTION]->end(); ++il)
    if (((Action *)il->second)->flags & AC_AUTOSTART) {
      ((Action *)il->second)->Execute();
    }

  unsigned int nwins, i;
  Window dummyw1, dummyw2, *wins;
  XWindowAttributes attr;
  XQueryTree(dpy, root, &dummyw1, &dummyw2, &wins, &nwins);
  for (i = 0; i != nwins; ++i) {
    XGetWindowAttributes(dpy, wins[i], &attr);
    if (!attr.override_redirect && attr.map_state == IsViewable)
      ct->AddWindow(wins[i]);
  }
  XFree(wins);


  ct->RootDesktop->GetFocus();
  WantFocus = 0;
  ToBeRaised = 0;

}

Manager::~Manager() {

}

void Manager::Quit() {
  delete ct;
  delete UEH;
  delete rman;
  XInstallColormap(dpy, DefaultColormap(dpy, screen));
  XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);

  XCloseDisplay(dpy);

}


#ifdef DEBUG

#define SHOW_EV(name, memb) \
    case name: s = #name; w = e.memb.window; break;

void ShowEvent(XEvent e) {
    char sbuf[20],*s=sbuf, buf[20];
    sbuf[0]='\0';
    Window w=0;
    Client *c=NULL;
    switch (e.type) {
        SHOW_EV(ButtonPress, xbutton)
        SHOW_EV(ButtonRelease, xbutton)
        SHOW_EV(ClientMessage, xclient)
        SHOW_EV(ColormapNotify, xcolormap)
        SHOW_EV(ConfigureNotify, xconfigure)
        SHOW_EV(ConfigureRequest, xconfigurerequest)
        SHOW_EV(CreateNotify, xcreatewindow)
        SHOW_EV(DestroyNotify, xdestroywindow)
        SHOW_EV(EnterNotify, xcrossing)
        SHOW_EV(Expose, xexpose)
        SHOW_EV(LeaveNotify, xcrossing)
        SHOW_EV(MapNotify, xmap)
        SHOW_EV(MapRequest, xmaprequest)
        SHOW_EV(MappingNotify, xmapping)
        SHOW_EV(MotionNotify, xmotion)
        SHOW_EV(PropertyNotify, xproperty)
        SHOW_EV(ReparentNotify, xreparent)
        SHOW_EV(ResizeRequest, xresizerequest)
        SHOW_EV(UnmapNotify, xunmap)
        SHOW_EV(KeyPress, xkey)
        SHOW_EV(KeyRelease, xkey)
        default:
#ifdef SHAPE
          if (shape && e.type == shape_event) {
            strcpy(s,"ShapeNotify"); w = ((XShapeEvent *)&e)->window;
          } else {
#endif
            snprintf(s, 19,"unknown event %i",e.type);
            w = None;
#ifdef SHAPE
          }
#endif
          break;
    }
    Icon *i = 0;
    c = ct->FindClient(w, &i);
    if (i)
      c = i->parent;
    strncpy(buf, c ? (c->name ? c->name : "") : "(none)", sizeof buf-1);
    buf[sizeof buf-1]='\0';
    err("%#-10lx: %-20s: %s", w, buf, s);

}

#endif

void Manager::AddReDrawClient(Client *c) {
  if (LastReDraw) {
    LastReDraw->next = new ClientList;
    LastReDraw = LastReDraw->next;
  } else {
    FirstReDraw = new ClientList;
    LastReDraw = FirstReDraw;
  }
  LastReDraw->next = 0;
  LastReDraw->client = c;
}

void Manager::CleanUp() {
// Now delete the windows Xerror found
  for (;MustBeDeleted;) {
    ClientList *cl = MustBeDeleted;
    if (MustBeDeleted->client)
      MustBeDeleted->client->parent->RemoveChild(MustBeDeleted->client,R_WITHDRAW);
    MustBeDeleted = MustBeDeleted->next;
    delete cl;
  }

  if (ToBeRaised && !XPending(dpy)) {
#ifdef DEBUG
    err("Raising Window");
#endif
    ToBeRaised->Raise(R_PARENT|R_INDIRECT);
    ToBeRaised = 0;
  }
  if (WantFocus && !XPending(dpy)) {
#ifdef DEBUG
    err("Giving Focus to Window");
#endif
    WantFocus->GetFocus();
    WantFocus = 0;
  }
  if (!ct->Focus && !ct->MaxWindow && !XPending(dpy)) {
#ifdef DEBUG
    err("Focus Lost");
#endif
    Client *c = ct->FindPointerClient();
//      c->Raise(R_PARENT|R_INDIRECT);
    c->GetFocus();
  }

  if (FirstReDraw && !XPending(dpy)) {
    ClientList *i;
    for (;FirstReDraw;FirstReDraw = i) {
      i = FirstReDraw->next;
      if (FirstReDraw->client) {
        if (FirstReDraw->client->visible)
          FirstReDraw->client->ReDraw();
        FirstReDraw->client->flags &= ~CF_IGNOREEXPOSE;
      }
      delete FirstReDraw;
    }
    LastReDraw = 0;

  }

  if (!XPending(dpy)) { // No more events
    if (IgnoreLeave && XSync(dpy,false) && !XPending(dpy)) // avoid annoying race condition
      IgnoreLeave = false;
  }
}


void Manager::NextEvent() {
  fd_set read_fds;
  while (!XPending(dpy) && fifo+1) {
#define BUFSIZE 255
    char buffer[BUFSIZE+1];
    int length = read(fifo, buffer, BUFSIZE);
    while (length > 0) {
      int i1=0,i2=0;
      while (i2 != length) {
        if (buffer[i2] == '\n') {
          buffer[i2++] = '\0';
          UEH->ExecCommands(buffer+i1);
          i1 = i2;
        } else {
          ++i2;
        }
      }
      if (length < BUFSIZE)
        break;
      if (i1) {
        i1 = i2 - i1;
        int i = i1;
        while (i1)
          buffer[--i1] = buffer[--i2];
        length = read(fifo,buffer + i,BUFSIZE - i) + i;
        if (length == i)
          break;
      } else {
        while (read(fifo,buffer,BUFSIZE) > 0);
        break;
      }
    }
    if (XPending(dpy))
      break;
    CleanUp();
    if (XPending(dpy))
      break;
    FD_ZERO(&read_fds);
    int fd1 = ConnectionNumber(dpy);
    FD_SET(fd1, &read_fds);
    FD_SET(fifo,&read_fds);
    select(1 + ((fd1<fifo) ? fifo : fd1), &read_fds, 0, 0, 0);
  }

}

void Manager::HandleExpose(XExposeEvent &e) {
  if (e.count)
    return;
  Icon *i=0;
  Client *c = ct->FindClient(e.window, &i);
  if (i) {
    i->ReDraw();
    return;
  }
  if (c) {
    AddReDrawClient(c);
    return;
  }
  if (UEH->menu) {
    Menu *m = UEH->menu;
    while (m) {
      if (e.window == m->window) {
        m->ReDraw();
        return;
      }
      m = m->submenu;
    }
  }
  if (UEH->dialog && e.window == UEH->dialog->window) {
    UEH->dialog->ReDraw();
    return;
  }

}


int Manager::Run() {
  XEvent ev;
#ifdef DEBUG
  err("Entering event loop");
#endif
  MustBeDeleted = 0;
  IgnoreLeave = false;
  alive = true;
  for (;alive;) {
    NextEvent();
    XNextEvent(dpy, &ev);
#ifdef DEBUG
    ShowEvent(ev);
#endif
    Client *c;
    switch (ev.type) {
      case KeyRelease:
      case KeyPress:
        UEH->Key(ev.xkey);
        break;
      case ButtonPress:
        UEH->Press(ev.xbutton);
        break;
      case ButtonRelease:
        UEH->Release(ev.xbutton);
        break;
      case MotionNotify:
        UEH->Motion(ev.xmotion);
        break;
      case ConfigureRequest:
        c = ct->FindClient(ev.xconfigurerequest.window, 0);
        if (c) {
          c->Configure(ev.xconfigurerequest);
        } else {
          XWindowChanges wc;
          wc.x = ev.xconfigurerequest.x;
          wc.y = ev.xconfigurerequest.y;
          wc.width = ev.xconfigurerequest.width;
          wc.height = ev.xconfigurerequest.height;
          wc.sibling = ev.xconfigurerequest.above;
          wc.stack_mode = ev.xconfigurerequest.detail;
          XConfigureWindow(dpy, ev.xconfigurerequest.window, ev.xconfigurerequest.value_mask, &wc);
        }
        break;
      case MapRequest:
        c = ct->FindClient(ev.xmaprequest.window, 0);
        if (c) {
          if (!c->visible && c->parent) {
            c->Map();
            c->Raise(0);
          }
        } else {
          ct->AddWindow(ev.xmaprequest.window);
        }
        break;
      case MapNotify:
        c = ct->FindClient(ev.xmap.window,0);
        if (c) {
          if (ev.xunmap.window != c->window)
            break;
          c->Map();
        }
        break;
      case UnmapNotify:
        c = ct->FindClient(ev.xunmap.window,0);
        if (c) {
          if (ev.xunmap.window != c->window)
            break;
          c->Unmap(true);
        }
        break;
      case DestroyNotify:
        c = ct->FindClient(ev.xdestroywindow.window, 0);
        if (c)
          c->parent->RemoveChild(c,R_WITHDRAW);
        break;
      case ClientMessage:
        c = ct->FindClient(ev.xclient.window,0);
        if (c && ev.xclient.message_type == wm_change_state &&
            ev.xclient.format == 32 && ev.xclient.data.l[0] == IconicState)
          c->Hide();
        break;
      case ColormapNotify:
        c = ct->FindClient(ev.xcolormap.window, 0);
        if (c && ev.xcolormap.c_new) {
          c->cmap = ev.xcolormap.colormap;
          if (ct->Focus == c)
            XInstallColormap(dpy, c->cmap);
        }
        break;
      case PropertyNotify:
        switch (ev.xproperty.atom) {
          case XA_WM_NAME:
            c = ct->FindClient(ev.xproperty.window,0);
            if (c) {
              c->ChangeName();
              c->UpdateName(false);
              if (c->icon)
                c->icon->ChangeName(0);
            }
            break;
          case XA_WM_ICON_NAME:
            c = ct->FindClient(ev.xproperty.window, 0);
            if (c && c->icon && !c->name)
              c->icon->ChangeName(0);
            break;
          case XA_WM_NORMAL_HINTS:
            c = ct->FindClient(ev.xproperty.window, 0);
            if (c) {
              long dummy;
              XGetWMNormalHints(dpy, c->window, c->size, &dummy);
#ifdef DEBUG
              c->dump();
#endif
            }
            break;
        }
        break;
      case EnterNotify:
        c = ct->FindClient(ev.xcrossing.window, 0);
        if (c) {
          if ((c->flags & CF_AUTOSHADE) && (c->flags & CF_SHADED))
            c->Shade();
          if (c->flags & CF_RAISEONENTER)
            ToBeRaised = c;
          if (c->flags & CF_FOCUSONENTER)
            WantFocus = c;
        } else {
          if (IgnoreLeave)
            break;
          for (int i=0;i!=4;++i)
            if (ev.xcrossing.window == ct->RootBorder[i]) {
              c = ct->RootDesktop;
              goto leave; // quick and dirty
            }
        }
        break;
      case LeaveNotify:
        if (IgnoreLeave)
          break;
        c = ct->FindClient(ev.xcrossing.window, 0);
        leave:
        {
          if (!c)
            break;
          if ((c->flags & CF_AUTOSHADE) && !(c->flags & CF_SHADED) && (c->frame == ev.xcrossing.window))
            c->Shade();
          Desktop *d = dynamic_cast<Desktop *>(c);
          if (!d)
            break;
          if (d->parent && (ev.xcrossing.state & ControlMask))
            d = d->parent;
          if ((ev.xcrossing.state & Mod1Mask) || (d->flags & DF_AUTOSCROLL))
            IgnoreLeave = d->Leave(ev.xcrossing.x_root,ev.xcrossing.y_root +
                       (ev.xcrossing.window==c->window ? d->THeight : 0),ev.xcrossing.state & Mod1Mask);
        }
        break;
      case Expose:
        HandleExpose(ev.xexpose);
        break;
      default:
#ifdef SHAPE
        if (shape && ev.type == shape_event) {
          c = ct->FindClient(((XShapeEvent *)&ev)->window, 0);
          if (c)
            c->SetShape();
        }
#endif
        break;
    }
#ifdef DEBUG2
    XSync(dpy,false);
#endif
    CleanUp();

  }
  Quit();
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1