/* Copyright (c) 1994-1996 David Hogan, see README for licence details */
#include <stdio.h>
#include <stdlib.h>

#include <X11/X.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>

#include "dat.h"
#include "fns.h"


void
mainloop (int shape_event)
{
  Client *c = 0;
  ScreenInfo *s = 0;
  XEvent ev;

  for (;;)
    {
      XNextEvent (dpy, &ev);

      switch (ev.type)
	{
	case KeyPress:
	  keyevent (&ev.xkey);
	  break;
	case KeyRelease:
	  break;
	case ButtonPress:
	  button (&ev.xbutton);
	  XAllowEvents (dpy, SyncPointer, ev.xbutton.time);
	  break;
	case ButtonRelease:
	  break;
	case MapRequest:
	  mapreq (&ev.xmaprequest);

	  if ((c = getclient (ev.xmaprequest.window, 0)) != 0)
	    {
	      if (normal (c) && ((s = c->screen) != 0))
		{
		  if (c == current)
		    {
		      if (c->isnotile)
			{
			  raise_notile (s);
			}
		      else
			{
			  raise_tile (s, 0);
			}
		    }

		  if (!c->isnotile || c->istool)
		    {
		      tile_all (s);
		    }
		}
	    }
	  break;
	case ConfigureRequest:
	  configurereq (&ev.xconfigurerequest);
	  break;
	case UnmapNotify:
	  unmap (&ev.xunmap);

	  if ((c = getclient (ev.xunmap.window, 0)) != 0)
	    {
	      if ((s = c->screen) != 0)
		{
		  if (!c->isnotile || c->istool)
		    {
		      tile_all (s);
		    }

		  if (!current)
		    {
		      revert_window (s);
		    }
		}
	    }
	  break;
	case PropertyNotify:
	  property (&ev.xproperty);
	  break;
	case Expose:
	  expose (&ev.xexpose);
	  break;
	case CreateNotify:
	  newwindow (&ev.xcreatewindow);
	  break;
	case DestroyNotify:
	  destroy (ev.xdestroywindow.window);
	  break;
	case ClientMessage:
	  clientmesg (&ev.xclient);
	  break;
	case ColormapNotify:
	  cmap (&ev.xcolormap);
	  break;
	case ReparentNotify:
	  reparent (&ev.xreparent);
	  break;
	case SelectionNotify:
	  break;
	case EnterNotify:
	  enter (&ev.xcrossing);
	  break;
	case FocusIn:
	  focusin (&ev.xfocus);
	  break;
	default:
	  if (shape && ev.type == shape_event)
	    {
	      shapenotify ((XShapeEvent *) & ev);
	    }
	  break;
	}
    }
}

void
expose (XExposeEvent * e)
{
  Client *c;
  ScreenInfo *s;

  if ((c = getclient (e->window, 0)))
    draw_border (c, c == current ? 1 : 0);
  else if ((s = getbarscreen (e->window)))
    draw_tbar (s);
}

void
configurereq (XConfigureRequestEvent * e)
{
  XWindowChanges wc;
  Client *c;

  c = getclient (e->window, 0);
  e->value_mask &= ~CWSibling;

  if (c)
    {
      gravitate (c, 1);

      if (e->value_mask & CWX)
	c->x = e->x;

      if (e->value_mask & CWY)
	c->y = e->y;

      if (e->value_mask & CWWidth)
	c->dx = e->width;

      if (e->value_mask & CWHeight)
	c->dy = e->height;

      if (e->value_mask & CWBorderWidth)
	c->border = e->border_width;

      gravitate (c, 0);

      if (e->value_mask & CWStackMode)
	{
	  if (wc.stack_mode == Above)
	    top (c);
	  else
	    e->value_mask &= ~CWStackMode;
	}

      if (c->parent != c->screen->root && c->window == e->window)
	{
	  wc.x = c->x - BORDER;
	  wc.y = c->y - BORDER;
	  wc.width = c->dx + 2 * BORDER;
	  wc.height = c->dy + 2 * BORDER;
	  wc.border_width = 1;
	  wc.sibling = None;
	  wc.stack_mode = e->detail;
	  XConfigureWindow (dpy, c->parent, e->value_mask, &wc);
	  sendconfig (c);
	}
    }

  if (c && c->init)
    {
      wc.x = BORDER;
      wc.y = BORDER;
    }
  else
    {
      wc.x = e->x;
      wc.y = e->y;
    }

  wc.width = e->width;
  wc.height = e->height;
  wc.border_width = 0;
  wc.sibling = None;
  wc.stack_mode = Above;
  e->value_mask &= ~CWStackMode;
  e->value_mask |= CWBorderWidth;

  XConfigureWindow (dpy, e->window, e->value_mask, &wc);
}

