#include <FVWMconfig.h>

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

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

/***************************************************************************
 * 
 * Check to see if the pointer is on the edge of the screen,
 * and scroll/page if needed
 *
 ***************************************************************************/
void HandlePaging(int HorWarpSize, int VertWarpSize, int *xl, int *yt,
		  int *delta_x, int *delta_y, Bool Grab)
{
  int x, y, total;

  *delta_x = 0;
  *delta_y = 0;

  /* Windows obscured by the scroll region won't receive 
   * EnterNotify events.  Broadcast this event so
   * applications that care (FvwmTaskBar) can decide what to do.
   */
  Broadcast(M_SCROLLREGION, 2, *xl, *yt, 0, 0, 0, 0, 0);

  if ((Scr.ScrollResistance >= 10000) ||
      ((HorWarpSize == 0) && (VertWarpSize == 0)))
    return;

  /* need to move the viewport */
  if ((Scr.VxMax == 0 ||
       (*xl >= SCROLL_REGION && *xl < Scr.MyDisplayWidth - SCROLL_REGION))
      && (Scr.VyMax == 0
	  || (*yt >= SCROLL_REGION
	      && *yt < Scr.MyDisplayHeight - SCROLL_REGION))) return;

  total = 0;
  while (total < Scr.ScrollResistance) {
    sleep_a_little(10000);
    total += 10;
    if (XCheckWindowEvent(dpy, Scr.PanFrameTop.win,
			  LeaveWindowMask, &Event)) {
      StashEventTime(&Event);
      return;
    }
    if (XCheckWindowEvent(dpy, Scr.PanFrameBottom.win,
			  LeaveWindowMask, &Event)) {
      StashEventTime(&Event);
      return;
    }
    if (XCheckWindowEvent(dpy, Scr.PanFrameLeft.win,
			  LeaveWindowMask, &Event)) {
      StashEventTime(&Event);
      return;
    }
    if (XCheckWindowEvent(dpy, Scr.PanFrameRight.win,
			  LeaveWindowMask, &Event)) {
      StashEventTime(&Event);
      return;
    }
  }

  XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
		&x, &y, &JunkX, &JunkY, &JunkMask);

  /* Turn off the rubberband if its on */
  MoveOutline(Scr.Root, 0, 0, 0, 0);

  /* Move the viewport */
  /* and/or move the cursor back to the approximate correct location */
  /* that is, the same place on the virtual desktop that it */
  /* started at */

  if (x < SCROLL_REGION)
    *delta_x = -HorWarpSize;
  else if (x >= Scr.MyDisplayWidth - SCROLL_REGION)
    *delta_x = HorWarpSize;
  else
    *delta_x = 0;
  if (Scr.VxMax == 0)
    *delta_x = 0;

  if (y < SCROLL_REGION)
    *delta_y = -VertWarpSize;
  else if (y >= Scr.MyDisplayHeight - SCROLL_REGION)
    *delta_y = VertWarpSize;
  else
    *delta_y = 0;
  if (Scr.VyMax == 0)
    *delta_y = 0;

  /* Ouch! lots of bounds checking */
  if (Scr.Vx + *delta_x < 0) {
    if (Scr.flags & EdgeWrapX) {
      *delta_x = Scr.VxMax;
      *xl = x + Scr.MyDisplayWidth;
    }
  } else if (Scr.Vx + *delta_x > Scr.VxMax) {
    if (Scr.flags & EdgeWrapX) {
      *delta_x = -Scr.VxMax;
      *xl = x - Scr.MyDisplayWidth;
    }
  } else
    *xl = x - *delta_x;

  if (Scr.Vy + *delta_y < 0) {
    if (Scr.flags & EdgeWrapY) {
      *delta_y = Scr.VyMax;
      *yt = y + Scr.MyDisplayHeight;
    }
  } else if (Scr.Vy + *delta_y > Scr.VyMax) {
    if (Scr.flags & EdgeWrapY) {
      *delta_y = -Scr.VyMax;
      *yt = y - Scr.MyDisplayHeight;
    }
  } else
    *yt = y - *delta_y;

  if (*xl <= SCROLL_REGION)
    *xl = SCROLL_REGION + 1;
  if (*yt <= SCROLL_REGION)
    *yt = SCROLL_REGION + 1;
  if (*xl >= Scr.MyDisplayWidth - SCROLL_REGION)
    *xl = Scr.MyDisplayWidth - SCROLL_REGION - 1;
  if (*yt >= Scr.MyDisplayHeight - SCROLL_REGION)
    *yt = Scr.MyDisplayHeight - SCROLL_REGION - 1;

  if ((*delta_x != 0) || (*delta_y != 0)) {
    if (Grab)
      XGrabServer(dpy);
    XWarpPointer(dpy, None, Scr.Root, 0, 0, 0, 0, *xl, *yt);
    MoveViewport(Scr.Vx + *delta_x, Scr.Vy + *delta_y, False);
    XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
		  xl, yt, &JunkX, &JunkY, &JunkMask);
    if (Grab)
      XUngrabServer(dpy);
  }
}



