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

/****************************************************************************
 *
 * Assorted odds and ends
 *
 **************************************************************************/

#include <FVWMconfig.h>

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <stdarg.h>

#include <X11/Xatom.h>

#include "fvwm.h"
#include "menus.h"
#include "misc.h"
#include "parse.h"
#include "screen.h"
#include "module.h"

FvwmWindow *FocusOnNextTimeStamp = NULL;

char NoName[]     = "Untitled";    /* name if no name in XA_WM_NAME */
char NoClass[]    = "NoClass";     /* Class if no res_class in class hints */
char NoResource[] = "NoResource";  /* Class if no res_name in class hints */

FILE *console = NULL;


/**************************************************************************
 * 
 * Releases dynamically allocated space used to store window/icon names
 *
 **************************************************************************/

void FreeWindowNames(FvwmWindow *tmp)
{
  free_icon_name(tmp);
  free_window_name(tmp);
}

void free_icon_name(FvwmWindow *tmp)
{
  if (!tmp) return;

  if (tmp->icon_name != NULL &&
      tmp->icon_name != NoName &&
      tmp->icon_name != tmp->name &&
      tmp->icon_name != tmp->class.res_name &&
      tmp->icon_name != tmp->class.res_class) XFree(tmp->icon_name);

  tmp->icon_name = NULL;
}

void free_window_name(FvwmWindow *tmp)
{
  if (!tmp) return;

  if (tmp->name != NULL &&
      tmp->name != NoName &&
      tmp->name != tmp->icon_name &&
      tmp->name != tmp->class.res_name &&
      tmp->name != tmp->class.res_class) XFree(tmp->name);

  tmp->name = NULL;
}


/***************************************************************************
 *
 * Handles destruction of a window 
 *
 ****************************************************************************/

