/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>

#include "libs/fvwmlib.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "defaults.h"
#include "misc.h"
#include "screen.h"
#include "focus.h"
#ifdef HAVE_STROKE
#include "stroke.h"
#endif /* HAVE_STROKE */

static void activate_binding(
  Binding *binding, BindingType type, Bool do_grab, Bool do_grab_root);

#define MODS_UNUSED_DEFAULT LockMask
static int mods_unused = MODS_UNUSED_DEFAULT;


static void update_nr_buttons(
  int contexts, int *nr_left_buttons, int *nr_right_buttons, Bool do_set)
{
  int i;
  int l = (nr_left_buttons) ? *nr_left_buttons : 0;
  int r = (nr_right_buttons) ? *nr_right_buttons : 0;

  if (contexts != C_ALL && (contexts & C_LALL) && nr_left_buttons != NULL)
  {
    /* check for nr_left_buttons */
    for (i = 0; i < NUMBER_OF_BUTTONS; i += 2)
    {
      if ((contexts & (C_L1 << i)) &&
	  (do_set || *nr_left_buttons <= i / 2))
      {
	*nr_left_buttons = i / 2 + 1;
      }
    }
  }
  if(contexts != C_ALL && (contexts & C_RALL) && nr_right_buttons != NULL)
  {
    /* check for nr_right_buttons */
    for (i = 1; i < NUMBER_OF_BUTTONS; i += 2)
    {
      if ((contexts & (C_L1 << i)) &&
	  (do_set || *nr_right_buttons <= i / 2))
      {
	*nr_right_buttons = i / 2 + 1;
      }
    }
  }
  if ((nr_left_buttons && *nr_left_buttons != l) ||
      (nr_right_buttons && *nr_right_buttons != r))
  {
    Scr.flags.do_need_window_update = 1;
    Scr.flags.has_nr_buttons_changed = 1;
  }
}

/****************************************************************************
 *
 *  Parses a mouse or key binding
 *
 ****************************************************************************/
