/* 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
 */

/****************************************************************************
 * This module is all original code
 * by Rob Nation
 * Copyright 1993, Robert Nation
 *     You may use this code for any purpose, as long as the original
 *     copyright remains in the source code and all documentation
 ****************************************************************************/

/***********************************************************************
 *
 * fvwm built-in functions and complex functions
 *
 ***********************************************************************/

#include "config.h"

#include <stdio.h>

#include <X11/keysym.h>
#include <X11/Intrinsic.h>

#include "libs/fvwmlib.h"
#include "libs/Parse.h"
#include "fvwm.h"
#include "commands.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "events.h"
#include "modconf.h"
#include "module_interface.h"
#include "misc.h"
#include "screen.h"
#include "repeat.h"
#include "read.h"
#include "virtual.h"
#include "libs/Colorset.h"

extern XEvent Event;
extern FvwmWindow *Tmp_win;
extern char const * const Fvwm_VersionInfo;

/* forward declarations */
static void execute_complex_function(F_CMD_ARGS, Bool *desperate);

/* dummies */
void CMD_Silent(F_CMD_ARGS)
{
  return;
}
void CMD_Function(F_CMD_ARGS)
{
  return;
}
void CMD_Title(F_CMD_ARGS)
{
  return;
}

/*
 * be sure to keep this list properly ordered for bsearch routine!
 */

#define PRE_REPEAT       "repeat"
#define PRE_SILENT       "silent"

/* The function names in the first field *must* be in lowercase or else the
 * function cannot be called.  The func parameter of the macro is also used
 * for parsing by modules that rely on the old case of the commands.
 */
#define CMD_ENTRY(cmd, func, cmd_id, flags) \
 {cmd, func, cmd_id, flags}
/* the next line must be blank for modules to properly parse this file */