/* the root window is surrounded by four window slices, which are InputOnly.
 * So you can see 'through' them, but they eat the input. An EnterEvent in
 * one of these windows causes a Paging. The windows have the according cursor
 * pointing in the pan direction or are hidden if there is no more panning
 * in that direction. This is mostly intended to get a panning even atop
 * of Motif applications, which does not work yet. It seems Motif windows
 * eat all mouse events.
 *
 * Hermann Dunkel, HEDU, dunkel@cul-ipn.uni-kiel.de 1/94
*/

/***************************************************************************
 * checkPanFrames hides PanFrames if they are on the very border of the
 * VIRTUELL screen and EdgeWrap for that direction is off. 
 * (A special cursor for the EdgeWrap border could be nice) HEDU
 ****************************************************************************/
void checkPanFrames(void)
{
  int wrapX = (Scr.flags & EdgeWrapX);
  int wrapY = (Scr.flags & EdgeWrapY);

  if (!(Scr.flags & WindowsCaptured))
    return;

  /* Remove Pan frames if paging by edge-scroll is permanently or
   * temporarily disabled */
  if (Scr.EdgeScrollY == 0) {
    XUnmapWindow(dpy, Scr.PanFrameTop.win);
    Scr.PanFrameTop.isMapped = False;
    XUnmapWindow(dpy, Scr.PanFrameBottom.win);
    Scr.PanFrameBottom.isMapped = False;
  }
  if (Scr.EdgeScrollX == 0) {
    XUnmapWindow(dpy, Scr.PanFrameLeft.win);
    Scr.PanFrameLeft.isMapped = False;
    XUnmapWindow(dpy, Scr.PanFrameRight.win);
    Scr.PanFrameRight.isMapped = False;
  }
  if ((Scr.EdgeScrollX == 0) && (Scr.EdgeScrollY == 0))
    return;

  /* LEFT, hide only if EdgeWrap is off */
  if (Scr.Vx == 0 && Scr.PanFrameLeft.isMapped && (!wrapX)) {
    XUnmapWindow(dpy, Scr.PanFrameLeft.win);
    Scr.PanFrameLeft.isMapped = False;
  } else if (Scr.Vx > 0 && Scr.PanFrameLeft.isMapped == False) {
    XMapRaised(dpy, Scr.PanFrameLeft.win);
    Scr.PanFrameLeft.isMapped = True;
  }
  /* RIGHT, hide only if EdgeWrap is off */
  if (Scr.Vx == Scr.VxMax && Scr.PanFrameRight.isMapped && (!wrapX)) {
    XUnmapWindow(dpy, Scr.PanFrameRight.win);
    Scr.PanFrameRight.isMapped = False;
  } else if (Scr.Vx < Scr.VxMax && Scr.PanFrameRight.isMapped == False) {
    XMapRaised(dpy, Scr.PanFrameRight.win);
    Scr.PanFrameRight.isMapped = True;
  }
  /* TOP, hide only if EdgeWrap is off */
  if (Scr.Vy == 0 && Scr.PanFrameTop.isMapped && (!wrapY)) {
    XUnmapWindow(dpy, Scr.PanFrameTop.win);
    Scr.PanFrameTop.isMapped = False;
  } else if (Scr.Vy > 0 && Scr.PanFrameTop.isMapped == False) {
    XMapRaised(dpy, Scr.PanFrameTop.win);
    Scr.PanFrameTop.isMapped = True;
  }
  /* BOTTOM, hide only if EdgeWrap is off */
  if (Scr.Vy == Scr.VyMax && Scr.PanFrameBottom.isMapped && (!wrapY)) {
    XUnmapWindow(dpy, Scr.PanFrameBottom.win);
    Scr.PanFrameBottom.isMapped = False;
  } else if (Scr.Vy < Scr.VyMax && Scr.PanFrameBottom.isMapped == False) {
    XMapRaised(dpy, Scr.PanFrameBottom.win);
    Scr.PanFrameBottom.isMapped = True;
  }
}