int ParseBinding(
  Display *dpy, Binding **pblist, char *tline, BindingType type,
  int *nr_left_buttons, int *nr_right_buttons, unsigned char *buttons_grabbed,
  Bool do_ungrab_root, Bool is_silent)
{
  char *action, context_string[20], modifier_string[20], *ptr, *token;
  char key_string[201] = "";
  int button = 0;
  STROKE_CODE(char stroke[STROKE_MAX_SEQUENCE + 1] = "";)
  int n1=0,n2=0,n3=0;
  STROKE_CODE(int n4=0;)
  STROKE_CODE(int i;)
  KeySym keysym = NoSymbol;
  int context;
  int modifier;
  Bool is_unbind_request = False;
  int rc;

  /* tline points after the key word "Mouse" or "Key" */
  ptr = GetNextToken(tline, &token);
  if (token != NULL)
  {
    if (type == KEY_BINDING)
      n1 = sscanf(token,"%200s", key_string); /* see len of key_string above */
#ifdef HAVE_STROKE
    else if (type == STROKE_BINDING)
    {
      int num = 0;
      int j;

      n1 = 1;
      i = 0;
      if (token[0] == 'N' && token[1] != '\0')
        num = 1;
      j=i+num;
      while(n1 && token[j] != '\0' && i < STROKE_MAX_SEQUENCE)
      {
        if (!isdigit(token[j]))
          n1 = 0;
        if (num)
        {
          /* Numeric pad to Telephone  */
          if ('7' <= token[j] && token[j] <= '9')
            token[j] -= 6;
          else if ('1' <= token[j] && token[j] <= '3')
            token[j] += 6;
        }
        stroke[i] = token[j];
        i++;
        j=i+num;
      }
      stroke[i] = '\0';
      if (strlen(token) > STROKE_MAX_SEQUENCE + num)
      {
        if (!is_silent)
          fvwm_msg(WARN, "ParseBinding",
            "Too long stroke sequence in line %s"
            "Only %i elements will be taken into account.\n",
            tline, STROKE_MAX_SEQUENCE);
      }
    }
#endif /* HAVE_STROKE */
    else
    {
      n1 = sscanf(token, "%d", &button);
      if (button < 0 || button > NUMBER_OF_MOUSE_BUTTONS)
      {
	if (!is_silent)
	  fvwm_msg(ERR, "ParseBinding",
	    "Illegal mouse button in line %s", tline);
	free(token);
	return 0;
      }
    }
    free(token);
  }

#ifdef HAVE_STROKE
  if (type == STROKE_BINDING)
  {
    ptr = GetNextToken(ptr, &token);
    if (token != NULL)
    {
      n4 = sscanf(token,"%d", &button);
      free(token);
    }
  }
#endif /* HAVE_STROKE */

  ptr = GetNextToken(ptr, &token);
  if (token != NULL)
  {
    n2 = sscanf(token, "%19s", context_string);
    free(token);
  }

  action = GetNextToken(ptr, &token);
  if (token != NULL)
  {
    n3 = sscanf(token, "%19s", modifier_string);
    free(token);
  }

  if (n1 != 1 || n2 != 1 || n3 != 1
     STROKE_CODE(|| (type == STROKE_BINDING && n4 != 1)))
  {
    if (!is_silent)
      fvwm_msg(ERR, "ParseBinding", "Syntax error in line %s", tline);
    return 0;
  }

  if (ParseContext(context_string, &context) && !is_silent)
    fvwm_msg(WARN, "ParseBinding", "Illegal context in line %s", tline);
  if (ParseModifiers(modifier_string, &modifier) && !is_silent)
    fvwm_msg(WARN, "ParseBinding", "Illegal modifier in line %s", tline);

  if (type == KEY_BINDING)
  {
    keysym = FvwmStringToKeysym(dpy, key_string);
    /*
     * Don't let a 0 keycode go through, since that means AnyKey to the
     * XGrabKey call.
     */
    if (keysym == 0)
    {
      if (!is_silent)
        fvwm_msg(ERR, "ParseBinding", "No such key: %s", key_string);
      return 0;
    }
  }

  /*
  ** strip leading whitespace from action if necessary
  */
  while (*action && (*action == ' ' || *action == '\t'))
    action++;
  /* see if it is an unbind request */
  if (!action || action[0] == '-')
    is_unbind_request = True;

  /*
  ** Remove the "old" bindings if any
  */
  {
    Bool is_binding_removed = False;
    Binding *b;
    Binding *rmlist = NULL;

    CollectBindingList(
      dpy, pblist, &rmlist, type, STROKE_ARG((void *)stroke)
      button, keysym, modifier, context);
    if (rmlist != NULL)
    {
      is_binding_removed = True;
      if (is_unbind_request)
      {
	/* remove the grabs for the key for unbind requests */
	for (b = rmlist; b != NULL; b = b->NextBinding)
	{
	  /* release the grab */
	  activate_binding(b, type, False, do_ungrab_root);
	}
      }
      FreeBindingList(rmlist);
    }

    if (is_binding_removed)
    {
      int bcontext = 0;

      if (buttons_grabbed)
	*buttons_grabbed = 0;
      for (b = *pblist; b != NULL; b = b->NextBinding)
      {
	if(b->type == MOUSE_BINDING && (b->Context & C_WINDOW) &&
	   (b->Modifier == 0 || b->Modifier == AnyModifier) &&
	   buttons_grabbed != NULL)
	{
	  if (button == 0)
	  {
	    *buttons_grabbed |= ((1 << NUMBER_OF_MOUSE_BUTTONS) - 1);
	  }
	  else
	  {
	    *buttons_grabbed |= (1 << (button - 1));
	  }
	}
	if (nr_left_buttons != NULL || nr_right_buttons != NULL)
	{
	  if (b->Context != C_ALL && (b->Context & (C_LALL | C_RALL)) &&
	      b->type == MOUSE_BINDING)
	  {
	    bcontext |= b->Context;
	  }
	}
      }
      update_nr_buttons(bcontext, nr_left_buttons, nr_right_buttons, True);
    }
  }

  /* return if it is an unbind request */
  if (is_unbind_request)
    return 0;

  update_nr_buttons(context, nr_left_buttons, nr_right_buttons, False);

  if((modifier & AnyModifier)&&(modifier&(~AnyModifier)))
  {
    fvwm_msg(WARN,"ParseBinding","Binding specified AnyModifier and other "
	     "modifers too. Excess modifiers will be ignored.");
    modifier = AnyModifier;
  }

  if ((type == MOUSE_BINDING)&&(context & C_WINDOW)&&
     (((modifier==0)||modifier == AnyModifier)) && (buttons_grabbed != NULL))
  {
    if (button == 0)
    {
      *buttons_grabbed |= ((1 << NUMBER_OF_MOUSE_BUTTONS) - 1);
    }
    else
    {
      *buttons_grabbed |= (1 << (button - 1));
    }
  }

  return AddBinding(
    dpy, pblist, type, STROKE_ARG((void *)stroke)
    button, keysym, key_string, modifier, context, (void *)action, NULL);

  return rc;
}