void Destroy(FvwmWindow *Tmp_win)
{
  int i;
  extern FvwmWindow *ButtonWindow;
  extern FvwmWindow *colormap_win;
  extern Bool PPosOverride;

  /*
   * Warning, this is also called by HandleUnmapNotify; if it ever needs to
   * look at the event, HandleUnmapNotify will have to mash the UnmapNotify
   * into a DestroyNotify.
   */
  if (!Tmp_win) return;

  /* 
   * I think that commenting this Unmap out will actually create the
   * illusion of closing faster, hopefully - CKH
   */
  XUnmapWindow(dpy, Tmp_win->frame);

  if (!PPosOverride)
    XSync(dpy, 0);

  if (Tmp_win == Scr.Hilite)
    Scr.Hilite = NULL;

  Broadcast(M_DESTROY_WINDOW, 3, Tmp_win->w, Tmp_win->frame,
	    (unsigned long) Tmp_win, 0, 0, 0, 0);

  if (Scr.PreviousFocus == Tmp_win)
    Scr.PreviousFocus = NULL;

  if (ButtonWindow == Tmp_win)
    ButtonWindow = NULL;

  if ((Tmp_win == Scr.Focus) && (Tmp_win->flags & ClickToFocus)) {
    if (Tmp_win->next) {
      HandleHardFocus(Tmp_win->next);
    } else {
      SetFocus(Scr.NoFocusWin, NULL, 1);
    }
  } else if (Scr.Focus == Tmp_win) {
    SetFocus(Scr.NoFocusWin, NULL, 1);
  }

  if (Tmp_win == FocusOnNextTimeStamp)
    FocusOnNextTimeStamp = NULL;

  if (Tmp_win == Scr.Ungrabbed)
    Scr.Ungrabbed = NULL;

  if (Tmp_win == Scr.pushed_window)
    Scr.pushed_window = NULL;

  if (Tmp_win == colormap_win)
    colormap_win = NULL;

  XDestroyWindow(dpy, Tmp_win->frame);
  XDeleteContext(dpy, Tmp_win->frame, FvwmContext);

  XDestroyWindow(dpy, Tmp_win->Parent);

  XDeleteContext(dpy, Tmp_win->Parent, FvwmContext);

  XDeleteContext(dpy, Tmp_win->w, FvwmContext);

  if ((Tmp_win->icon_w) && (Tmp_win->flags & PIXMAP_OURS))
    XFreePixmap(dpy, Tmp_win->iconPixmap);

  if (Tmp_win->icon_w) {
    XDestroyWindow(dpy, Tmp_win->icon_w);
    XDeleteContext(dpy, Tmp_win->icon_w, FvwmContext);
  }

  if ((Tmp_win->flags & ICON_OURS) && (Tmp_win->icon_pixmap_w != None))
    XDestroyWindow(dpy, Tmp_win->icon_pixmap_w);

  if (Tmp_win->icon_pixmap_w != None)
    XDeleteContext(dpy, Tmp_win->icon_pixmap_w, FvwmContext);

  if (Tmp_win->flags & TITLE) {
    XDeleteContext(dpy, Tmp_win->title_w, FvwmContext);
    for (i = 0; i < Scr.nr_left_buttons; i++)
      XDeleteContext(dpy, Tmp_win->left_w[i], FvwmContext);
    for (i = 0; i < Scr.nr_right_buttons; i++)
      if (Tmp_win->right_w[i] != None)
	XDeleteContext(dpy, Tmp_win->right_w[i], FvwmContext);
  }

  if (Tmp_win->flags & BORDER) {
    for (i = 0; i < 4; i++)
      XDeleteContext(dpy, Tmp_win->sides[i], FvwmContext);
    for (i = 0; i < 4; i++)
      XDeleteContext(dpy, Tmp_win->corners[i], FvwmContext);
  }

  if (Tmp_win->title_icon != NULL)
    DestroyPicture(dpy, Tmp_win->title_icon);

  /*
     RBW - 05/15/1998 - new: have to unhook the stacking order chain also.
     There's always a prev and next, since this is a ring anchored on
     Scr.FvwmRoot
  */
  Tmp_win->stack_prev->stack_next = Tmp_win->stack_next;
  Tmp_win->stack_next->stack_prev = Tmp_win->stack_prev;

  Tmp_win->prev->next = Tmp_win->next;
  if (Tmp_win->next != NULL)
    Tmp_win->next->prev = Tmp_win->prev;

  FreeWindowNames(Tmp_win);

  if (Tmp_win->wmhints)
    XFree((char *) Tmp_win->wmhints);

  /* removing NoClass change for now... */
#if 0
  if (Tmp_win->class.res_name)
    XFree((char *) Tmp_win->class.res_name);
  if (Tmp_win->class.res_class)
    XFree((char *) Tmp_win->class.res_class);
#else
  if (Tmp_win->class.res_name && Tmp_win->class.res_name != NoResource)
    XFree((char *) Tmp_win->class.res_name);
  if (Tmp_win->class.res_class && Tmp_win->class.res_class != NoClass)
    XFree((char *) Tmp_win->class.res_class);
#endif				/* 0 */
  if (Tmp_win->mwm_hints)
    XFree((char *) Tmp_win->mwm_hints);

  if (Tmp_win->cmap_windows != (Window *) NULL)
    XFree((void *) Tmp_win->cmap_windows);

  free((char *) Tmp_win);

  if (!PPosOverride)
    XSync(dpy, 0);

  return;
}


/**************************************************************************
 *
 * Removes expose events for a specific window from the queue 
 *
 *************************************************************************/

int flush_expose(Window w)
{
  XEvent dummy;
  int i = 0;

  while (XCheckTypedWindowEvent(dpy, w, Expose, &dummy)) i++;

  return i;
}


/***********************************************************************
 *
 *  Procedure:
 *	RestoreWithdrawnLocation
 * 
 *  Puts windows back where they were before fvwm took over 
 *
 ************************************************************************/