void
mapreq (XMapRequestEvent * e)
{
  Client *c;

  c = getclient (e->window, 0);

  if (c == 0 || c->window != e->window)
    {
      return;
    }

  switch (c->state)
    {
    case WithdrawnState:
      if (c->parent == c->screen->root)
	{
	  if (!manage (c, 0))
	    return;
	  break;
	}
      XReparentWindow (dpy, c->window, c->parent, BORDER, BORDER);
      XAddToSaveSet (dpy, c->window);
      /* fall through... */

    case NormalState:
      if (c->trans != None)
	{
	  Client *p = getclient (c->trans, 0);

	  if (p)
	    {
	      c->desktop = p->desktop;
	    }
	}

      raise_tbar (c->screen);
      XMapWindow (dpy, c->window);
      XMapRaised (dpy, c->parent);
      top (c);
      wmsetstate (c, NormalState);

      if ((mouse_on_screen () == c->screen)
	  || (current && (current->screen == c->screen)))
	{
	  active (c);
	}
      break;

    case IconicState:
      unhidec (c, 1);
      break;
    }
}

void
unmap (XUnmapEvent * e)
{
  Client *c;

  c = getclient (e->window, 0);

  if (c)
    {
      switch (c->state)
	{
	case IconicState:
	  if (e->send_event)
	    {
	      unhidec (c, 0);
	      withdraw (c);
	    }
	  break;

	case NormalState:
	  if (c == current)
	    current = 0;

	  if (!c->reparenting)
	    withdraw (c);
	  break;
	}

      c->reparenting = 0;
    }
}

void
newwindow (XCreateWindowEvent * e)
{
  Client *c;
  ScreenInfo *s;

  if (e->override_redirect)
    return;
  c = getclient (e->window, 1);

  /* Crashing bug patch by MG has been applied here. */
  if (c && c->window == e->window)
    {
      c->x = e->x;
      c->y = e->y;
      c->dx = e->width;
      c->dy = e->height;
      c->border = e->border_width;

      if (0 != (s = getscreen (e->parent)))
	c->screen = s;
      else
	c->screen = &screens[0];

      if (c->parent == None)
	c->parent = c->screen->root;
    }
}

void
destroy (Window w)
{
  Client *c;

  if (!(c = getclient (w, 0)))
    return;

  rmclient (c);

  ignore_badwindow = 1;
  XSync (dpy, False);
  ignore_badwindow = 0;
}

void
clientmesg (XClientMessageEvent * e)
{
  Client *c;

  if (e->message_type == exit_larswm)
    {
      cleanup ();
      exit (0);
    }
  else if (e->message_type == restart_larswm)
    {
      cleanup ();
      execvp (myargv[0], myargv);
      perror ("larswm: exec failed");
      exit (1);
    }
  else if (e->message_type == wm_change_state)
    {
      c = getclient (e->window, 0);

      if (e->format == 32 && e->data.l[0] == IconicState && c != 0)
	{
	  if (normal (c))
	    hide (c);
	}
    }
}