static void activate_binding(
  Binding *binding, BindingType type, Bool do_grab, Bool do_grab_root)
{
  FvwmWindow *t;

  if (binding == NULL)
    return;
  if (do_grab_root)
  {
    /* necessary for key bindings that work over unfocused windows */
    GrabWindowKeyOrButton(
      dpy, Scr.Root, binding, C_WINDOW|C_DECOR|C_ROOT|C_ICON,
      GetUnusedModifiers(), None, do_grab);
  }
  if (fFvwmInStartup == True)
    return;

  /* grab keys immediately */
  for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
  {
    if ((binding->Context & C_WINDOW) &&
	(binding->type == MOUSE_BINDING
	 STROKE_CODE(|| binding->type == STROKE_BINDING)))
    {
      GrabWindowButton(
	dpy, t->Parent, binding,
	C_WINDOW,
	GetUnusedModifiers(), None, do_grab);
    }
    if ((binding->Context & (C_WINDOW|C_DECOR)) && binding->type == KEY_BINDING)
    {
      GrabWindowKey(
	dpy, t->frame, binding, C_WINDOW|C_DECOR, GetUnusedModifiers(),
	do_grab);
    }
    if (binding->Context & C_ICON)
    {
      if(t->icon_w != None)
      {
        GrabWindowKeyOrButton(
	  dpy, t->icon_w, binding, C_ICON, GetUnusedModifiers(), None, do_grab);
      }
      if(t->icon_pixmap_w != None)
      {
	GrabWindowKeyOrButton(
	  dpy, t->icon_pixmap_w, binding, C_ICON, GetUnusedModifiers(),
	  None, do_grab);
      }
    }
  }

  return;
}

static void binding_cmd(F_CMD_ARGS, BindingType type, Bool do_grab_root)
{
  Binding *b;
  int count;
  unsigned char btg = Scr.buttons2grab;

  count = ParseBinding(
    dpy, &Scr.AllBindings, action, type, &Scr.nr_left_buttons,
    &Scr.nr_right_buttons, &btg, do_grab_root, Scr.flags.silent_functions);
  if (btg != Scr.buttons2grab)
  {
    Scr.flags.do_need_window_update = 1;
    Scr.flags.has_mouse_binding_changed = 1;
    Scr.buttons2grab = btg;
  }
  for (b = Scr.AllBindings; count > 0; count--, b = b->NextBinding)
  {
    activate_binding(b, type, True, do_grab_root);
  }

  return;
}

void CMD_Key(F_CMD_ARGS)
{
  binding_cmd(F_PASS_ARGS, KEY_BINDING, False);
}

void CMD_PointerKey(F_CMD_ARGS)
{
  binding_cmd(F_PASS_ARGS, KEY_BINDING, True);
}

void CMD_Mouse(F_CMD_ARGS)
{
  binding_cmd(F_PASS_ARGS, MOUSE_BINDING, False);
}

#ifdef HAVE_STROKE
void CMD_Stroke(F_CMD_ARGS)
{
  binding_cmd(F_PASS_ARGS, STROKE_BINDING, False);
}
#endif /* HAVE_STROKE */

/* Removes all unused modifiers from in_modifiers */
unsigned int MaskUsedModifiers(unsigned int in_modifiers)
{
  return in_modifiers & ~mods_unused;
}

unsigned int GetUnusedModifiers(void)
{
  return mods_unused;
}

/***********************************************************************
 *
 *	SetModifierLocks : declares which X modifiers are actually locks
 *		and should be ignored when testing mouse/key binding modifiers.
 *	syntax :
 *		ModifierLocks [S][L][C][M][1][2][3][4][5]
 *
 *	S is shift
 *	L is caps lock
 *	C is control
 *	M is meta, the same as mod1
 *	1-5 are mod1-mod5
 *	The default is L25 (ie. mask out caps lock, num lock & scroll lock
 *	for XFree86 3.3.3.1
 *
 *	Benoit TRIQUET <benoit@adsl-216-100-248-201.dsl.pacbell.net> 2/21/99
 ***********************************************************************/
void CMD_IgnoreModifiers(F_CMD_ARGS)
{
  char *token;

  mods_unused = 0;
  GetNextToken(action, &token);
  if (!token)
    return;

  if (StrEquals(token, "default"))
  {
    free(token);
    mods_unused = MODS_UNUSED_DEFAULT;
    return;
  }

  if (ParseModifiers(token, &mods_unused))
    fvwm_msg(ERR, "ignore_modifiers", "illegal modifier in line %s\n", action);

  free(token);
  return;
}


syntax highlighted by Code2HTML, v. 0.9.1