/****************************************************************************
 *
 * Gotta make sure these things are on top of everything else, or they
 * don't work!
 *
 * For some reason, this seems to be unneeded.
 *
 ***************************************************************************/
void raisePanFrames(void)
{
  if (Scr.PanFrameTop.isMapped)
    XRaiseWindow(dpy, Scr.PanFrameTop.win);
  if (Scr.PanFrameLeft.isMapped)
    XRaiseWindow(dpy, Scr.PanFrameLeft.win);
  if (Scr.PanFrameRight.isMapped)
    XRaiseWindow(dpy, Scr.PanFrameRight.win);
  if (Scr.PanFrameBottom.isMapped)
    XRaiseWindow(dpy, Scr.PanFrameBottom.win);
}

/****************************************************************************
 *
 * Creates the windows for edge-scrolling 
 *
 ****************************************************************************/
void initPanFrames()
{
  XSetWindowAttributes attributes;	/* attributes for create */
  unsigned long valuemask;

  attributes.event_mask = EnterWindowMask | LeaveWindowMask |
      VisibilityChangeMask;
  valuemask = CWEventMask | CWCursor;

  attributes.cursor = Scr.FvwmCursors[LEFT];
  Scr.PanFrameLeft.win =
      XCreateWindow(dpy, Scr.Root,
		    0, PAN_FRAME_THICKNESS,
		    PAN_FRAME_THICKNESS, Scr.MyDisplayHeight - 2 * PAN_FRAME_THICKNESS, 0,	/* no border */
		    CopyFromParent, InputOnly, CopyFromParent,
		    valuemask, &attributes);

  attributes.cursor = Scr.FvwmCursors[TOP];
  Scr.PanFrameTop.win =
      XCreateWindow(dpy, Scr.Root, 0, 0, Scr.MyDisplayWidth, PAN_FRAME_THICKNESS, 0,	/* no border */
		    CopyFromParent, InputOnly,
		    CopyFromParent, valuemask, &attributes);

  attributes.cursor = Scr.FvwmCursors[RIGHT];
  Scr.PanFrameRight.win =
      XCreateWindow(dpy, Scr.Root,
		    Scr.MyDisplayWidth - PAN_FRAME_THICKNESS,
		    PAN_FRAME_THICKNESS, PAN_FRAME_THICKNESS,
		    Scr.MyDisplayHeight - 2 * PAN_FRAME_THICKNESS, 0,	/* no border */
		    CopyFromParent, InputOnly, CopyFromParent, valuemask,
		    &attributes);

  attributes.cursor = Scr.FvwmCursors[BOTTOM];
  Scr.PanFrameBottom.win =
      XCreateWindow(dpy, Scr.Root,
		    0, Scr.MyDisplayHeight - PAN_FRAME_THICKNESS, Scr.MyDisplayWidth, PAN_FRAME_THICKNESS, 0,	/* no border */
		    CopyFromParent, InputOnly, CopyFromParent,
		    valuemask, &attributes);

  Scr.PanFrameLeft.isMapped =
      Scr.PanFrameTop.isMapped =
      Scr.PanFrameRight.isMapped = Scr.PanFrameBottom.isMapped = False;
}


/***************************************************************************
 *
 *  Moves the viewport within the virtual desktop
 *
 ***************************************************************************/