static const func_type func_config[] =
{
  CMD_ENTRY("+", CMD_Plus, F_ADDMENU2, 0),
#ifdef MULTISTYLE
  CMD_ENTRY("addbuttonstyle", CMD_AddButtonStyle, F_ADD_BUTTON_STYLE,
	    FUNC_DECOR),
  CMD_ENTRY("addtitlestyle", CMD_AddTitleStyle, F_ADD_TITLE_STYLE, FUNC_DECOR),
#endif /* MULTISTYLE */
#ifdef USEDECOR
  CMD_ENTRY("addtodecor", CMD_AddToDecor, F_ADD_DECOR, 0),
#endif /* USEDECOR */
  CMD_ENTRY("addtofunc", CMD_AddToFunc, F_ADDFUNC, FUNC_ADD_TO),
  CMD_ENTRY("addtomenu", CMD_AddToMenu, F_ADDMENU, 0),
  CMD_ENTRY("all", CMD_All, F_ALL, 0),
  CMD_ENTRY("animatedmove", CMD_AnimatedMove, F_ANIMATED_MOVE,
	    FUNC_NEEDS_WINDOW),
  CMD_ENTRY("beep", CMD_Beep, F_BEEP, 0),
  CMD_ENTRY("borderstyle", CMD_BorderStyle, F_BORDERSTYLE, FUNC_DECOR),
  CMD_ENTRY("bugopts", CMD_BugOpts, F_BUG_OPTS, 0),
  CMD_ENTRY("busycursor", CMD_BusyCursor, F_BUSY_CURSOR, 0),
  CMD_ENTRY("buttonstate", CMD_ButtonState, F_BUTTON_STATE, 0),
  CMD_ENTRY("buttonstyle", CMD_ButtonStyle, F_BUTTON_STYLE, FUNC_DECOR),
#ifdef USEDECOR
  CMD_ENTRY("changedecor", CMD_ChangeDecor, F_CHANGE_DECOR, FUNC_NEEDS_WINDOW),
#endif /* USEDECOR */
  CMD_ENTRY("changemenustyle", CMD_ChangeMenuStyle, F_CHANGE_MENUSTYLE, 0),
  CMD_ENTRY("clicktime", CMD_ClickTime, F_CLICK, 0),
  CMD_ENTRY("close", CMD_Close, F_CLOSE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("colorlimit", CMD_ColorLimit, F_COLOR_LIMIT, 0),
  CMD_ENTRY("colormapfocus", CMD_ColormapFocus, F_COLORMAP_FOCUS, 0),
  CMD_ENTRY("colorset", CMD_Colorset, F_NOP, 0),
  CMD_ENTRY("copymenustyle", CMD_CopyMenuStyle, F_COPY_MENU_STYLE, 0),
  CMD_ENTRY("current", CMD_Current, F_CURRENT, 0),
  CMD_ENTRY("cursormove", CMD_CursorMove, F_MOVECURSOR, 0),
  CMD_ENTRY("cursorstyle", CMD_CursorStyle, F_CURSOR_STYLE, 0),
  CMD_ENTRY("defaultcolors", CMD_DefaultColors, F_DFLT_COLORS, 0),
  CMD_ENTRY("defaultcolorset", CMD_DefaultColorset, F_DFLT_COLORSET, 0),
  CMD_ENTRY("defaultfont", CMD_DefaultFont, F_DFLT_FONT, 0),
  CMD_ENTRY("defaulticon", CMD_DefaultIcon, F_DFLT_ICON, 0),
  CMD_ENTRY("defaultlayers", CMD_DefaultLayers, F_DFLT_LAYERS, 0),
  CMD_ENTRY("delete", CMD_Delete, F_DELETE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("desk", CMD_Desk, F_GOTO_DESK, 0),
  CMD_ENTRY("desktopsize", CMD_DesktopSize, F_SETDESK, 0),
  CMD_ENTRY("destroy", CMD_Destroy, F_DESTROY, FUNC_NEEDS_WINDOW),
#ifdef USEDECOR
  CMD_ENTRY("destroydecor", CMD_DestroyDecor, F_DESTROY_DECOR, 0),
#endif /* USEDECOR */
  CMD_ENTRY("destroyfunc", CMD_DestroyFunc, F_DESTROY_FUNCTION, 0),
  CMD_ENTRY("destroymenu", CMD_DestroyMenu, F_DESTROY_MENU, 0),
  CMD_ENTRY("destroymenustyle", CMD_DestroyMenuStyle, F_DESTROY_MENUSTYLE, 0),
  CMD_ENTRY("destroymoduleconfig", CMD_DestroyModuleConfig, F_DESTROY_MOD, 0),
  CMD_ENTRY("destroystyle", CMD_DestroyStyle, F_DESTROY_STYLE, 0),
  CMD_ENTRY("direction", CMD_Direction, F_DIRECTION, 0),
  CMD_ENTRY("echo", CMD_Echo, F_ECHO, 0),
  CMD_ENTRY("edgeresistance", CMD_EdgeResistance, F_EDGE_RES, 0),
  CMD_ENTRY("edgescroll", CMD_EdgeScroll, F_EDGE_SCROLL, 0),
  CMD_ENTRY("edgethickness", CMD_EdgeThickness, F_NOP, 0),
  CMD_ENTRY("emulate", CMD_Emulate, F_EMULATE, 0),
  CMD_ENTRY("escapefunc", CMD_EscapeFunc, F_ESCAPE_FUNC, 0),
  CMD_ENTRY("exec", CMD_Exec, F_EXEC, 0),
  CMD_ENTRY("execuseshell", CMD_ExecUseShell, F_EXEC_SETUP, 0),
  CMD_ENTRY("fakeclick", CMD_FakeClick, F_FAKE_CLICK, 0),
  CMD_ENTRY("flipfocus", CMD_FlipFocus, F_FLIP_FOCUS, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("focus", CMD_Focus, F_FOCUS, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("function", CMD_Function, F_FUNCTION, 0),
  CMD_ENTRY("globalopts", CMD_GlobalOpts, F_GLOBAL_OPTS, 0),
#ifdef GNOME
  CMD_ENTRY("gnomebutton", CMD_GnomeButton, F_MOUSE, 0),
  CMD_ENTRY("gnomeshowdesks", CMD_GnomeShowDesks, F_GOTO_DESK, 0),
#endif /* GNOME */
  CMD_ENTRY("gotodesk", CMD_GotoDesk, F_GOTO_DESK, 0),
  CMD_ENTRY("gotodeskandpage", CMD_GotoDeskAndPage, F_GOTO_DESK, 0),
  CMD_ENTRY("gotopage", CMD_GotoPage, F_GOTO_PAGE, 0),
  CMD_ENTRY("hidegeometrywindow", CMD_HideGeometryWindow, F_HIDEGEOMWINDOW, 0),
  CMD_ENTRY("hilightcolor", CMD_HilightColor, F_HICOLOR, 0),
  CMD_ENTRY("hilightcolorset", CMD_HilightColorset, F_HICOLORSET, 0),
  CMD_ENTRY("iconfont", CMD_IconFont, F_ICONFONT, 0),
  CMD_ENTRY("iconify", CMD_Iconify, F_ICONIFY, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("iconpath", CMD_IconPath, F_ICON_PATH, 0),
  CMD_ENTRY("ignoremodifiers", CMD_IgnoreModifiers, F_IGNORE_MODIFIERS, 0),
  CMD_ENTRY("imagepath", CMD_ImagePath, F_IMAGE_PATH, 0),
  CMD_ENTRY("key", CMD_Key, F_KEY, 0),
  CMD_ENTRY("killmodule", CMD_KillModule, F_KILL_MODULE, 0),
  CMD_ENTRY("layer", CMD_Layer, F_LAYER, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("lower", CMD_Lower, F_LOWER, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("maximize", CMD_Maximize, F_MAXIMIZE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("menu", CMD_Menu, F_STAYSUP, 0),
  CMD_ENTRY("menustyle", CMD_MenuStyle, F_MENUSTYLE, 0),
  CMD_ENTRY("module", CMD_Module, F_MODULE, 0),
  CMD_ENTRY("modulepath", CMD_ModulePath, F_MODULE_PATH, 0),
  CMD_ENTRY("modulesynchronous", CMD_ModuleSynchronous, F_MODULE_SYNC, 0),
  CMD_ENTRY("moduletimeout", CMD_ModuleTimeout, F_NOP, 0),
  CMD_ENTRY("mouse", CMD_Mouse, F_MOUSE, 0),
  CMD_ENTRY("move", CMD_Move, F_MOVE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("movethreshold", CMD_MoveThreshold, F_MOVE_THRESHOLD, 0),
  CMD_ENTRY("movetodesk", CMD_MoveToDesk, F_MOVE_TO_DESK, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("movetopage", CMD_MoveToPage, F_MOVE_TO_PAGE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("movetoscreen", CMD_MoveToScreen, F_MOVE_TO_SCREEN,
	    FUNC_NEEDS_WINDOW),
  CMD_ENTRY("next", CMD_Next, F_NEXT, 0),
  CMD_ENTRY("none", CMD_None, F_NONE, 0),
  CMD_ENTRY("nop", CMD_Nop, F_NOP, FUNC_DONT_REPEAT),
  CMD_ENTRY("opaquemovesize", CMD_OpaqueMoveSize, F_OPAQUE, 0),
  CMD_ENTRY("pick", CMD_Pick, F_PICK, 0),
  CMD_ENTRY("piperead", CMD_PipeRead, F_READ, 0),
  CMD_ENTRY("pixmappath", CMD_PixmapPath, F_PIXMAP_PATH, 0),
  CMD_ENTRY("placeagain", CMD_PlaceAgain, F_PLACEAGAIN, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("pointerkey", CMD_PointerKey, F_POINTERKEY, 0),
  CMD_ENTRY("popup", CMD_Popup, F_POPUP, 0),
  CMD_ENTRY("prev", CMD_Prev, F_PREV, 0),
  CMD_ENTRY("quit", CMD_Quit, F_QUIT, 0),
  CMD_ENTRY("quitscreen", CMD_QuitScreen, F_QUIT_SCREEN, 0),
  CMD_ENTRY("quitsession", CMD_QuitSession, F_QUIT_SESSION, 0),
  CMD_ENTRY("raise", CMD_Raise, F_RAISE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("raiselower", CMD_RaiseLower, F_RAISELOWER, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("read", CMD_Read, F_READ, 0),
  CMD_ENTRY("recapture", CMD_Recapture, F_RECAPTURE, 0),
  CMD_ENTRY("recapturewindow", CMD_RecaptureWindow, F_RECAPTURE_WINDOW, 0),
  CMD_ENTRY("refresh", CMD_Refresh, F_REFRESH, 0),
  CMD_ENTRY("refreshwindow", CMD_RefreshWindow, F_REFRESH, FUNC_NEEDS_WINDOW),
  CMD_ENTRY(PRE_REPEAT, CMD_Repeat, F_REPEAT, FUNC_DONT_REPEAT),
  CMD_ENTRY("resize", CMD_Resize, F_RESIZE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("resizemove", CMD_ResizeMove, F_RESIZEMOVE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("restart", CMD_Restart, F_RESTART, 0),
  CMD_ENTRY("savequitsession", CMD_SaveQuitSession, F_SAVE_QUIT_SESSION, 0),
  CMD_ENTRY("savesession", CMD_SaveSession, F_SAVE_SESSION, 0),
  CMD_ENTRY("scroll", CMD_Scroll, F_SCROLL, 0),
  CMD_ENTRY("send_configinfo", CMD_Send_ConfigInfo, F_CONFIG_LIST,
	    FUNC_DONT_REPEAT),
  CMD_ENTRY("send_windowlist", CMD_Send_WindowList, F_SEND_WINDOW_LIST,
	    FUNC_DONT_REPEAT),
  CMD_ENTRY("sendtomodule", CMD_SendToModule, F_SEND_STRING, FUNC_DONT_REPEAT),
  CMD_ENTRY("set_mask", CMD_set_mask, F_SET_MASK, FUNC_DONT_REPEAT),
  CMD_ENTRY("set_nograb_mask", CMD_set_nograb_mask, F_SET_NOGRAB_MASK,
	    FUNC_DONT_REPEAT),
  CMD_ENTRY("set_sync_mask", CMD_set_sync_mask, F_SET_SYNC_MASK,
	    FUNC_DONT_REPEAT),
  CMD_ENTRY("setanimation", CMD_SetAnimation, F_SET_ANIMATION, 0),
  CMD_ENTRY("setenv", CMD_SetEnv, F_SETENV, 0),
  CMD_ENTRY(PRE_SILENT, CMD_Silent, F_SILENT, 0),
  CMD_ENTRY("snapattraction", CMD_SnapAttraction, F_SNAP_ATT, 0),
  CMD_ENTRY("snapgrid", CMD_SnapGrid, F_SNAP_GRID, 0),
  CMD_ENTRY("stick", CMD_Stick, F_STICK, FUNC_NEEDS_WINDOW),
#ifdef HAVE_STROKE
  CMD_ENTRY("stroke", CMD_Stroke, F_STROKE, 0),
  CMD_ENTRY("strokefunc", CMD_StrokeFunc, F_STROKE_FUNC, 0),
#endif /* HAVE_STROKE */
  CMD_ENTRY("style", CMD_Style, F_STYLE, 0),
  CMD_ENTRY("thiswindow", CMD_ThisWindow, F_THISWINDOW, 0),
  CMD_ENTRY("title", CMD_Title, F_TITLE, 0),
  CMD_ENTRY("titlestyle", CMD_TitleStyle, F_TITLESTYLE, FUNC_DECOR),
  CMD_ENTRY("unsetenv", CMD_UnsetEnv, F_SETENV, 0),
  CMD_ENTRY("updatedecor", CMD_UpdateDecor, F_UPDATE_DECOR, 0),
  CMD_ENTRY("updatestyles", CMD_UpdateStyles, F_UPDATE_STYLES, 0),
  CMD_ENTRY("wait", CMD_Wait, F_WAIT, 0),
  CMD_ENTRY("warptowindow", CMD_WarpToWindow, F_WARP, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("windowfont", CMD_WindowFont, F_WINDOWFONT, 0),
  CMD_ENTRY("windowid", CMD_WindowId, F_WINDOWID, 0),
  CMD_ENTRY("windowlist", CMD_WindowList, F_WINDOWLIST, 0),
  CMD_ENTRY("windowshade", CMD_WindowShade, F_WINDOW_SHADE, FUNC_NEEDS_WINDOW),
  CMD_ENTRY("windowshadeanimate", CMD_WindowShadeAnimate, F_SHADE_ANIMATE, 0),
  CMD_ENTRY("xinerama", CMD_Xinerama, F_XINERAMA, 0),
  CMD_ENTRY("xineramaprimaryscreen", CMD_XineramaPrimaryScreen,
	    F_XINERAMAPRIMARYSCREEN, 0),
  CMD_ENTRY("xineramasls", CMD_XineramaSls, F_XINERAMASLS, 0),
  CMD_ENTRY("xineramaslssize", CMD_XineramaSlsSize, F_XINERAMASLSSIZE, 0),
  CMD_ENTRY("xorpixmap", CMD_XorPixmap, F_XOR, 0),
  CMD_ENTRY("xorvalue", CMD_XorValue, F_XOR, 0),
  {"",0,0,0}
};


/*
** do binary search on func list
*/
static int func_comp(const void *a, const void *b)
{
  return (strcmp((char *)a, ((func_type *)b)->keyword));
}

static const func_type *FindBuiltinFunction(char *func)
{
  func_type *ret_func;
  char *temp;
  char *s;

  if (!func || func[0] == 0)
    return NULL;

  /* since a lot of lines in a typical rc are probably menu/func continues: */
  if (func[0]=='+' || (func[0] == ' ' && func[1] == '+'))
    return &(func_config[0]);

  temp = safestrdup(func);
  for (s = temp; *s != 0; s++)
    if (isupper(*s))
      *s = tolower(*s);

  ret_func = (func_type *)bsearch
    (temp, func_config,
     sizeof(func_config) / sizeof(func_type) - 1,
     sizeof(func_type), func_comp);
  free(temp);

  return ret_func;
}

static char *function_vars[] =
{
  "fg.cs",
  "bg.cs",
  "hilight.cs",
  "shadow.cs",
  "desk.width",
  "desk.height",
  "vp.x",
  "vp.y",
  "vp.width",
  "vp.height",
  "page.nx",
  "page.ny",
  "w.x",
  "w.y",
  "w.width",
  "w.height",
  "screen",
  "desk.n",
  "w.id",
  "w.name",
  "w.iconname",
  "w.class",
  "w.resource",
  "version.num",
  "version.info",
  "version.line",
  "desk.pagesx",
  "desk.pagesy",
  NULL
};

static signed int expand_extended_var(
  char *var_name, char *output, FvwmWindow *tmp_win)
{
  char *rest;
  char dummy[64];
  char *target = (output) ? output : dummy;
  int cs = -1;
  int n;
  int i;
  Pixel pixel = 0;
  int val = -12345678;
  Bool is_numeric = False;
  const char *string = NULL;

  /* allow partial matches for *.cs variables */
  switch ((i = GetTokenIndex(var_name, function_vars, -1, &rest)))
  {
  case 0:
  case 1:
  case 2:
  case 3:
    if (!isdigit(*rest) || (*rest == '0' && *(rest + 1) != 0))
      /* not a non-negative integer without leading zeros */
      return -1;
    if (sscanf(rest, "%d%n", &cs, &n) < 1)
      return -1;
    if (*(rest + n) != 0)
      /* trailing characters */
      return -1;
    if (cs < 0)
      return -1;
    AllocColorset(cs);
    switch (i)
    {
    case 0:
      /* fg.cs */
      pixel = Colorset[cs].fg;
      break;
    case 1:
      /* bg.cs */
      pixel = Colorset[cs].bg;
      break;
    case 2:
      /* hilight.cs */
      pixel = Colorset[cs].hilite;
      break;
    case 3:
      /* shadow.cs */
      pixel = Colorset[cs].shadow;
      break;
    }
    return pixel_to_color_string(dpy, Pcmap, pixel, target, False);
  default:
    break;
  }

  /* only exact matches for all other variables */
  switch ((i = GetTokenIndex(var_name, function_vars, 0, &rest)))
  {
  case 4:
    /* desk.width */
    is_numeric = True;
    val = Scr.VxMax + Scr.MyDisplayWidth;
    break;
  case 5:
    /* desk.height */
    is_numeric = True;
    val = Scr.VyMax + Scr.MyDisplayHeight;
    break;
  case 6:
    /* vp.x */
    is_numeric = True;
    val = Scr.Vx;
    break;
  case 7:
    /* vp.y */
    is_numeric = True;
    val = Scr.Vy;
    break;
  case 8:
    /* vp.width */
    is_numeric = True;
    val = Scr.MyDisplayWidth;
    break;
  case 9:
    /* vp.height */
    is_numeric = True;
    val = Scr.MyDisplayHeight;
    break;
  case 10:
    /* page.nx */
    is_numeric = True;
    val = (int)(Scr.Vx / Scr.MyDisplayWidth);
    break;
  case 11:
    /* page.ny */
    is_numeric = True;
    val = (int)(Scr.Vy / Scr.MyDisplayHeight);
    break;

  case 12:
  case 13:
  case 14:
  case 15:
    if (!tmp_win || IS_ICONIFIED(tmp_win))
      return -1;
    else
    {
      is_numeric = True;
      switch (i)
      {
      case 12:
	/* w.x */
	val = tmp_win->frame_g.x;
	break;
      case 13:
	/* w.y */
	val = tmp_win->frame_g.y;
	break;
      case 14:
	/* w.width */
	val = tmp_win->frame_g.width;
	break;
      case 15:
	/* w.height */
	val = tmp_win->frame_g.height;
	break;
      default:
	return -1;
      }
    }
    break;
  case 16:
    /* screen */
    is_numeric = True;
    val = Scr.screen;
    break;
  case 17:
    /* desk.n */
    is_numeric = True;
    val = Scr.CurrentDesk;
    break;
  case 18:
    /* w.id */
    if (tmp_win)
    {
      sprintf(target, "0x%x", (unsigned int)tmp_win->w);
      string = target;
    }
    break;
  case 19:
    /* w.name */
    if (tmp_win)
    {
      string = tmp_win->name;
    }
    break;
  case 20:
    /* w.iconname */
    if (tmp_win)
    {
      string = tmp_win->icon_name;
    }
    break;
  case 21:
    /* w.class */
    if (tmp_win)
    {
      string = tmp_win->class.res_class;
    }
    break;
  case 22:
    /* w.resource */
    if (tmp_win)
    {
      string = tmp_win->class.res_name;
    }
    break;
  case 23:
    /* version.num */
    string = VERSION;
    break;
  case 24:
    /* version.info */
    string = "";
    break;
  case 25:
    /* version.line */
    string = Fvwm_VersionInfo;
    break;
  case 26:
    /* desk.pagesx */
    is_numeric = True;
    val = (int)(Scr.VxMax / Scr.MyDisplayWidth) + 1;
    break;
  case 27:
    /* desk.pagesy */
    is_numeric = True;
    val = (int)(Scr.VyMax / Scr.MyDisplayHeight) + 1;
    break;
  default:
    /* unknown variable - try to find it in the environment */
    string = getenv(var_name);
  }
  if (is_numeric)
  {
    sprintf(target, "%d", val);
    return strlen(target);
  }
  else
  {
    if (output && string)
    {
      strcpy(output, string);
    }
    return string ? strlen(string) : -1;
  }
}

static char *expand(
  char *input, char *arguments[], FvwmWindow *tmp_win, Bool addto, Bool ismod)
{
  int l,i,l2,n,k,j,m;
  int xlen;
  char *out;
  char *var;
  const char *string = NULL;
  Bool is_string = False;

  l = strlen(input);
  l2 = l;

  if(input[0] == '+' && Scr.last_added_item.type == ADDED_FUNCTION)
  {
    addto = 1;
  }

  /* Calculate best guess at length of expanded string */
  i=0;
  while(i<l)
  {
    if (input[i] == '$' && (!ismod || !isalpha(input[i+1])))
    {
      switch (input[i+1])
      {
      case '$':
	/* skip the second $, it is not a part of variable */
	i++;
	break;
      case '[':
	/* extended variables */
	var = &input[i+2];
	m = i + 2;
	while (m < l && input[m] != ']' && input[m])
	  m++;
	if (input[m] == ']')
	{
	  input[m] = 0;
	  /* handle variable name */
	  k = strlen(var);
	  if (!addto)
	  {
	    xlen = expand_extended_var(var, NULL, tmp_win);
	    if (xlen >= 0)
	      l2 += xlen - (k + 2);
	  }
	  i += k + 2;
	  input[m] = ']';
	}
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '*':
	if (input[i+1] == '*')
	  n = 0;
	else
	  n = input[i+1] - '0' + 1;
	if(arguments[n] != NULL)
	{
	  l2 += strlen(arguments[n])-2;
	  i++;
	}
	break;
      case '.':
	string = get_current_read_dir();
	break;
      case 'w':
      case 'd':
      case 'x':
      case 'y':
	l2 += 16;
	i++;
	break;
      case 'c':
	if (tmp_win && tmp_win->class.res_class &&
	    tmp_win->class.res_class[0])
	{
	  string = tmp_win->class.res_class;
	}
	break;
      case 'r':
	if (tmp_win && tmp_win->class.res_name &&
	    tmp_win->class.res_name[0])
	{
	  string = tmp_win->class.res_name;
	}
	break;
      case 'n':
	if (tmp_win && tmp_win->name && tmp_win->name[0])
	{
	  string = tmp_win->name;
	}
	break;
      case 'v':
	if (Fvwm_VersionInfo)
	{
	  l2 += strlen(Fvwm_VersionInfo) + 2;
	}
	break;
      }
      if (string)
      {
	for (k = 0; string[k] != 0; k++, l2++)
	{
	  if (string[k] == '\'')
	    l2++;
	}
	string = NULL;
      }

    }
    i++;
  }

  /* Actually create expanded string */
  i=0;
  out = safemalloc(l2+1);
  j=0;
  while(i<l)
  {
    if (input[i] == '$' && (!ismod || !isalpha(input[i+1])))
    {
      switch (input[i+1])
      {
      case '[':
	/* extended variables */
	if (addto)
	{
	  /* Don't expand these in an 'AddToFunc' command */
	  out[j++] = input[i];
	  break;
	}
	var = &input[i+2];
	m = i + 2;
	while (m < l && input[m] != ']' && input[m])
	  m++;
	if (input[m] == ']')
	{
	  input[m] = 0;
	  /* handle variable name */
	  k = strlen(var);
	  xlen = expand_extended_var(var, &out[j], tmp_win);
	  input[m] = ']';
	  if (xlen >= 0)
	  {
	    j += xlen;
	    i += k + 2;
	  }
	  else
	  {
	    /* copy the whole string in square brackets */
	    for ( ; i <= m; i++, j++)
	    {
		    out[j] = input[i];
	    }
	    i--;
	  }
	}
	else
	{
	  out[j++] = input[i];
	}
	break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case '*':
	if (input[i+1] == '*')
	  n = 0;
	else
	  n = input[i+1] - '0' + 1;
	if(arguments[n] != NULL)
	{
	  for(k=0;arguments[n][k];k++)
	    out[j++] = arguments[n][k];
	  i++;
	}
	else if (addto == 1)
	{
	  out[j++] = '$';
	}
	else
	{
	  i++;
#if 0
	  /* DV: Whoa! Better live with the extra whitespace. */
	  if (isspace(input[i+1]))
	    i++; /*eliminates extra white space*/
#endif
	}
	break;
      case '.':
	string = get_current_read_dir();
	is_string = True;
	break;
      case 'w':
	if(tmp_win)
	  sprintf(&out[j],"0x%x",(unsigned int)tmp_win->w);
	else
	  sprintf(&out[j],"$w");
	j += strlen(&out[j]);
	i++;
	break;
      case 'd':
	sprintf(&out[j], "%d", Scr.CurrentDesk);
	j += strlen(&out[j]);
	i++;
	break;
      case 'x':
	sprintf(&out[j], "%d", Scr.Vx);
	j += strlen(&out[j]);
	i++;
	break;
      case 'y':
	sprintf(&out[j], "%d", Scr.Vy);
	j += strlen(&out[j]);
	i++;
	break;

      case 'c':
	if (tmp_win && tmp_win->class.res_class &&
	    tmp_win->class.res_class[0])
	{
	  string = tmp_win->class.res_class;
	}
	is_string = True;
	break;
      case 'r':
	if (tmp_win && tmp_win->class.res_name &&
	    tmp_win->class.res_name[0])
	{
	  string = tmp_win->class.res_name;
	}
	is_string = True;
	break;
      case 'n':
	if (tmp_win && tmp_win->name && tmp_win->name[0])
	{
	  string = tmp_win->name;
	}
	is_string = True;
	break;
      case 'v':
	sprintf(&out[j], "%s", (Fvwm_VersionInfo) ? Fvwm_VersionInfo : "");
	j += strlen(&out[j]);
	i++;
	break;
      case '$':
	out[j++] = '$';
	i++;
	break;
      default:
	out[j++] = input[i];
	break;
      } /* switch */
      if (is_string && string)
      {
#if 0
	out[j++] = '\'';
	for(k = 0; string[k]; k++)
	{
	  if (string[k] == '\'')
	    out[j++] = '\\';
	  out[j++] = string[k];
	}
	out[j++] = '\'';
#endif
	j = QuoteString(&out[j], string) - out;
	string = NULL;
	is_string = False;
	i++;
      }
      else if (is_string)
      {
	out[j++] = '$';
	is_string = False;
      }
    } /* if '$' */
    else
      out[j++] = input[i];
    i++;
  }
  out[j] = 0;
  return out;
}

/*****************************************************************************
 *
 * Builtin which determines if the button press was a click or double click...
 * Waits Scr.ClickTime, or until it is evident that the user is not
 * clicking, but is moving the cursor
 *
 ****************************************************************************/
static cfunc_action_type CheckActionType(
  int x, int y, XEvent *d, Bool may_time_out, Bool is_button_pressed,
  int *ret_button)
{
  int xcurrent,ycurrent,total = 0;
  Time t0;
  int dist;
  XEvent old_event;
  extern Time lastTimestamp;
  Bool do_sleep = False;

  xcurrent = x;
  ycurrent = y;
  t0 = lastTimestamp;
  dist = Scr.MoveThreshold;

  while ((total < Scr.ClickTime && lastTimestamp - t0 < Scr.ClickTime) ||
	 !may_time_out)
  {
    if (!(x - xcurrent <= dist && xcurrent - x <= dist &&
	  y - ycurrent <= dist && ycurrent - y <= dist))
    {
      return (is_button_pressed) ? CF_MOTION : CF_TIMEOUT;
    }

    if (do_sleep)
    {
      usleep(20000);
    }
    else
    {
      usleep(1);
      do_sleep = 1;
    }
    total += 20;
    if (XCheckMaskEvent(dpy, ButtonReleaseMask|ButtonMotionMask|
			PointerMotionMask|ButtonPressMask|ExposureMask, d))
    {
      do_sleep = 0;
      StashEventTime(d);
      switch (d->xany.type)
      {
      case ButtonRelease:
	*ret_button = d->xbutton.button;
	return CF_CLICK;
      case MotionNotify:
	if (d->xmotion.same_screen == False)
	{
	  break;
	}
	if ((d->xmotion.state & DEFAULT_ALL_BUTTONS_MASK) ||
            !is_button_pressed)
	{
	  xcurrent = d->xmotion.x_root;
	  ycurrent = d->xmotion.y_root;
	}
        else
	{
	  return CF_CLICK;
	}
	break;
      case ButtonPress:
	*ret_button = d->xbutton.button;
	if (may_time_out)
	  is_button_pressed = True;
	break;
      case Expose:
	/* must handle expose here so that raising a window with "I" works */
	memcpy(&old_event, &Event, sizeof(XEvent));
	memcpy(&Event, d, sizeof(XEvent));
	/* note: handling Expose events will never modify the global Tmp_win */
	DispatchEvent(True);
	memcpy(&Event, &old_event, sizeof(XEvent));
	break;
      default:
	/* can't happen */
	break;
      }
    }
  }

  return (is_button_pressed) ? CF_HOLD : CF_TIMEOUT;
}


/***********************************************************************
 *
 *  Procedure:
 *	execute_function - execute a fvwm built in function
 *
 *  Inputs:
 *	Action	- the action to execute
 *	tmp_win	- the fvwm window structure
 *	eventp	- pointer to the event that caused the function
 *	context - the context in which the button was pressed
 *
 ***********************************************************************/
void execute_function(exec_func_args_type *efa)
{
  static unsigned int func_depth = 0;
  FvwmWindow *s_Tmp_win = Tmp_win;
  Window w;
  int j;
  char *function;
  char *taction;
  char *trash;
  char *trash2;
  char *expaction = NULL;
  char *arguments[11];
  const func_type *bif;
  Bool set_silent;
  Bool must_free_string = False;
  Bool must_free_function = False;

  if (!efa->action)
  {
    /* impossibly short command */
    return;
  }
  /* ignore whitespace at the beginning of all config lines */
  efa->action = SkipSpaces(efa->action, NULL, 0);
  if (!efa->action || efa->action[0] == 0)
  {
    /* impossibly short command */
    return;
  }
  if (efa->action[0] == '#')
  {
    /* a comment */
    return;
  }

  func_depth++;
  if (efa->args)
  {
    for(j=0;j<11;j++)
      arguments[j] = efa->args[j];
  }
  else
  {
    for(j=0;j<11;j++)
      arguments[j] = NULL;
  }

  if (efa->tmp_win == NULL)
  {
    if (efa->flags.is_window_unmanaged)
      w = efa->win;
    else
      w = Scr.Root;
  }
  else
  {
    if (efa->eventp)
    {
      w = GetSubwindowFromEvent(dpy, efa->eventp);
      if (!w)
      {
	w = efa->eventp->xany.window;
      }
    }
    else
    {
      w = efa->tmp_win->w;
    }
  }

  set_silent = False;
  if (efa->action[0] == '-')
  {
    efa->flags.exec |= FUNC_DONT_EXPAND_COMMAND;
    efa->action++;
  }

  taction = efa->action;
  /* parse prefixes */
  trash = PeekToken(taction, &trash2);
  while (trash)
  {
    if (StrEquals(trash, PRE_SILENT))
    {
      if (Scr.flags.silent_functions == 0)
      {
	set_silent = 1;
	Scr.flags.silent_functions = 1;
      }
      taction = trash2;
      trash = PeekToken(taction, &trash2);
    }
    else
      break;
  }
  if (taction == NULL)
  {
    if (set_silent)
      Scr.flags.silent_functions = 0;
    func_depth--;
    return;
  }

  function = PeekToken(taction, NULL);
  if (function)
    function = expand(function, arguments, efa->tmp_win, False, False);
  if (function && function[0] != '*')
  {
#if 1
    /* DV: with this piece of code it is impossible to have a complex function
     * with embedded whitespace that begins with a builtin function name, e.g. a
     * function "echo hello". */
    /* DV: ... and without it some of the complex functions will fail */
    char *tmp = function;

    while (*tmp && !isspace(*tmp))
      tmp++;
    *tmp = 0;
#endif
    bif = FindBuiltinFunction(function);
    must_free_function = True;
  }
  else
  {
    bif = NULL;
    if (function)
    {
      free(function);
    }
    function = "";
  }

#ifdef USEDECOR
  if (Scr.cur_decor && Scr.cur_decor != &Scr.DefaultDecor &&
      (!bif || !(bif->flags & FUNC_DECOR)))
  {
    fvwm_msg(
      ERR, "execute_function",
      "Command can not be added to a decor; executing command now: '%s'",
      efa->action);
  }
#endif

  if (!(efa->flags.exec & FUNC_DONT_EXPAND_COMMAND))
  {
    expaction = expand(taction, arguments, efa->tmp_win,
      (bif) ? !!(bif->flags & FUNC_ADD_TO) : False, taction[0] == '*');
    if (func_depth <= 1)
      must_free_string = set_repeat_data(expaction, REPEAT_COMMAND, bif);
    else
      must_free_string = True;
  }
  else
  {
    expaction = taction;
  }

#ifdef FVWM_COMMAND_LOG
  fvwm_msg(INFO, "LOG", "%s", expaction);
#endif

  /* Note: the module config command, "*" can not be handled by the
   * regular command table because there is no required white space after
   * the asterisk. */
  if (expaction[0] == '*')
  {
#ifdef USEDECOR
    if (Scr.cur_decor && Scr.cur_decor != &Scr.DefaultDecor)
    {
      fvwm_msg(
	WARN, "execute_function",
	"Command can not be added to a decor; executing command now: '%s'",
	expaction);
    }
#endif
    /* process a module config command */
    ModuleConfig(expaction);
  }
  else
  if (bif && bif->func_type != F_FUNCTION)
  {
    char *runaction;

    runaction = SkipNTokens(expaction, 1);
    bif->action(efa->eventp,w,efa->tmp_win,efa->context,runaction,&efa->module);
  }
  else
  {
    Bool desperate = 1;
    char *runaction;

    if (bif)
    {
      /* strip "function" command */
      runaction = SkipNTokens(expaction, 1);
    }
    else
    {
      runaction = expaction;
    }

    execute_complex_function(
      efa->eventp,w,efa->tmp_win,efa->context,runaction, &efa->module,
      &desperate);
    if (!bif && desperate)
    {
      if (executeModuleDesperate(
	efa->eventp, w, efa->tmp_win, efa->context, runaction, &efa->module) ==
	  -1 && *function != 0)
      {
	fvwm_msg(
	  ERR, "execute_function", "No such command '%s'", function);
      }
    }
  }

  if (set_silent)
    Scr.flags.silent_functions = 0;

  if (must_free_string)
  {
    free(expaction);
  }
  if (must_free_function)
  {
    free(function);
  }
  if (efa->flags.do_save_tmpwin)
  {
    if (check_if_fvwm_window_exists(s_Tmp_win))
      Tmp_win = s_Tmp_win;
    else
      Tmp_win = NULL;
  }
  func_depth--;

  return;
}

void old_execute_function(
  char *action, FvwmWindow *tmp_win, XEvent *eventp, unsigned long context,
  int Module, FUNC_FLAGS_TYPE exec_flags, char *args[])
{
  exec_func_args_type efa;

  memset(&efa, 0, sizeof(efa));
  efa.eventp = eventp;
  efa.tmp_win = tmp_win;
  efa.action = action;
  efa.args = args;
  efa.context = context;
  efa.module = Module;
  efa.flags.exec = exec_flags;
  execute_function(&efa);

  return;
}


/***********************************************************************
 *
 *  Procedure:
 *	DeferExecution - defer the execution of a function to the
 *	    next button press if the context is C_ROOT
 *
 *  Inputs:
 *      eventp  - pointer to XEvent to patch up
 *      w       - pointer to Window to patch up
 *      tmp_win - pointer to FvwmWindow Structure to patch up
 *	context	- the context in which the mouse button was pressed
 *	func	- the function to defer
 *	cursor	- the cursor to display while waiting
 *      finishEvent - ButtonRelease or ButtonPress; tells what kind of event to
 *                    terminate on.
 *
 ***********************************************************************/
int DeferExecution(
  XEvent *eventp, Window *w,FvwmWindow **tmp_win, unsigned long *context,
  cursor_type cursor, int FinishEvent)
{
  int done;
  int finished = 0;
  Window dummy;
  Window original_w;

  original_w = *w;

  if((*context != C_ROOT)&&(*context != C_NO_CONTEXT)&&(*tmp_win != NULL))
  {
    if((FinishEvent == ButtonPress)||((FinishEvent == ButtonRelease) &&
                                      (eventp->type != ButtonPress)))
    {
      return FALSE;
    }
    else if (FinishEvent == ButtonRelease)
    {
      /* We are only waiting until the user releases the button. Do not change
       * the cursor. */
      cursor = CRS_NONE;
    }
  }
  if (Scr.flags.silent_functions)
  {
    return True;
  }
  if(!GrabEm(cursor, GRAB_NORMAL))
  {
    XBell(dpy, 0);
    return True;
  }

  MyXGrabKeyboard(dpy);
  while (!finished)
  {
    done = 0;
    /* block until there is an event */
    XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask |
               ExposureMask | KeyPressMask | VisibilityChangeMask |
               ButtonMotionMask | PointerMotionMask
	       /* | EnterWindowMask | LeaveWindowMask*/, eventp);
    StashEventTime(eventp);

    if(eventp->type == KeyPress)
    {
      KeySym keysym = XLookupKeysym(&eventp->xkey,0);
      if (keysym == XK_Escape)
      {
	UngrabEm(GRAB_NORMAL);
	MyXUngrabKeyboard(dpy);
	return True;
      }
      Keyboard_shortcuts(eventp, NULL, NULL, NULL, FinishEvent);
    }
    if(eventp->type == FinishEvent)
    {
      finished = 1;
    }
    switch (eventp->type)
    {
    case KeyPress:
      if (eventp->type != FinishEvent)
	original_w = eventp->xany.window;
      /* fall through */
    case ButtonPress:
    case ButtonRelease:
      done = 1;
      break;
    default:
      break;
    }

    if(!done)
    {
      DispatchEvent(False);
    }
  }

  MyXUngrabKeyboard(dpy);
  UngrabEm(GRAB_NORMAL);
  *w = eventp->xany.window;
  if(((*w == Scr.Root)||(*w == Scr.NoFocusWin))
     && (eventp->xbutton.subwindow != (Window)0))
  {
    *w = eventp->xbutton.subwindow;
    eventp->xany.window = *w;
  }
  if (*w == Scr.Root)
  {
    *context = C_ROOT;
    XBell(dpy, 0);
    return TRUE;
  }
  if (XFindContext (dpy, *w, FvwmContext, (caddr_t *)tmp_win) == XCNOENT)
  {
    *tmp_win = NULL;
    XBell(dpy, 0);
    return (TRUE);
  }

  if(*w == (*tmp_win)->Parent)
  {
    *w = (*tmp_win)->w;
  }

  if(original_w == (*tmp_win)->Parent)
  {
    original_w = (*tmp_win)->w;
  }

  /* this ugly mess attempts to ensure that the release and press
   * are in the same window. */
  if(*w != original_w && original_w != Scr.Root &&
     original_w != None && original_w != Scr.NoFocusWin)
  {
    if (*w != (*tmp_win)->frame || original_w != (*tmp_win)->w)
    {
      *context = C_ROOT;
      XBell(dpy, 0);
      return TRUE;
    }
  }
  *context = GetContext(*tmp_win,eventp,&dummy);

  return FALSE;
}


void find_func_type(char *action, short *func_type, unsigned char *flags)
{
  int j, len = 0;
  char *endtok = action;
  Bool matched;
  int mlen;

  if (action)
  {
    while (*endtok&&!isspace((unsigned char)*endtok))++endtok;
    len = endtok - action;
    j=0;
    matched = FALSE;
    while((!matched)&&((mlen = strlen(func_config[j].keyword))>0))
      {
	if((mlen == len) &&
	   (strncasecmp(action,func_config[j].keyword,mlen)==0))
	  {
	    matched=TRUE;
	    /* found key word */
	    if (func_type)
	      *func_type = func_config[j].func_type;
	    if (flags)
	      *flags = func_config[j].flags;
	    return;
	  }
	else
	  j++;
      }
    /* No clue what the function is. Just return "BEEP" */
  }
  if (func_type)
    *func_type = F_BEEP;
  if (flags)
    *flags = 0;
  return;
}


/***********************************************************************
 *
 *  Procedure:
 *	AddToFunction - add an item to a FvwmFunction
 *
 *  Inputs:
 *	func      - pointer to the FvwmFunction to add the item
 *	action    - the definition string from the config line
 *
 ***********************************************************************/
void AddToFunction(FvwmFunction *func, char *action)
{
  FunctionItem *tmp;
  char *token = NULL;
  char condition;

  token = PeekToken(action, &action);
  if (!token)
    return;
  condition = token[0];
  if (isupper(condition))
    condition = tolower(condition);
  if (condition != CF_IMMEDIATE &&
      condition != CF_MOTION &&
      condition != CF_HOLD &&
      condition != CF_CLICK &&
      condition != CF_DOUBLE_CLICK)
  {
    fvwm_msg(
      ERR, "AddToFunction",
      "Got '%s' instead of a valid function specifier", token);
    return;
  }
  if (token[0] != 0 && token[1] != 0 &&
      (FindBuiltinFunction(token) || FindFunction(token)))
  {
    fvwm_msg(
      WARN, "AddToFunction",
      "Got the command or function name '%s' instead of a function specifier. "
      "This may indicate a syntax error in the configuration file. "
      "Using %c as the specifier.", token, token[0]);
  }
  if (!action)
    return;
  while (isspace(*action))
    action++;
  if (*action == 0)
    return;

  tmp = (FunctionItem *)safemalloc(sizeof(FunctionItem));
  tmp->next_item = NULL;
  tmp->func = func;
  if (func->first_item == NULL)
  {
    func->first_item = tmp;
    func->last_item = tmp;
  }
  else
  {
    func->last_item->next_item = tmp;
    func->last_item = tmp;
  }

  tmp->condition = condition;
  tmp->action = stripcpy(action);

  find_func_type(tmp->action, NULL, &(tmp->flags));
}

/***********************************************************************
 *
 *  Procedure:
 *	NewFunction - create a new FvwmFunction
 *
 *  Returned Value:
 *	(FvwmFunction *)
 *
 *  Inputs:
 *	name	- the name of the function
 *
 ***********************************************************************/
FvwmFunction *NewFvwmFunction(const char *name)
{
  FvwmFunction *tmp;

  tmp = (FvwmFunction *)safemalloc(sizeof(FvwmFunction));

  tmp->next_func = Scr.functions;
  tmp->first_item = NULL;
  tmp->last_item = NULL;
  tmp->name = stripcpy(name);
  tmp->use_depth = 0;

  Scr.functions = tmp;
  return (tmp);
}


void DestroyFunction(FvwmFunction *func)
{
  FunctionItem *fi,*tmp2;
  FvwmFunction *tmp, *prev;

  if (func == NULL)
    return;

  tmp = Scr.functions;
  prev = NULL;
  while (tmp && tmp != func)
  {
    prev = tmp;
    tmp = tmp->next_func;
  }
  if (tmp != func)
  {
    return;
  }

  if (func->use_depth != 0)
  {
    fvwm_msg(ERR,"DestroyFunction", "Function %s is in use (depth %d)",
	     func->name, func->use_depth);
    return;
  }

  if (prev == NULL)
  {
    Scr.functions = func->next_func;
  }
  else
  {
    prev->next_func = func->next_func;
  }

  free(func->name);

  fi = func->first_item;
  while(fi != NULL)
  {
    tmp2 = fi->next_item;
    if (fi->action != NULL)
      free(fi->action);
    free(fi);
    fi = tmp2;
  }
  free(func);
}


/* FindFunction expects a token as the input. Make sure you have used
 * GetNextToken before passing a function name to remove quotes */
FvwmFunction *FindFunction(const char *function_name)
{
  FvwmFunction *func;

  if(function_name == NULL || *function_name == 0)
    return NULL;

  func = Scr.functions;
  while(func != NULL)
  {
    if(func->name != NULL)
      if(strcasecmp(function_name, func->name) == 0)
      {
        return func;
      }
    func = func->next_func;
  }
  return NULL;

}


static void cf_cleanup(unsigned int *depth, char **arguments)
{
  int i;

  (*depth)--;
  if (!(*depth))
  {
    Scr.flags.is_executing_complex_function = 0;
  }
  for (i = 0; i < 11; i++)
  {
    if(arguments[i] != NULL)
    {
      free(arguments[i]);
    }
  }

  return;
}

static void execute_complex_function(F_CMD_ARGS, Bool *desperate)
{
  cfunc_action_type type = CF_MOTION;
  char c;
  FunctionItem *fi;
  Bool Persist = False;
  Bool HaveDoubleClick = False;
  Bool HaveHold = False;
  Bool NeedsTarget = False;
  Bool ImmediateNeedsTarget = False;
  char *arguments[11], *taction;
  char* func_name;
  int x, y ,i;
  XEvent d, *ev;
  FvwmFunction *func;
  static unsigned int depth = 0;
  int button;

  /* FindFunction expects a token, not just a quoted string */
  func_name = PeekToken(action, &taction);
  if (!func_name)
  {
    return;
  }
  func = FindFunction(func_name);
  if (func == NULL)
  {
    if(*desperate == 0)
      fvwm_msg(ERR, "ComplexFunction", "No such function %s",action);
    return;
  }
  if (!depth)
    Scr.flags.is_executing_complex_function = 1;
  depth++;
  *desperate = 0;
  /* duplicate the whole argument list for use as '$*' */
  if (taction)
  {
    arguments[0] = safestrdup(taction);
    /* strip trailing newline */
    if (arguments[0][0])
    {
      int l= strlen(arguments[0]);

      if (arguments[0][l - 1] == '\n')
	arguments[0][l - 1] = 0;
    }
    /* Get the argument list */
    for(i=1;i<11;i++)
      taction = GetNextToken(taction,&arguments[i]);
  }
  else
  {
    for(i=0;i<11;i++)
      arguments[i] = NULL;
  }
  /* see functions.c to find out which functions need a window to operate on */
  ev = eventp;
  /* In case we want to perform an action on a button press, we
   * need to fool other routines */
  if(eventp->type == ButtonPress)
    eventp->type = ButtonRelease;
  func->use_depth++;

  for (fi = func->first_item; fi != NULL; fi = fi->next_item)
  {
    if (fi->flags & FUNC_NEEDS_WINDOW)
    {
      NeedsTarget = True;
      if (fi->condition == CF_IMMEDIATE)
      {
        ImmediateNeedsTarget = True;
        break;
      }
    }
  }

  if(ImmediateNeedsTarget)
  {
    if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT,ButtonPress))
    {
      func->use_depth--;
      cf_cleanup(&depth, arguments);
      return;
    }
    NeedsTarget = False;
  }

  /* we have to grab buttons before executing immediate actions because these
   * actions can move the window away from the pointer so that a button
   * release would go to the application below. */
  if (!GrabEm(CRS_NONE, GRAB_NORMAL))
  {
    func->use_depth--;
    XBell(dpy, 0);
    cf_cleanup(&depth, arguments);
    return;
  }
  for (fi = func->first_item; fi != NULL; fi = fi->next_item)
  {
    /* c is already lowercase here */
    c = fi->condition;
    switch (c)
    {
    case CF_IMMEDIATE:
      if (tmp_win)
	w = tmp_win->frame;
      else
	w = None;
      old_execute_function(
	fi->action, tmp_win, eventp, context, -1, 0, arguments);
      break;
    case CF_DOUBLE_CLICK:
      HaveDoubleClick = True;
      Persist = True;
      break;
    case CF_HOLD:
      HaveHold = True;
      Persist = True;
      break;
    default:
      Persist = True;
      break;
    }
  }

  if(!Persist)
  {
    func->use_depth--;
    cf_cleanup(&depth, arguments);
    UngrabEm(GRAB_NORMAL);
    return;
  }

  /* Only defer execution if there is a possibility of needing
   * a window to operate on */
  if(NeedsTarget)
  {
    if (DeferExecution(eventp,&w,&tmp_win,&context, CRS_SELECT,ButtonPress))
    {
      func->use_depth--;
      cf_cleanup(&depth, arguments);
      UngrabEm(GRAB_NORMAL);
      return;
    }
  }

  switch (eventp->xany.type)
  {
  case ButtonPress:
  case ButtonRelease:
    x = eventp->xbutton.x_root;
    y = eventp->xbutton.y_root;
    button = eventp->xbutton.button;
    /* Take the click which started this fuction off the
     * Event queue.  -DDN- Dan D Niles dniles@iname.com */
    XCheckMaskEvent(dpy, ButtonPressMask, &d);
    break;
  default:
    XQueryPointer( dpy, Scr.Root, &JunkRoot, &JunkChild,
		   &x,&y,&JunkX, &JunkY, &JunkMask);
    button = 0;
    break;
  }

  /* Wait and see if we have a click, or a move */
  /* wait forever, see if the user releases the button */
  type = CheckActionType(x, y, &d, HaveHold, True, &button);
  if (type == CF_CLICK)
  {
    ev = &d;
    /* If it was a click, wait to see if its a double click */
    if(HaveDoubleClick)
    {
      int button2;

      type = CheckActionType(x, y, &d, True, False, &button2);
      switch (type)
      {
      case CF_HOLD:
      case CF_MOTION:
      case CF_CLICK:
	if (button == button2)
	{
	  type = CF_DOUBLE_CLICK;
	}
	else
	{
	  type = CF_CLICK;
	}
	ev = &d;
	break;
      case CF_TIMEOUT:
	type = CF_CLICK;
	break;
      default:
	/* can't happen */
	break;
      }
    }
  }
  else if (type == CF_TIMEOUT)
  {
    type = CF_HOLD;
  }

  /* some functions operate on button release instead of
   * presses. These gets really weird for complex functions ... */
  if(ev->type == ButtonPress)
    ev->type = ButtonRelease;

  fi = func->first_item;
#ifdef BUGGY_CODE
  /* domivogt (11-Apr-2000): The pointer ***must not*** be ungrabbed here.  If
   * it is, any window that the mouse enters during the function will receive
   * MotionNotify events with a button held down!  The results are
   * unpredictable.  E.g. rxvt interprets the ButtonMotion as user input to
   * select text. */
  UngrabEm(GRAB_NORMAL);
#endif
  while(fi != NULL)
  {
    /* make lower case */
    c = fi->condition;
    if(isupper(c))
      c=tolower(c);
    if(c == type)
    {
      if(tmp_win)
	w = tmp_win->frame;
      else
	w = None;
      old_execute_function(
	fi->action, tmp_win, ev, context, -1, 0, arguments);
    }
    fi = fi->next_item;
  }
  /* This is the right place to ungrab the pointer (see comment above). */
  func->use_depth--;
  cf_cleanup(&depth, arguments);
  UngrabEm(GRAB_NORMAL);

  return;
}


syntax highlighted by Code2HTML, v. 0.9.1