void RestoreWithdrawnLocation(FvwmWindow *tmp, Bool restart)
{
  int a, b, w2, h2;
  unsigned int bw, mask;
  XWindowChanges xwc;

  if (!tmp) return;

  if (XGetGeometry(dpy, tmp->w, &JunkRoot, &xwc.x, &xwc.y,
		   &JunkWidth, &JunkHeight, &bw, &JunkDepth)) {
    XTranslateCoordinates(dpy, tmp->frame, Scr.Root, xwc.x, xwc.y,
			  &a, &b, &JunkChild);
    xwc.x = a + tmp->xdiff;
    xwc.y = b + tmp->ydiff;
    xwc.border_width = tmp->old_bw;
    mask = (CWX | CWY | CWBorderWidth);

    /* We can not assume that the window is currently on the screen.
     * Although this is normally the case, it is not always true.  The
     * most common example is when the user does something in an
     * application which will, after some amount of computational delay,
     * cause the window to be unmapped, but then switches screens before
     * this happens.  The XTranslateCoordinates call above will set the
     * window coordinates to either be larger than the screen, or negative.
     * This will result in the window being placed in odd, or even
     * unviewable locations when the window is remapped.  The followin code
     * forces the "relative" location to be within the bounds of the display.
     *
     * gpw -- 11/11/93
     *
     * Unfortunately, this does horrendous things during re-starts, 
     * hence the "if(restart) clause (RN) 
     *
     * Also, fixed so that it only does this stuff if a window is more than
     * half off the screen. (RN)
     */

    if (!restart) {
      /* Don't mess with it if its partially on the screen now */
      if ((tmp->frame_x < 0) || (tmp->frame_y < 0) ||
	  (tmp->frame_x >= Scr.MyDisplayWidth) ||
	  (tmp->frame_y >= Scr.MyDisplayHeight)) {
	w2 = (tmp->frame_width >> 1);
	h2 = (tmp->frame_height >> 1);
	if ((xwc.x < -w2) || (xwc.x > (Scr.MyDisplayWidth - w2))) {
	  xwc.x = xwc.x % Scr.MyDisplayWidth;
	  if (xwc.x < -w2)
	    xwc.x += Scr.MyDisplayWidth;
	}
	if ((xwc.y < -h2) || (xwc.y > (Scr.MyDisplayHeight - h2))) {
	  xwc.y = xwc.y % Scr.MyDisplayHeight;
	  if (xwc.y < -h2)
	    xwc.y += Scr.MyDisplayHeight;
	}
      }
    }
    XReparentWindow(dpy, tmp->w, Scr.Root, xwc.x, xwc.y);

    if ((tmp->flags & ICONIFIED) && (!(tmp->flags & SUPPRESSICON))) {
      if (tmp->icon_w)
	XUnmapWindow(dpy, tmp->icon_w);
      if (tmp->icon_pixmap_w)
	XUnmapWindow(dpy, tmp->icon_pixmap_w);
    }

    XConfigureWindow(dpy, tmp->w, mask, &xwc);
    if (!restart)
      XSync(dpy, 0);
  }
}


/***************************************************************************
 *
 * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
 * client messages will have the following form:
 *
 *     event type	ClientMessage
 *     message type	_XA_WM_PROTOCOLS
 *     window		tmp->w
 *     format		32
 *     data[0]		message atom
 *     data[1]		time stamp
 *
 ****************************************************************************/

void send_clientmessage(Window w, Atom a, Time timestamp)
{
  XClientMessageEvent ev;

  ev.type = ClientMessage;
  ev.window = w;
  ev.message_type = _XA_WM_PROTOCOLS;
  ev.format = 32;
  ev.data.l[0] = a;
  ev.data.l[1] = timestamp;
  XSendEvent(dpy, w, False, 0L, (XEvent *) & ev);
}


/****************************************************************************
 *
 * Records the time of the last processed event. Used in XSetInputFocus
 *
 ****************************************************************************/

Time lastTimestamp = CurrentTime;	/* until Xlib does this for us */