void MoveViewport(int newx, int newy, Bool grab)
{
  FvwmWindow *t, *t1;
  int deltax, deltay, txl, txr, tyt, tyb;
  int PageTop, PageLeft, PageBottom, PageRight;

  if (grab)
    XGrabServer(dpy);

  if (newx > Scr.VxMax) newx = Scr.VxMax;
  if (newy > Scr.VyMax) newy = Scr.VyMax;
  if (newx < 0) newx = 0;
  if (newy < 0) newy = 0;

  deltay = Scr.Vy - newy;
  deltax = Scr.Vx - newx;

  /*
     Identify the bounding rectangle that will be moved into
     the viewport.
  */
  PageBottom = Scr.MyDisplayHeight - deltay;
  PageRight  = Scr.MyDisplayWidth  - deltax;
  PageTop    = -deltay;
  PageLeft   = -deltax;

  Scr.Vx = newx;
  Scr.Vy = newy;
  Broadcast(M_NEW_PAGE, 5,
	    Scr.Vx, Scr.Vy, Scr.CurrentDesk, Scr.VxMax, Scr.VyMax, 0, 0);

  if ((deltax != 0) || (deltay != 0)) {

    /*  
       RBW - 05/15/1998  - new: chase the chain bidirectionally, all at
       once! The idea is to move the windows that are moving out of the
       viewport from the bottom of the stacking order up, to minimize the
       expose-redraw overhead. Windows that will be moving into view will
       be moved top down, for the same reason.

       Newer: use the new stacking-order chain, rather than the old
       last-focused chain.
    */

    t = Scr.FvwmRoot.stack_next;
    t1 = Scr.FvwmRoot.stack_prev;
    while ((t != &Scr.FvwmRoot) || (t1 != &Scr.FvwmRoot)) {
      if (t != &Scr.FvwmRoot) {
        /*
           If the window is moving into the viewport...
        */
        txl = t->frame_x;
        tyt = t->frame_y;
        txr = t->frame_x + t->frame_width;
        tyb = t->frame_y + t->frame_height;
        if ((txr >= PageLeft && txl <= PageRight
             && tyb >= PageTop && tyt <= PageBottom)
             && !t->ViewportMoved) {
          t->ViewportMoved = True;    /* Block double move. */
          /* If the window is iconified, and sticky Icons is set,
           * then the window should essentially be sticky */
          if (!((t->flags & ICONIFIED) && (t->flags & StickyIcon)) &&
              (!(t->flags & STICKY))) {
            if (!(t->flags & StickyIcon)) {
              t->icon_x_loc += deltax;
              t->icon_xl_loc += deltax;
              t->icon_y_loc += deltay;
              if (t->icon_pixmap_w != None)
                XMoveWindow(dpy, t->icon_pixmap_w, t->icon_x_loc,
                            t->icon_y_loc);
              if (t->icon_w != None)
                XMoveWindow(dpy, t->icon_w, t->icon_x_loc,
			    t->icon_y_loc + t->icon_p_height);
              if (!(t->flags & ICON_UNMAPPED))
                Broadcast(M_ICON_LOCATION, 7, t->w, t->frame,
                          (unsigned long) t,
                          t->icon_x_loc, t->icon_y_loc,
                          t->icon_w_width, 
                          t->icon_w_height + t->icon_p_height);
            }
            SetupFrame(t, t->frame_x + deltax, t->frame_y + deltay,
                       t->frame_width, t->frame_height, FALSE);
          }
        }
        /* Bump to next win... */
        t = t->stack_next;
      }
      if (t1 != &Scr.FvwmRoot) {
        /*
           If the window is not moving into the viewport...
        */
        txl = t1->frame_x;
        tyt = t1->frame_y;
        txr = t1->frame_x + t1->frame_width;
        tyb = t1->frame_y + t1->frame_height;
        if (!(txr >= PageLeft && txl <= PageRight
            && tyb >= PageTop && tyt <= PageBottom)
            && ! t1->ViewportMoved) {
          t1->ViewportMoved = True;    /* Block double move. */
          /* If the window is iconified, and sticky Icons is set,
           * then the window should essentially be sticky */
          if (!((t1->flags & ICONIFIED) && (t1->flags & StickyIcon)) &&
              (!(t1->flags & STICKY))) {
            if (!(t1->flags & StickyIcon)) {
              t1->icon_x_loc += deltax;
              t1->icon_xl_loc += deltax;
              t1->icon_y_loc += deltay;
              if (t1->icon_pixmap_w != None)
                XMoveWindow(dpy, t1->icon_pixmap_w, t1->icon_x_loc,
                            t1->icon_y_loc);
              if (t1->icon_w != None)
                XMoveWindow(dpy, t1->icon_w, t1->icon_x_loc,
                            t1->icon_y_loc + t1->icon_p_height);
              if (!(t1->flags &ICON_UNMAPPED))
                Broadcast(M_ICON_LOCATION, 7, t1->w, t1->frame,
                          (unsigned long) t1,
                          t1->icon_x_loc, t1->icon_y_loc,
                          t1->icon_w_width, 
                          t1->icon_w_height + t1->icon_p_height);
            }
            SetupFrame(t1, t1->frame_x + deltax, t1->frame_y + deltay,
                       t1->frame_width, t1->frame_height, FALSE);
          }
        }
        /* Bump to next win... */
        t1 = t1->stack_prev;
      }
    }

    for (t = Scr.FvwmRoot.next; t != NULL; t = t->next) {
      t->ViewportMoved = False;  /* Clear double move blocker. */
      /* If its an icon, and its sticking, autoplace it so
       * that it doesn't wind up on top a a stationary
       * icon */
      if (((t->flags & STICKY) || (t->flags & StickyIcon)) &&
	  (t->flags & ICONIFIED) && (!(t->flags & ICON_MOVED)) &&
	  (!(t->flags & ICON_UNMAPPED)))
	AutoPlace(t);
    }

  }
  checkPanFrames();

  /* do this with PanFrames too ??? HEDU */
  while (XCheckTypedEvent(dpy, MotionNotify, &Event))
    StashEventTime(&Event);

  if (grab)
    XUngrabServer(dpy);
}