void
cmap (XColormapEvent * e)
{
  Client *c;
  int i;

  if (e->new)
    {
      c = getclient (e->window, 0);
      if (c)
	{
	  c->cmap = e->colormap;
	  if (c == current)
	    cmapfocus (c);
	}
      else
	for (c = clients; c; c = c->next)
	  {
	    for (i = 0; i < c->ncmapwins; i++)
	      if (c->cmapwins[i] == e->window)
		{
		  c->wmcmaps[i] = e->colormap;
		  if (c == current)
		    cmapfocus (c);
		  return;
		}
	  }
    }
}

void
property (XPropertyEvent * e)
{
  Client *c;
  long msize;

  /* check if text to be displayed on status bar was just changed */
  if ((e->atom == bartext_larswm) && (e->window == DefaultRootWindow (dpy)))
    {
      update_cmd_output ();
      return;
    }

  if (!(c = getclient (e->window, 0)))
    return;

  switch (e->atom)
    {
    case XA_WM_ICON_NAME:
      if (c->iconname != 0)
	XFree ((char *) c->iconname);
      c->iconname =
	(e->state == PropertyDelete) ? 0 : getprop (c->window, e->atom);
      setlabel (c);
      renamec (c, c->label);
      break;

    case XA_WM_NAME:
      if (c->name != 0)
	XFree ((char *) c->name);
      c->name =
	(e->state == PropertyDelete) ? 0 : getprop (c->window, e->atom);
      setlabel (c);
      renamec (c, c->label);
      break;

    case XA_WM_TRANSIENT_FOR:
      gettrans (c);
      break;

    case XA_WM_NORMAL_HINTS:
      if (XGetWMNormalHints (dpy, c->window, &c->size, &msize) == 0
	  || c->size.flags == 0)
	{
	  c->size.flags = PSize;
	}

      if (c->size.flags & PBaseSize)
	{
	  c->min_dx = c->size.base_width;
	  c->min_dy = c->size.base_height;
	}
      else if (c->size.flags & PMinSize)
	{
	  c->min_dx = c->size.min_width;
	  c->min_dy = c->size.min_height;
	}
      else
	{
	  c->min_dx = c->min_dy = 1;
	}
      break;

    default:
      if (e->atom == wm_colormaps)
	{
	  getcmaps (c);

	  if (c == current)
	    {
	      cmapfocus (c);
	    }
	}
      break;
    }
}

void
reparent (XReparentEvent * e)
{
  Client *c;
  XWindowAttributes attr;
  ScreenInfo *s;

  if (!getscreen (e->event) || e->override_redirect)
    return;
  if ((s = getscreen (e->parent)) != 0)
    {
      c = getclient (e->window, 1);
      if (c != 0 && (c->dx == 0 || c->dy == 0))
	{
	  XGetWindowAttributes (dpy, c->window, &attr);
	  c->x = attr.x;
	  c->y = attr.y;
	  c->dx = attr.width;
	  c->dy = attr.height;
	  c->border = attr.border_width;
	  c->screen = s;
	  if (c->parent == None)
	    c->parent = c->screen->root;
	}
    }
  else
    {
      c = getclient (e->window, 0);
      if (c != 0 && (c->parent == c->screen->root || withdrawn (c)))
	rmclient (c);
    }
}

void
shapenotify (XShapeEvent * e)
{
  Client *c;

  c = getclient (e->window, 0);
  if (c == 0)
    return;

  setshape (c);
}

void
enter (XCrossingEvent * e)
{
  Client *c;

  if (e->mode != NotifyGrab || e->detail != NotifyNonlinearVirtual)
    return;

  c = getclient (e->window, 0);

  if (c && c->parent && (c != current))
    {
      XMapRaised (dpy, c->parent);
      active (c);
    }
  else
    {
      XSetInputFocus (dpy, e->window, RevertToPointerRoot, e->time);
    }
}

void
focusin (XFocusChangeEvent * e)
{
  Client *c;

  if (e->detail != NotifyNonlinearVirtual)
    return;

  c = getclient (e->window, 0);

  if (c && c->window && c->parent && (c->window == e->window)
      && (c != current))
    {
      XMapRaised (dpy, c->parent);
      active (c);
    }
  else
    {
      XSetInputFocus (dpy, e->window, RevertToPointerRoot, CurrentTime);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1