Bool StashEventTime(XEvent *ev)
{
  Time NewTimestamp = CurrentTime;

  switch (ev->type) {
  case KeyPress:
  case KeyRelease:
    NewTimestamp = ev->xkey.time;
    break;

  case ButtonPress:
  case ButtonRelease:
    NewTimestamp = ev->xbutton.time;
    break;

  case MotionNotify:
    NewTimestamp = ev->xmotion.time;
    break;

  case EnterNotify:
  case LeaveNotify:
    NewTimestamp = ev->xcrossing.time;
    break;

  case PropertyNotify:
    NewTimestamp = ev->xproperty.time;
    break;

  case SelectionClear:
    NewTimestamp = ev->xselectionclear.time;
    break;

  case SelectionRequest:
    NewTimestamp = ev->xselectionrequest.time;
    break;

  case SelectionNotify:
    NewTimestamp = ev->xselection.time;
    break;

  default:
    return False;
  }

  /* Only update is the new timestamp is later than the old one, or
   * if the new one is from a time at least 30 seconds earlier than the
   * old one (in which case the system clock may have changed) */
  if ((NewTimestamp > lastTimestamp)
      || ((lastTimestamp - NewTimestamp) > 30000)) lastTimestamp =
	NewTimestamp;

  if (FocusOnNextTimeStamp) {
    SetFocus(FocusOnNextTimeStamp->w, FocusOnNextTimeStamp, 1);
    FocusOnNextTimeStamp = NULL;
  }

  return True;
}


int GetTwoArguments(char *action, int *val1, int *val2, int *val1_unit,
		    int *val2_unit)
{
  char c1, c2;
  int n;

  *val1 = 0;
  *val2 = 0;
  *val1_unit = Scr.MyDisplayWidth;
  *val2_unit = Scr.MyDisplayHeight;

  n = sscanf(action, "%d %d", val1, val2);
  if (n == 2)
    return 2;

  c1 = 's';
  c2 = 's';
  n = sscanf(action, "%d%c %d%c", val1, &c1, val2, &c2);

  if (n != 4)
    return 0;

  if ((c1 == 'p') || (c1 == 'P'))
    *val1_unit = 100;

  if ((c2 == 'p') || (c2 == 'P'))
    *val2_unit = 100;

  return 2;
}


int GetOneArgument(char *action, long *val1, int *val1_unit)
{
  char c1;
  int n;

  *val1 = 0;
  *val1_unit = Scr.MyDisplayWidth;

  n = sscanf(action, "%ld", val1);
  if (n == 1)
    return 1;

  c1 = '%';
  n = sscanf(action, "%d%c", val1, &c1);

  if (n != 2)
    return 0;

  if ((c1 == 'p') || (c1 == 'P'))
    *val1_unit = 100;

  return 1;
}


/*****************************************************************************
 *
 * Grab the pointer and keyboard
 *
 ****************************************************************************/

Bool GrabEm(int cursor)
{
  int i = 0, val = 0;
  unsigned int mask;

  XSync(dpy, 0);
  /* move the keyboard focus prior to grabbing the pointer to
   * eliminate the enterNotify and exitNotify events that go
   * to the windows */
  if (Scr.PreviousFocus == NULL)
    Scr.PreviousFocus = Scr.Focus;
  SetFocus(Scr.NoFocusWin, NULL, 0);
  mask =
      ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
      PointerMotionMask | EnterWindowMask | LeaveWindowMask;
  while ((i < 1000)
	 && (val =
	     XGrabPointer(dpy, Scr.Root, True, mask, GrabModeAsync,
			  GrabModeAsync, Scr.Root, Scr.FvwmCursors[cursor],
			  CurrentTime) != GrabSuccess)) {
    i++;
    /* If you go too fast, other windows may not get a change to release
     * any grab that they have. */
    sleep_a_little(1000);
  }

  /* If we fall out of the loop without grabbing the pointer, its
     time to give up */
  XSync(dpy, 0);
  if (val != GrabSuccess) {
    return False;
  }
  return True;
}


/*****************************************************************************
 *
 * UnGrab the pointer and keyboard
 *
 ****************************************************************************/

void UngrabEm()
{
  Window w;

  XSync(dpy, 0);
  XUngrabPointer(dpy, CurrentTime);

  if (Scr.PreviousFocus != NULL) {
    w = Scr.PreviousFocus->w;

    /* if the window still exists, focus on it */
    if (w) {
      SetFocus(w, Scr.PreviousFocus, 0);
    }
    Scr.PreviousFocus = NULL;
  }
  XSync(dpy, 0);
}