/**************************************************************************
 * 
 * Move to a new desktop
 *
 *************************************************************************/
void changeDesks_func(XEvent * eventp, Window w, FvwmWindow * tmp_win,
		      unsigned long context, char *action, int *Module)
{
  int n, val1, val1_unit, val2, val2_unit;

  n = GetTwoArguments(action, &val1, &val2, &val1_unit, &val2_unit);
  changeDesks(val1, val2);
}

void changeDesks(int val1, int val2)
{
  int oldDesk;
  FvwmWindow *FocusWin = 0, *t, *t1;
  static FvwmWindow *StickyWin = 0;

  oldDesk = Scr.CurrentDesk;

  if (val1 != 0) {
    Scr.CurrentDesk = Scr.CurrentDesk + val1;
  } else {
    Scr.CurrentDesk = val2;
    if (Scr.CurrentDesk == oldDesk)
      return;
  }

  Broadcast(M_NEW_DESK, 1, Scr.CurrentDesk, 0, 0, 0, 0, 0, 0);
  /* Scan the window list, mapping windows on the new Desk,
   * unmapping windows on the old Desk */
  XGrabServer(dpy);

  /*  
     RBW - 05/08/1998 - new: chase the chain bidirectionally, unmapping
     windows bottom-up and mapping them top-down, to minimize expose-redraw
     overhead.

     Newer: use the new stacking-order chain, rather than the old
     last-focused chain.
  */
  t = Scr.FvwmRoot.stack_next;
  t1 = Scr.FvwmRoot.stack_prev;
  while ((t != &Scr.FvwmRoot) || (t1 != &Scr.FvwmRoot)) {
    if (t != &Scr.FvwmRoot) {
      if (!((t->flags & ICONIFIED) && (t->flags & StickyIcon)) &&
          (!(t->flags & STICKY)) && (!(t->flags & ICON_UNMAPPED))) {
        if (t->Desk == Scr.CurrentDesk) {
          MapIt(t);
          if (t->FocusDesk == Scr.CurrentDesk) {
            FocusWin = t;
          }
        }
      } else {
        /*
           Only need to do these in one of the passes...
         */
        /* Window is sticky */
        t->Desk = Scr.CurrentDesk;
        if (Scr.Focus == t) {
          t->FocusDesk = oldDesk;
          StickyWin = t;
        }
      }
      t = t->stack_next;
    }
    if (t1 != &Scr.FvwmRoot) {
      /* Only change mapping for non-sticky windows */
      if (!((t1->flags & ICONIFIED) && (t1->flags & StickyIcon)) &&
          (!(t1->flags & STICKY)) && (!(t1->flags & ICON_UNMAPPED))) {
        if (t1->Desk == oldDesk) {
          if (Scr.Focus == t1)
            t1->FocusDesk = oldDesk;
          else
            t1->FocusDesk = -1;
          UnmapIt(t1);
        } 
      }
      t1 = t1->stack_prev;
    }
  }

  XUngrabServer(dpy);

  for (t = Scr.FvwmRoot.next; t != NULL; t = t->next) {
    /* If its an icon, and its sticking, autoplace it so
     * that it doesn't wind up on top a a stationary
     * icon */
    if (((t->flags & STICKY) || (t->flags & StickyIcon)) &&
	(t->flags & ICONIFIED) && (!(t->flags & ICON_MOVED)) &&
	(!(t->flags & ICON_UNMAPPED)))
      AutoPlace(t);
  }

  if ((FocusWin) && (FocusWin->flags & ClickToFocus))
#ifndef NO_REMEMBER_FOCUS
    SetFocus(FocusWin->w, FocusWin, 1);
  /* OK, someone beat me up, but I don't like this. If you are a predominantly
   * focus-follows-mouse person, but put in one sticky click-to-focus window
   * (typically because you don't really want to give focus to this window),
   * then the following lines are screwed up. */
/*  else if (StickyWin && (StickyWin->flags && STICKY))
    SetFocus(StickyWin->w, StickyWin,1); */
  else
#endif
    SetFocus(Scr.NoFocusWin, NULL, 1);
}



