/* bindings.c: deal with event bindings, and running the event loop */

/* Copyright (C) 1999 by the Massachusetts Institute of Technology.
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "nawm.h"
#include "lang.h"
#include "parser.h"

bindlist *current_bindings;
bindlist *anymode;
extern Window root;
int quit;
extern Display *dpy;
extern variable CURRENTWINDOW, PX, PY;
extern long options;
long eventmask = SubstructureNotifyMask | EnterWindowMask | LeaveWindowMask;
event_handler *eventhandlers = NULL;

bindlist *findmode(char *name);
int interactive(bindlist *mode);

typedef struct _mode {
  char *name;
  bindlist *bindings;
  struct _mode *next;
} mode;
mode *modes;

void initbindings(void)
{
  anymode = NULL;
  current_bindings = NULL;
}

bindlist *mkbinding(int type, char *data, node *cmds)
{
  bindlist *ans = xmalloc(sizeof(bindlist));
  char *p = data;

  ans->cmds = cmds;

  switch(type)
    {
    case KEYPRESS:
    case KEYRELEASE:
      ans->type = (type == KEYPRESS) ? KeyPress : KeyRelease;
      ans->u.key.mods = parse_mods(&p);
      ans->u.key.kc = parse_key(p);
      free(data);
      break;

    case BUTTONPRESS:
    case BUTTONRELEASE:
      ans->type = (type == BUTTONPRESS) ? ButtonPress : ButtonRelease;
      ans->u.button.mods = parse_mods(&p);
      ans->u.button.button = parse_button(p);
      free(data);
      break;

    case ENTER:
    case LEAVE:
      ans->type = (type == ENTER) ? EnterNotify : LeaveNotify;
      ans->u.name = data;
      break;

    case MOTION:
      ans->type = MotionNotify;
      break;

    default:
      ans->type = type;
      break;
    }
      
  return ans;
}

int parse_mods(char **str)
{
  int mods = 0;

  do
    {
      if (!strncasecmp(*str, "shift", 5) && isspace(*(*str + 5)))
	{
	  mods |= ShiftMask;
	  *str += 5;
	}
      else if (!strncasecmp(*str, "lock", 4) && isspace(*(*str + 4)))
	{
	  mods |= LockMask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "control", 7) && isspace(*(*str + 7)))
	{
	  mods |= ControlMask;
	  *str += 7;
	}
      else if ((!strncasecmp(*str, "mod1", 4) || !strncasecmp(*str, "meta", 4))
	       && isspace(*(*str + 4)))
	{
	  mods |= Mod1Mask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "mod2", 4) && isspace(*(*str + 4)))
	{
	  mods |= Mod2Mask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "mod3", 4) && isspace(*(*str + 4)))
	{
	  mods |= Mod3Mask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "mod4", 4) && isspace(*(*str + 4)))
	{
	  mods |= Mod4Mask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "mod5", 4) && isspace(*(*str + 4)))
	{
	  mods |= Mod5Mask;
	  *str += 4;
	}
      else if (!strncasecmp(*str, "any", 3) && isspace(*(*str + 3)))
	{
	  mods = AnyModifier;
	  *str += 3;
	}
      else break;

      while (isspace(**str))
	(*str)++;
    }
  while (**str);

  return mods;
}


KeyCode parse_key(char *name)
{
  KeySym ks;
  KeyCode kc = 0;
  char *ndup, *p;

  ndup = xstrdup(name);
  for (p = strtok(ndup, "| "); p && !kc; p = strtok(NULL, "| "))
    {
      ks = XStringToKeysym(p);
      if (ks == NoSymbol)
	continue;
      kc = XKeysymToKeycode(dpy, ks);
    }
  free(ndup);

  if (ks == NoSymbol)
    die("Bad keysym name: %s", name);
  else if (!kc)
    die("No keycode for keysym: %s", name);
  return kc;
}

int parse_button(char *name)
{
  if (!strcasecmp(name, "left"))
    return 1;
  else if (!strcasecmp(name, "right"))
    return 3;
  else if (!strcasecmp(name, "middle"))
    return 2;
  else if (*name > '0' && *name < '6' && !*(name + 1))
    return *name - '0';
  die("Bad button name: %s", name);
}

void add_to_anymode(bindlist *b)
{
  b->next = anymode;
  anymode = b;
}

void defmode(char *name, bindlist *bindings)
{
  mode *tmp;

  tmp = xmalloc(sizeof(mode));
  tmp->name = name;
  tmp->bindings = bindings;
  tmp->next = modes;
  modes = tmp;
}

bindlist *findmode(char *name)
{
  mode *m;

  for (m = modes; m; m = m->next)
    {
      if (!strcmp(name, m->name))
	return m->bindings;
    }
  die("No such mode: %s", name);
}

void set_mode(char *mode)
{
  undo_bindings(current_bindings);
  current_bindings = findmode(mode);
  do_bindings(current_bindings);
}


void do_bindings(bindlist *mode)
{
  for (; mode; mode = mode->next)
    {
      switch (mode->type)
	{
	case BEGIN_:
	  eval_cmds(mode->cmds);
	  break;
	case ButtonPress:
	case ButtonRelease:
	  XGrabButton(dpy, mode->u.button.button, mode->u.button.mods, root,
		      False, ButtonPressMask | ButtonReleaseMask,
		      GrabModeAsync, GrabModeAsync, None, None);
	  if (mode->u.button.mods != AnyModifier &&
	      options & NAWM_OPT_NOCAPSLOCK)
	    {
	      XGrabButton(dpy, mode->u.button.button,
			  mode->u.button.mods ^ LockMask, root,
			  False, ButtonPressMask | ButtonReleaseMask,
			  GrabModeAsync, GrabModeAsync, None, None);
	    }
	  break;
	case KeyPress:
	case KeyRelease:
	  XGrabKey(dpy, mode->u.key.kc, mode->u.key.mods, root,
		   False, GrabModeAsync, GrabModeAsync);
	  if (mode->u.key.mods != AnyModifier && options & NAWM_OPT_NOCAPSLOCK)
	    {
	      XGrabKey(dpy, mode->u.key.kc, mode->u.key.mods ^ LockMask, root,
		       False, GrabModeAsync, GrabModeAsync);
	    }
	  break;
	case MotionNotify:
	  XGrabPointer(dpy, root, False, ButtonPressMask |
		       ButtonReleaseMask | PointerMotionMask,
		       GrabModeAsync, GrabModeAsync, None,
		       None, CurrentTime);
	  break;
	}
    }
}

void undo_bindings(bindlist *mode)
{
  for (; mode; mode = mode->next)
    {
      switch (mode->type)
	{
	case END:
	  eval_cmds(mode->cmds);
	  break;
	case ButtonPress:
	case ButtonRelease:
	  XUngrabButton(dpy, mode->u.button.button, mode->u.button.mods, root);
	  break;
	case KeyPress:
	case KeyRelease:
	  XUngrabKey(dpy, mode->u.key.kc, mode->u.key.mods, root);
	  break;
	case MotionNotify:
	  XUngrabPointer(dpy, CurrentTime);
	  break;
	}
    }
}

int interactive(bindlist *mode)
{
  for (; mode; mode = mode->next)
    {
      if (mode->type != BEGIN_ && mode->type != END)
	return 1;
    }
  return 0;
}

void run(void)
{
  XEvent ev;
  bindlist *binding;
  Window rr, cr;
  int rx, ry, cx, cy;
  unsigned int mr;
  event_handler *eh;

  quit = 0;
  do_bindings(anymode);
  XSelectInput(dpy, root, eventmask);

  while (!quit && (interactive(anymode) || interactive(current_bindings)))
    {
      XNextEvent(dpy, &ev);

      XQueryPointer(dpy, root, &rr, &cr, &rx, &ry, &cx, &cy, &mr);
      if (cr)
	assign_var(&CURRENTWINDOW, (nawmval)client_window(cr));
      else
	assign_var(&CURRENTWINDOW, 0);

      switch (ev.type)
	{
	case MappingNotify:
	  update_keymap();
	  break;

	case CreateNotify:
	case DestroyNotify:
	case ConfigureNotify:
	case UnmapNotify:
	case MapNotify:
	  update_cache(&ev);
	  break;

	case MotionNotify:
	  for (binding = current_bindings; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type)
		eval_cmds(binding->cmds);
	    }
	  for (binding = anymode; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type)
		eval_cmds(binding->cmds);
	    }
	  break;

	case KeyPress:
	case KeyRelease:
	  if (options & NAWM_OPT_NOCAPSLOCK)
	    ev.xkey.state &= ~LockMask;
	  for (binding = current_bindings; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  binding->u.key.kc == ev.xkey.keycode &&
		  (binding->u.key.mods == ev.xkey.state ||
		   binding->u.key.mods == AnyModifier))
		eval_cmds(binding->cmds);
	    }
	  for (binding = anymode; binding; binding = binding->next)
	    {
	      if(binding->type == ev.type &&
		 binding->u.key.kc == ev.xkey.keycode &&
		 (binding->u.key.mods == ev.xkey.state ||
		  binding->u.key.mods == AnyModifier))
		eval_cmds(binding->cmds);
	    }
	  break;

	case ButtonPress:
	case ButtonRelease:
	  if (ev.type == ButtonRelease)
	    ev.xkey.state &= ~(1 << (ev.xbutton.button + 7));
	  if (options & NAWM_OPT_NOCAPSLOCK)
	    ev.xkey.state &= ~LockMask;
	  for (binding = current_bindings; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  binding->u.button.button == ev.xbutton.button &&
		  (binding->u.button.mods == ev.xbutton.state ||
		   binding->u.button.mods == AnyModifier))
		eval_cmds(binding->cmds);
	    }
	  for (binding = anymode; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  binding->u.button.button == ev.xbutton.button &&
		  (binding->u.button.mods == ev.xbutton.state ||
		   binding->u.button.mods == AnyModifier))
		eval_cmds(binding->cmds);
	    }
	  break;

	case EnterNotify:
	  for (binding = current_bindings; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  (!binding->u.name || hasname(ev.xcrossing.window,
					       binding->u.name)))
		eval_cmds(binding->cmds);
	    }
	  for (binding = anymode; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  (!binding->u.name || hasname(ev.xcrossing.window,
					       binding->u.name)))
		eval_cmds(binding->cmds);
	    }
	  break;

	case LeaveNotify:
	  for (binding = current_bindings; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  (!binding->u.name || hasname(ev.xcrossing.window,
					       binding->u.name)))
		{
		  assign_var(&CURRENTWINDOW, (nawmval)ev.xcrossing.window);
		  eval_cmds(binding->cmds);
		}
	    }
	  for (binding = anymode; binding; binding = binding->next)
	    {
	      if (binding->type == ev.type &&
		  (!binding->u.name || hasname(ev.xcrossing.window,
					       binding->u.name)))
		{
		  assign_var(&CURRENTWINDOW, (nawmval)ev.xcrossing.window);
		  eval_cmds(binding->cmds);
		}
	    }
	  break;

	}

      for (eh = eventhandlers; eh; eh = eh->next)
	eh->handler(&ev);
    }

  undo_bindings(anymode);
}