/****************************************************************************
 *
 * Keeps the "StaysOnTop" windows on the top of the pile.
 * This is achieved by clearing a flag for OnTop windows here, and waiting
 * for a visibility notify on the windows. Exeption: OnTop windows which are
 * obscured by other OnTop windows, which need to be raised here.
 *
 ****************************************************************************/

void KeepOnTop()
{
  FvwmWindow *t;

  /* flag that on-top windows should be re-raised */
  for (t = Scr.FvwmRoot.next; t != NULL; t = t->next) {
    if ((t->flags & ONTOP) && !(t->flags & VISIBLE)) {
      RaiseWindow(t);
      t->flags &= ~RAISED;
    } else
      t->flags |= RAISED;
  }
}


/**************************************************************************
 * 
 * Unmaps a window on transition to a new desktop
 *
 *************************************************************************/

void UnmapIt(FvwmWindow *t)
{
  XWindowAttributes winattrs;
  unsigned long eventMask;
  /*
   * Prevent the receipt of an UnmapNotify, since that would
   * cause a transition to the Withdrawn state.
   */
  XGetWindowAttributes(dpy, t->w, &winattrs);
  eventMask = winattrs.your_event_mask;
  XSelectInput(dpy, t->w, eventMask & ~StructureNotifyMask);
  if (t->flags & ICONIFIED) {
    if (t->icon_pixmap_w != None)
      XUnmapWindow(dpy, t->icon_pixmap_w);
    if (t->icon_w != None)
      XUnmapWindow(dpy, t->icon_w);
  } else if (t->flags & (MAPPED | MAP_PENDING)) {
    XUnmapWindow(dpy, t->frame);
  }
  XSelectInput(dpy, t->w, eventMask);
}


/**************************************************************************
 * 
 * Maps a window on transition to a new desktop
 *
 *************************************************************************/

void MapIt(FvwmWindow *t)
{
  if (t->flags & ICONIFIED) {
    if (t->icon_pixmap_w != None)
      XMapWindow(dpy, t->icon_pixmap_w);
    if (t->icon_w != None)
      XMapWindow(dpy, t->icon_w);
  } else if (t->flags & MAPPED) {
    XMapWindow(dpy, t->frame);
    t->flags |= MAP_PENDING;
    XMapWindow(dpy, t->Parent);
  }
}