/**************************************************************************
 * 
 * Move a window to a new desktop
 *
 *************************************************************************/
void changeWindowsDesk(XEvent *eventp, Window w, FvwmWindow *t,
		       unsigned long context, char *action, int *Module)
{
  long val1;
  int val1_unit, n;

  if (DeferExecution(eventp, &w, &t, &context, SELECT, ButtonRelease))
    return;

  if (t == NULL)
    return;

  n = GetOneArgument(action, &val1, &val1_unit);

  if (val1 == t->Desk)
    return;

  /*
     Set the window's desktop, and map or unmap it as needed.
  */
  /* Only change mapping for non-sticky windows */
  if (!((t->flags & ICONIFIED) && (t->flags & StickyIcon)) &&
      (!(t->flags & STICKY)) && (!(t->flags & ICON_UNMAPPED))) {
    if (t->Desk == Scr.CurrentDesk) {
      t->Desk = val1;
      UnmapIt(t);
    } else if (val1 == Scr.CurrentDesk) {
      t->Desk = val1;
      /* If its an icon, auto-place it */
      if (t->flags & ICONIFIED)
	AutoPlace(t);
      MapIt(t);
    } else {
      t->Desk = val1;
    }
  }
  BroadcastConfig(M_CONFIGURE_WINDOW, t);
}


void scroll(XEvent * eventp, Window w, FvwmWindow * tmp_win,
	    unsigned long context, char *action, int *Module)
{
  int x, y;
  int val1, val2, val1_unit, val2_unit, n;

  n = GetTwoArguments(action, &val1, &val2, &val1_unit, &val2_unit);

  if ((val1 > -100000) && (val1 < 100000))
    x = Scr.Vx + val1 * val1_unit / 100;
  else
    x = Scr.Vx + (val1 / 1000) * val1_unit / 100;

  if ((val2 > -100000) && (val2 < 100000))
    y = Scr.Vy + val2 * val2_unit / 100;
  else
    y = Scr.Vy + (val2 / 1000) * val2_unit / 100;

  if (((val1 <= -100000) || (val1 >= 100000)) && (x > Scr.VxMax)) {
    x = 0;
    y += Scr.MyDisplayHeight;
    if (y > Scr.VyMax)
      y = 0;
  }
  if (((val1 <= -100000) || (val1 >= 100000)) && (x < 0)) {
    x = Scr.VxMax;
    y -= Scr.MyDisplayHeight;
    if (y < 0)
      y = Scr.VyMax;
  }
  if (((val2 <= -100000) || (val2 >= 100000)) && (y > Scr.VyMax)) {
    y = 0;
    x += Scr.MyDisplayWidth;
    if (x > Scr.VxMax)
      x = 0;
  }
  if (((val2 <= -100000) || (val2 >= 100000)) && (y < 0)) {
    y = Scr.VyMax;
    x -= Scr.MyDisplayWidth;
    if (x < 0)
      x = Scr.VxMax;
  }
  MoveViewport(x, y, True);
}

void goto_page_func(XEvent * eventp, Window w, FvwmWindow * tmp_win,
		    unsigned long context, char *action, int *Module)
{
  int val1, val2, val1_unit, val2_unit, n, x, y;

  n = GetTwoArguments(action, &val1, &val2, &val1_unit, &val2_unit);
  if (n != 2) {
    fvwm_msg(ERR, "goto_page_func", "GotoPage requires two arguments");
    return;
  }

  if ((val1_unit != Scr.MyDisplayWidth) ||
      (val2_unit != Scr.MyDisplayHeight)) {
    fvwm_msg(ERR, "goto_page_func",
	     "GotoPage arguments should be unitless");
  }

  x = val1 * Scr.MyDisplayWidth;
  y = val2 * Scr.MyDisplayHeight;
  MoveViewport(x, y, True);
}