void RaiseWindow(FvwmWindow *t)
{
  FvwmWindow *t2;
  int count, i;
  Window *wins;

  /* raise the target, at least */
  count = 1;
  Broadcast(M_RAISE_WINDOW, 3, t->w, t->frame, (unsigned long) t, 0, 0, 0,
	    0);

  for (t2 = Scr.FvwmRoot.next; t2 != NULL; t2 = t2->next) {
    if (t2->flags & ONTOP)
      count++;
    if ((t2->flags & TRANSIENT) && (t2->transientfor == t->w) && (t2 != t)) {
      count++;
      Broadcast(M_RAISE_WINDOW, 3, t2->w, t2->frame, (unsigned long) t2,
		0, 0, 0, 0);
      if ((t2->flags & ICONIFIED) && (!(t2->flags & SUPPRESSICON))) {
	count += 2;
      }
    }
  }
  if ((t->flags & ICONIFIED) && (!(t->flags & SUPPRESSICON))) {
    count += 2;
  }

  wins = (Window *) safemalloc(count * sizeof(Window));

  i = 0;

  /* ONTOP windows on top */
  for (t2 = Scr.FvwmRoot.next; t2 != NULL; t2 = t2->next) {
    if (t2->flags & ONTOP) {
      Broadcast(M_RAISE_WINDOW, 3, t2->w, t2->frame, (unsigned long) t2,
		0, 0, 0, 0);
      wins[i++] = t2->frame;
    }
  }

  /* now raise transients */
#ifndef DONT_RAISE_TRANSIENTS
  for (t2 = Scr.FvwmRoot.next; t2 != NULL; t2 = t2->next) {
    if ((t2->flags & TRANSIENT) && (t2->transientfor == t->w) &&
	(t2 != t) && (!(t2->flags & ONTOP))) {
      wins[i++] = t2->frame;
      if ((t2->flags & ICONIFIED) && (!(t2->flags & SUPPRESSICON))) {
	if (!(t2->flags & NOICON_TITLE))
	  wins[i++] = t2->icon_w;
	if (t->icon_pixmap_w)
	  wins[i++] = t2->icon_pixmap_w;
      }
    }
  }
#endif
  if ((t->flags & ICONIFIED) && (!(t->flags & SUPPRESSICON))) {
    if (!(t->flags & NOICON_TITLE))
      wins[i++] = t->icon_w;
    if (t->icon_pixmap_w)
      wins[i++] = t->icon_pixmap_w;
  }
  if (!(t->flags & ONTOP))
    wins[i++] = t->frame;
  if (!(t->flags & ONTOP))
    Scr.LastWindowRaised = t;

  if (i > 0) {
    XRaiseWindow(dpy, wins[0]);

    /*
       RBW - 05/15/1998 - new: maintain the stacking order chain.
    */
    t->stack_prev->stack_next = t->stack_next;  /* Pluck from chain. */
    t->stack_next->stack_prev = t->stack_prev;
    t->stack_next = Scr.FvwmRoot.stack_next;    /* Set new pointers. */
    t->stack_prev = Scr.FvwmRoot.stack_next->stack_prev;
    Scr.FvwmRoot.stack_next->stack_prev = t;    /* Insert at top of chain. */
    Scr.FvwmRoot.stack_next = t;
  }

  XRestackWindows(dpy, wins, i);
  free(wins);
  raisePanFrames();
}

void LowerWindow(FvwmWindow *t)
{
  XLowerWindow(dpy, t->frame);

  Broadcast(M_LOWER_WINDOW, 3, t->w, t->frame, (unsigned long) t, 0, 0, 0,
	    0);

  if ((t->flags & ICONIFIED) && (!(t->flags & SUPPRESSICON))) {
    XLowerWindow(dpy, t->icon_w);
    XLowerWindow(dpy, t->icon_pixmap_w);
  }
  Scr.LastWindowRaised = (FvwmWindow *) 0;
  
  /*
     RBW - 05/15/1998 - new: maintain the stacking order chain.
  */
  t->stack_prev->stack_next = t->stack_next;     /* Pluck from chain. */
  t->stack_next->stack_prev = t->stack_prev;
  t->stack_next = Scr.FvwmRoot.stack_prev->stack_next;  /* Set new pointers. */
  t->stack_prev = Scr.FvwmRoot.stack_prev;
  Scr.FvwmRoot.stack_prev->stack_next = t;       /* Insert at end of chain. */
  Scr.FvwmRoot.stack_prev = t;
}


/****************************************************************************
 * 
 * Copies a string into a new, malloc'ed string
 * Strips leading spaces and trailing spaces and new lines
 *
 ****************************************************************************/

char *stripcpy(char *source)
{
  char *tmp, *ptr;
  int len;

  if (source == NULL)
    return NULL;

  while (isspace(*source))
    source++;

  len = strlen(source);
  tmp = source + len - 1;
  while (((isspace(*tmp)) || (*tmp == '\n')) && (tmp >= source)) {
    tmp--;
    len--;
  }
  ptr = safemalloc(len + 1);
  strncpy(ptr, source, len);
  ptr[len] = 0;

  return ptr;
}


/****************************************************************************
 *
 * Gets the next "word" of input from char string indata.
 * "word" is a string with no spaces, or a qouted string.
 * Return value is ptr to indata,updated to point to text after the word
 * which is extracted.
 * token is the extracted word, which is copied into a malloced
 * space, and must be freed after use. 
 *
 **************************************************************************/

char *GetNextToken(char *indata, char **token)
{
  char *t, *start, *end, *text;

  t = indata;
  if (t == NULL) {
    *token = NULL;
    return NULL;
  }

  while (isspace(*t) && (*t != 0))
    t++;

  start = t;
  while (!isspace(*t) && (*t != 0)) {
    /* Check for qouted text */
    if (*t == '"') {
      t++;
      while ((*t != '"') && (*t != 0)) {
	/* Skip over escaped text, ie \" or \space */
	if ((*t == '\\') && (*(t + 1) != 0))
	  t++;
	t++;
      }
      if (*t == '"')
	t++;
    } else {
      /* Skip over escaped text, ie \" or \space */
      if ((*t == '\\') && (*(t + 1) != 0))
	t++;
      t++;
    }
  }
  end = t;

  text = safemalloc(end - start + 1);
  *token = text;

  while (start < end) {
    /* Check for qouted text */
    if (*start == '"') {
      start++;
      while ((*start != '"') && (*start != 0)) {
	/* Skip over escaped text, ie \" or \space */
	if ((*start == '\\') && (*(start + 1) != 0))
	  start++;
	*text++ = *start++;
      }
      if (*start == '"')
	start++;
    } else {
      /* Skip over escaped text, ie \" or \space */
      if ((*start == '\\') && (*(start + 1) != 0))
	start++;
      *text++ = *start++;
    }
  }
  *text = 0;
  if (*end != 0)
    end++;

  return end;
}

void HandleHardFocus(FvwmWindow *t)
{
  int x, y;

  FocusOnNextTimeStamp = t;
  Scr.Focus = NULL;
  /* Do something to guarantee a new time stamp! */
  XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
		&JunkX, &JunkY, &x, &y, &JunkMask);
  GrabEm(WAIT);
  XWarpPointer(dpy, Scr.Root, Scr.Root, 0, 0, Scr.MyDisplayWidth,
	       Scr.MyDisplayHeight, x + 2, y + 2);
  XSync(dpy, 0);
  XWarpPointer(dpy, Scr.Root, Scr.Root, 0, 0, Scr.MyDisplayWidth,
	       Scr.MyDisplayHeight, x, y);
  UngrabEm();
}


/****************************************************************************
 *
 *  OpenConsole - Open the console as a way of sending messages
 *
 ***************************************************************************/

void OpenConsole()
{
#ifndef NO_CONSOLE
  console = fopen("/dev/console", "w");
  if (console != NULL)
    fvwm_msg(DBG, "FvwmErrorHandler", "OpenConsole successful...\n");
#endif
}


/****************************************************************************
 *
 * fvwm_msg: used to send output from fvwm to files and or stderr/stdout
 *
 * type -> DBG == Debug, ERR == Error, INFO == Information, WARN == Warning
 * id -> name of function, or other identifier
 *
 ***************************************************************************/

void fvwm_msg(int type, char *id, char *msg, ...)
{
  char *typestr;
  va_list args;
  int error = 0;

  switch (type) {
  case DBG:
#if 0
    if (!debugging)
      return;
#endif				/* 0 */
    typestr = "<<DEBUG>> ";
    break;
  case ERR:
    typestr = "<<ERROR>> ";
    break;
  case WARN:
    typestr = "<<WARNING>> ";
    break;
  case INFO:
  default:
    typestr = "";
    break;
  }

  va_start(args, msg);

  if (console == NULL) {
    fprintf(stderr, "Fvwm-95: in function %s: %s", id, typestr);
    vfprintf(stderr, msg, args);
    fprintf(stderr, "\n");
  } else {
    fprintf(console, "Fvwm-95: in function %s: %s", id, typestr);
    vfprintf(console, msg, args);
    fprintf(console, "\n");
  }

  if (type == ERR) {
    char tmp[1024];		/* I hate to use a fixed length but this will do for now */
    sprintf(tmp, "[FVWM95][%s]: %s ", id, typestr);
    vsprintf(tmp + strlen(tmp), msg, args);
    tmp[strlen(tmp)] = '\n';
    tmp[strlen(tmp) + 1] = '\0';
    BroadcastName(M_ERROR, 0, 0, 0, tmp);
  }

  va_end(args);
}
