/* 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 "libs/FScreen.h"
#include "fvwm.h"
#include "externs.h"
#include "cursor.h"
#include "functions.h"
#include "bindings.h"
#include "events.h"
#include "misc.h"
#include "screen.h"
#include "defaults.h"
#include "virtual.h"
#include "module_interface.h"
#include "focus.h"
#include "gnome.h"
#include "move_resize.h"
#include "borders.h"
#include "icons.h"
#include "stack.h"


/*
 * dje 12/19/98
 *
 * Testing with edge_thickness of 1 showed that some XServers don't
 * care for 1 pixel windows, causing EdgeScrolling  not to work with some
 * servers.  One  bug report was for SUNOS  4.1.3_U1.  We didn't find out
 * the exact  cause of the problem.  Perhaps  no enter/leave  events were
 * generated.
 *
 * Allowed: 0,1,or 2 pixel pan frames.
 *
 * 0   completely disables mouse  edge scrolling,  even	 while dragging a
 * window.
 *
 * 1 gives the	smallest pan frames,  which seem to  work best	except on
 * some servers.
 *
 * 2 is the default.
 */
static int edge_thickness = 2;
static int last_edge_thickness = 2;
static unsigned int prev_page_x = 0;
static unsigned int prev_page_y = 0;
static int prev_desk = 0;
static int prev_desk_and_page_desk = 0;
static unsigned int prev_desk_and_page_page_x = 0;
static unsigned int prev_desk_and_page_page_y = 0;

/**************************************************************************
 *
 * Parse arguments for "Desk" and "MoveToDesk" (formerly "WindowsDesk"):
 *
 * (nil)       : desk number = current desk
 * n	       : desk number = current desk + n
 * 0 n	       : desk number = n
 * n x	       : desk number = current desk + n
 * 0 n min max : desk number = n, but limit to min/max
 * n min max   : desk number = current desk + n, but wrap around at desk #min
 *		 or desk #max
 * n x min max : desk number = current desk + n, but wrap around at desk #min
 *		 or desk #max
 *
 * The current desk number is returned if not enough parameters could be
 * read (or if action is empty).
 *
 **************************************************************************/
static int GetDeskNumber(char *action, int current_desk)
{
  int n;
  int m;
  int desk;
  int val[4];
  int min, max;

  if (MatchToken(action, "prev"))
  {
    return prev_desk;
  }

  n = GetIntegerArguments(action, NULL, &(val[0]), 4);
  if (n <= 0)
    return current_desk;
  if (n == 1)
    return current_desk + val[0];

  desk = current_desk;
  m = 0;

  if (val[0] == 0)
  {
    /* absolute desk number */
    desk = val[1];
  }
  else
  {
    /* relative desk number */
    desk += val[0];
  }

  if (n == 3)
  {
    m = 1;
  }
  if (n == 4)
  {
    m = 2;
  }


  if (n > 2)
  {
    /* handle limits */
    if (val[m] <= val[m+1])
    {
      min = val[m];
      max = val[m+1];
    }
    else
    {
      /*  min > max is nonsense, so swap 'em.  */
      min = val[m+1];
      max = val[m];
    }
    if (desk < min)
    {
      /*  Relative move outside of range, wrap around.  */
      if (val[0] < 0)
	desk = max;
      else
	desk = min;
    }
    else if (desk > max)
    {
      /*  Relative move outside of range, wrap around.  */
      if (val[0] > 0)
	desk = min;
      else
	desk = max;
    }
  }

  return desk;
}


/**************************************************************************
 *
 * Unmaps a window on transition to a new desktop
 *
 *************************************************************************/
static void unmap_window(FvwmWindow *t)
{
  XWindowAttributes winattrs;
  unsigned long eventMask = 0;
  Status ret;

  /*
   * Prevent the receipt of an UnmapNotify, since that would
   * cause a transition to the Withdrawn state.
   */
  ret = XGetWindowAttributes(dpy, t->w, &winattrs);
  if (ret)
  {
    eventMask = winattrs.your_event_mask;
    /* suppress UnmapRequest event */
    XSelectInput(dpy, t->w, eventMask & ~StructureNotifyMask);
  }
  if (IS_ICONIFIED(t))
  {
    if (t->icon_pixmap_w != None)
      XUnmapWindow(dpy,t->icon_pixmap_w);
    if (t->icon_w != None)
      XUnmapWindow(dpy,t->icon_w);
  }
  else
  {
    XUnmapWindow(dpy,t->frame);
#ifdef ICCCM2_UNMAP_WINDOW_PATCH
    /* this is required by the ICCCM2 */
    XUnmapWindow(dpy, t->w);
#endif
    SetMapStateProp(t, IconicState);
  }
  if (ret)
  {
    XSelectInput(dpy, t->w, eventMask);
  }

  return;
}

/**************************************************************************
 *
 * Maps a window on transition to a new desktop
 *
 *************************************************************************/
static void map_window(FvwmWindow *t)
{
  XWindowAttributes winattrs;
  unsigned long eventMask = 0;
  Status ret;

  if (IS_SCHEDULED_FOR_DESTROY(t))
  {
    return;
  }
  /*
   * Prevent the receipt of an UnmapNotify, since that would
   * cause a transition to the Withdrawn state.
   */
  ret = XGetWindowAttributes(dpy, t->w, &winattrs);
  if (ret)
  {
    eventMask = winattrs.your_event_mask;
    /* suppress MapRequest event */
    XSelectInput(dpy, t->w, eventMask & ~StructureNotifyMask);
  }
  if(IS_ICONIFIED(t))
  {
    if(t->icon_pixmap_w != None)
      XMapWindow(dpy,t->icon_pixmap_w);
    if(t->icon_w != None)
      XMapWindow(dpy,t->icon_w);
  }
  else if(IS_MAPPED(t))
  {
    XMapWindow(dpy, t->frame);
    XMapWindow(dpy, t->Parent);
    XMapWindow(dpy, t->decor_w);
#ifdef ICCCM2_UNMAP_WINDOW_PATCH
    /* this is required by the ICCCM2 */
    XMapWindow(dpy, t->w);
#endif
    SetMapStateProp(t, NormalState);
  }
  if (ret)
  {
    XSelectInput(dpy, t->w, eventMask);
  }

  return;
}


void CMD_EdgeThickness(F_CMD_ARGS)
{
  int val, n;

  n = GetIntegerArguments(action, NULL, &val, 1);
  if(n != 1) {
    fvwm_msg(ERR,"setEdgeThickness",
	     "EdgeThickness requires 1 numeric argument, found %d args",n);
    return;
  }
  if (val < 0 || val > 2) {		/* check range */
    fvwm_msg(ERR,"setEdgeThickness",
	     "EdgeThickness arg must be between 0 and 2, found %d",val);
    return;
  }
  edge_thickness = val;
  checkPanFrames();
}

void CMD_EdgeScroll(F_CMD_ARGS)
{
  int val1, val2, val1_unit,val2_unit,n;

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

  /*
   * if edgescroll >1000 and < 100000m
   * wrap at edges of desktop (a "spherical" desktop)
   */
  if (val1 >= 1000)
  {
    val1 /= 1000;
    Scr.flags.edge_wrap_x = 1;
  }
  else
  {
    Scr.flags.edge_wrap_x = 0;
  }
  if (val2 >= 1000)
  {
    val2 /= 1000;
    Scr.flags.edge_wrap_y = 1;
  }
  else
  {
    Scr.flags.edge_wrap_y = 0;
  }

  Scr.EdgeScrollX = val1*val1_unit/100;
  Scr.EdgeScrollY = val2*val2_unit/100;

  checkPanFrames();
}

void CMD_EdgeResistance(F_CMD_ARGS)
{
  int val[3];
  int n;

  n = GetIntegerArguments(action, NULL, val, 3);
  if (n < 2 || n > 3)
  {
    fvwm_msg(ERR, "SetEdgeResistance",
	     "EdgeResistance requires two or three arguments");
    return;
  }
  Scr.ScrollResistance = val[0];
  Scr.MoveResistance = val[1];
  Scr.XiMoveResistance = (n < 3) ? val[1] : val[2];
}

void CMD_Xinerama(F_CMD_ARGS)
{
  int toggle;

  toggle = ParseToggleArgument(action, NULL, -1, 0);
  if (toggle == -1)
  {
    toggle = !FScreenIsEnabled();
  }
  if (!toggle != !FScreenIsEnabled())
  {
    Scr.flags.do_need_window_update = True;
    Scr.flags.has_xinerama_state_changed = True;
    FScreenOnOff(toggle);
    broadcast_xinerama_state();
  }

  return;
}

void CMD_XineramaPrimaryScreen(F_CMD_ARGS)
{
  int val;

  val = FScreenGetScreenArgument(action, 0);
  FScreenSetPrimaryScreen(val);
  if (FScreenIsEnabled())
  {
    Scr.flags.do_need_window_update = True;
    Scr.flags.has_xinerama_state_changed = True;
  }
  broadcast_xinerama_state();

  return;
}

void CMD_XineramaSls(F_CMD_ARGS)
{
  int toggle;

  toggle = ParseToggleArgument(action, NULL, -1, 0);
  if (toggle == -1)
  {
    toggle = !FScreenIsSLSEnabled();
  }
  if (!toggle != !FScreenIsSLSEnabled())
  {
    if (FScreenIsEnabled())
    {
      Scr.flags.do_need_window_update = True;
      Scr.flags.has_xinerama_state_changed = True;
    }
    FScreenSLSOnOff(toggle);
    broadcast_xinerama_state();
  }

  return;
}

void CMD_XineramaSlsSize(F_CMD_ARGS)
{
  int val[2];

  if (GetIntegerArguments(action, NULL, val, 2) != 2 &&
      GetRectangleArguments(action, &val[0], &val[1]) != 2)
  {
    val[0] = 1;
    val[1] = 1;
  }
  if (FScreenConfigureSLS(val[0], val[1]))
  {
    broadcast_xinerama_state();
  }

  return;
}


void CMD_DesktopSize(F_CMD_ARGS)
{
  int val[2];

  if (GetIntegerArguments(action, NULL, val, 2) != 2 &&
      GetRectangleArguments(action, &val[0], &val[1]) != 2)
  {
    fvwm_msg(ERR,"CMD_DesktopSize","DesktopSize requires two arguments");
    return;
  }

  Scr.VxMax = (val[0] <= 0)? 0: val[0]*Scr.MyDisplayWidth-Scr.MyDisplayWidth;
  Scr.VyMax = (val[1] <= 0)? 0: val[1]*Scr.MyDisplayHeight-Scr.MyDisplayHeight;
  BroadcastPacket(M_NEW_PAGE, 5,
		  Scr.Vx, Scr.Vy, Scr.CurrentDesk, Scr.VxMax, Scr.VyMax);

  checkPanFrames();
  /* update GNOME pager */
  GNOME_SetAreaCount();
}


/***************************************************************************
 *
 * Check to see if the pointer is on the edge of the screen, and scroll/page
 * if needed
 *
 * Returns
 *  0: no paging
 *  1: paging occured
 * -1: no need to call the function again before a new event arrives
 ***************************************************************************/
int HandlePaging(
	int HorWarpSize, int VertWarpSize, int *xl, int *yt,
	int *delta_x, int *delta_y, Bool Grab, Bool fLoop,
	Bool do_continue_previous)
{
  static unsigned int add_time = 0;
  int x,y;
  XEvent e;
  static Time my_timestamp = 0;
  static Time my_last_timestamp = 0;
  static Bool is_timestamp_valid = False;
  static int last_x = 0;
  static int last_y = 0;
  static Bool is_last_position_valid = False;

  *delta_x = 0;
  *delta_y = 0;
  if (!is_timestamp_valid && do_continue_previous)
  {
    /* don't call me again until something has happened */
    return -1;
  }
  if (!is_timestamp_valid && Event.type == MotionNotify)
  {
    x = Event.xmotion.x_root;
    y = Event.xmotion.y_root;
    /* need to move the viewport */
    if ((Scr.VxMax == 0 ||
	 (x >= edge_thickness &&
	  x < Scr.MyDisplayWidth  - edge_thickness)) &&
	(Scr.VyMax == 0 ||
	 (y >= edge_thickness &&
	  y < Scr.MyDisplayHeight - edge_thickness)))
    {
      return -1;
    }
  }
  if (Scr.ScrollResistance >= 10000 || (HorWarpSize == 0 && VertWarpSize==0))
  {
    is_timestamp_valid = False;
    add_time = 0;
    return 0;
  }
  if (Scr.VxMax == 0 && Scr.VyMax == 0)
  {
    is_timestamp_valid = False;
    add_time = 0;
    return 0;
  }
  if (!is_timestamp_valid)
  {
    is_timestamp_valid = True;
    my_timestamp = lastTimestamp;
    is_last_position_valid = False;
    add_time = 0;
    last_x = -1;
    last_y = -1;
  }
  else if (my_last_timestamp != lastTimestamp)
  {
    add_time = 0;
  }
  my_last_timestamp = lastTimestamp;

  do
  {
    if (XPending(dpy) > 0 &&
       (XCheckWindowEvent(
	 dpy, Scr.PanFrameTop.win, LeaveWindowMask, &Event) ||
	XCheckWindowEvent(
	  dpy, Scr.PanFrameBottom.win, LeaveWindowMask, &Event) ||
	XCheckWindowEvent(
	  dpy, Scr.PanFrameLeft.win, LeaveWindowMask, &Event) ||
	XCheckWindowEvent(
	  dpy, Scr.PanFrameRight.win, LeaveWindowMask, &Event)))
    {
      StashEventTime(&Event);
      is_timestamp_valid = False;
      add_time = 0;
      return 0;
    }
    else if (XCheckMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask, &e))
    {
      XPutBackEvent(dpy, &e);
      is_timestamp_valid = False;
      add_time = 0;
      return 0;
    }
    /* get pointer location */
    GetLocationFromEventOrQuery(dpy, Scr.Root, &Event, &x, &y);
    /* check actual pointer location since PanFrames can get buried under
       a window being moved or resized - mab */
    if (x >= edge_thickness && x < Scr.MyDisplayWidth-edge_thickness &&
        y >= edge_thickness && y < Scr.MyDisplayHeight-edge_thickness)
    {
      is_timestamp_valid = False;
      add_time = 0;
      discard_window_events(Scr.PanFrameTop.win, LeaveWindowMask);
      discard_window_events(Scr.PanFrameBottom.win, LeaveWindowMask);
      discard_window_events(Scr.PanFrameLeft.win, LeaveWindowMask);
      discard_window_events(Scr.PanFrameRight.win, LeaveWindowMask);
      return 0;
    }
    if (!fLoop && is_last_position_valid &&
	(x - last_x > MAX_PAGING_MOVE_DISTANCE ||
	 x - last_x < -MAX_PAGING_MOVE_DISTANCE ||
	 y - last_y > MAX_PAGING_MOVE_DISTANCE ||
	 y - last_y < -MAX_PAGING_MOVE_DISTANCE))
    {
      /* The pointer is moving too fast, prevent paging until it slows
       * down. Don't prevent paging when fLoop is set since we can't be
       * sure that HandlePaging will be called again. */
      is_timestamp_valid = True;
      my_timestamp = lastTimestamp;
      add_time = 0;
      last_x = x;
      last_y = y;
      return 0;
    }
    last_x = x;
    last_y = y;
    is_last_position_valid = True;
    usleep(10000);
    add_time += 10;
  } while (fLoop &&
	   lastTimestamp - my_timestamp + add_time < Scr.ScrollResistance);

  if (lastTimestamp - my_timestamp + add_time < Scr.ScrollResistance)
  {
    return 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 < edge_thickness)
    *delta_x = -HorWarpSize;
  else if (x >= Scr.MyDisplayWidth-edge_thickness)
    *delta_x = HorWarpSize;
  else
    *delta_x = 0;
  if (Scr.VxMax == 0) *delta_x = 0;
  if(y < edge_thickness)
    *delta_y = -VertWarpSize;
  else if (y >= Scr.MyDisplayHeight-edge_thickness)
    *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.edge_wrap_x))
    {
      *delta_x = -Scr.Vx;
      *xl = x - *delta_x;
    }
    else
    {
      *delta_x += Scr.VxMax + Scr.MyDisplayWidth;
      *xl = x + *delta_x % Scr.MyDisplayWidth + HorWarpSize;
    }
  }
  else if(Scr.Vx + *delta_x > Scr.VxMax)
  {
    if (!(Scr.flags.edge_wrap_x))
    {
      *delta_x = Scr.VxMax - Scr.Vx;
      *xl = x - *delta_x;
    }
    else
    {
      *delta_x -= Scr.VxMax +Scr.MyDisplayWidth;
      *xl = x + *delta_x % Scr.MyDisplayWidth - HorWarpSize;
    }
  }
  else
    *xl = x - *delta_x;

  if(Scr.Vy + *delta_y < 0)
  {
    if (!(Scr.flags.edge_wrap_y))
    {
      *delta_y = -Scr.Vy;
      *yt = y - *delta_y;
    }
    else
    {
      *delta_y += Scr.VyMax + Scr.MyDisplayHeight;
      *yt = y + *delta_y % Scr.MyDisplayHeight + VertWarpSize;
    }
  }
  else if(Scr.Vy + *delta_y > Scr.VyMax)
  {
    if (!(Scr.flags.edge_wrap_y))
    {
      *delta_y = Scr.VyMax - Scr.Vy;
      *yt = y - *delta_y;
    }
    else
    {
      *delta_y -= Scr.VyMax + Scr.MyDisplayHeight;
      *yt = y + *delta_y % Scr.MyDisplayHeight - VertWarpSize;
    }
  }
  else
    *yt = y - *delta_y;

  /* make sure the pointer isn't warped into the panframes */
  if (*xl < edge_thickness)
    *xl = edge_thickness;
  if (*yt < edge_thickness)
    *yt = edge_thickness;
  if (*xl >= Scr.MyDisplayWidth - edge_thickness)
    *xl = Scr.MyDisplayWidth - edge_thickness -1;
  if (*yt >= Scr.MyDisplayHeight - edge_thickness)
    *yt = Scr.MyDisplayHeight - edge_thickness -1;

  is_timestamp_valid = False;
  add_time = 0;
  if (*delta_x == 0 && *delta_y == 0)
  {
    return 0;
  }

  if(Grab)
    MyXGrabServer(dpy);
  /* Turn off the rubberband if its on */
  switch_move_resize_grid(False);
  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)
    MyXUngrabServer(dpy);

  return 1;
}


/* 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 applictions, 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
 * VIRTUAL screen and EdgeWrap for that direction is off.
 * (A special cursor for the EdgeWrap border could be nice) HEDU
 ****************************************************************************/
void checkPanFrames(void)
{
  Bool do_unmap_l = False;
  Bool do_unmap_r = False;
  Bool do_unmap_t = False;
  Bool do_unmap_b = False;

  if (!Scr.flags.windows_captured)
    return;

  /* thickness of 0 means remove the pan frames */
  if (edge_thickness == 0)
  {
    do_unmap_l = True;
    do_unmap_r = True;
    do_unmap_t = True;
    do_unmap_b = True;
  }
  /* Remove Pan frames if paging by edge-scroll is permanently or
   * temporarily disabled */
  if (Scr.EdgeScrollX == 0 || Scr.VxMax == 0)
  {
    do_unmap_l = True;
    do_unmap_r = True;
  }
  if (Scr.EdgeScrollY == 0 || Scr.VyMax == 0)
  {
    do_unmap_t = True;
    do_unmap_b = True;
  }
  if (Scr.Vx == 0 && !Scr.flags.edge_wrap_x)
  {
    do_unmap_l = True;
  }
  if (Scr.Vx == Scr.VxMax && !Scr.flags.edge_wrap_x)
  {
    do_unmap_r = True;
  }
  if (Scr.Vy == 0 && !Scr.flags.edge_wrap_y)
  {
    do_unmap_t = True;
  }
  if (Scr.Vy == Scr.VyMax && !Scr.flags.edge_wrap_y)
  {
    do_unmap_b = True;
  }

  /*
   * hide or show the windows
   */

  /* left */
  if (do_unmap_l)
  {
    if (Scr.PanFrameLeft.isMapped)
    {
      XUnmapWindow(dpy, Scr.PanFrameLeft.win);
      Scr.PanFrameLeft.isMapped = False;
    }
  }
  else
  {
    if (edge_thickness != last_edge_thickness)
    {
      XResizeWindow(
	dpy, Scr.PanFrameLeft.win, edge_thickness, Scr.MyDisplayHeight);
    }
    if (!Scr.PanFrameLeft.isMapped)
    {
      XMapRaised(dpy, Scr.PanFrameLeft.win);
      Scr.PanFrameLeft.isMapped = True;
    }
  }
  /* right */
  if (do_unmap_r)
  {
    if (Scr.PanFrameRight.isMapped)
    {
      XUnmapWindow(dpy, Scr.PanFrameRight.win);
      Scr.PanFrameRight.isMapped = False;
    }
  }
  else
  {
    if (edge_thickness != last_edge_thickness)
    {
      XMoveResizeWindow(
	dpy, Scr.PanFrameRight.win, Scr.MyDisplayWidth - edge_thickness, 0,
	edge_thickness, Scr.MyDisplayHeight);
    }
    if (!Scr.PanFrameRight.isMapped)
    {
      XMapRaised(dpy, Scr.PanFrameRight.win);
      Scr.PanFrameRight.isMapped = True;
    }
  }
  /* top */
  if (do_unmap_t)
  {
    if (Scr.PanFrameTop.isMapped)
    {
      XUnmapWindow(dpy, Scr.PanFrameTop.win);
      Scr.PanFrameTop.isMapped = False;
    }
  }
  else
  {
    if (edge_thickness != last_edge_thickness)
    {
      XResizeWindow(
	dpy, Scr.PanFrameTop.win, Scr.MyDisplayWidth, edge_thickness);
    }
    if (!Scr.PanFrameTop.isMapped)
    {
      XMapRaised(dpy, Scr.PanFrameTop.win);
      Scr.PanFrameTop.isMapped = True;
    }
  }
  /* bottom */
  if (do_unmap_b)
  {
    if (Scr.PanFrameBottom.isMapped)
    {
      XUnmapWindow(dpy, Scr.PanFrameBottom.win);
      Scr.PanFrameBottom.isMapped = False;
    }
  }
  else
  {
    if (edge_thickness != last_edge_thickness)
    {
      XMoveResizeWindow(
	dpy, Scr.PanFrameBottom.win, 0, Scr.MyDisplayHeight - edge_thickness,
	Scr.MyDisplayWidth, edge_thickness);
    }
    if (!Scr.PanFrameBottom.isMapped)
    {
      XMapRaised(dpy, Scr.PanFrameBottom.win);
      Scr.PanFrameBottom.isMapped = True;
    }
  }
  last_edge_thickness = edge_thickness;

  return;
}

/****************************************************************************
 *
 * 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(void)
{
  XSetWindowAttributes attributes;
  unsigned long valuemask;
  int saved_thickness;

  /* Not creating the frames disables all subsequent behavior */
  /* TKP. This is bad, it will cause an XMap request on a null window later*/
  /* if (edge_thickness == 0) return; */
  saved_thickness = edge_thickness;
  if (edge_thickness == 0)
    edge_thickness = 2;

  attributes.event_mask = XEVMASK_PANFW;
  valuemask=  (CWEventMask | CWCursor);

  attributes.cursor = Scr.FvwmCursors[CRS_TOP_EDGE];
  /* I know these overlap, it's useful when at (0,0) and the top one is
   * unmapped */
  Scr.PanFrameTop.win =
    XCreateWindow (dpy, Scr.Root,
		   0, 0,
		   Scr.MyDisplayWidth, edge_thickness,
		   0,	/* no border */
		   CopyFromParent, InputOnly,
		   CopyFromParent,
		   valuemask, &attributes);
  attributes.cursor = Scr.FvwmCursors[CRS_LEFT_EDGE];
  Scr.PanFrameLeft.win =
    XCreateWindow (dpy, Scr.Root,
		   0, 0,
		   edge_thickness, Scr.MyDisplayHeight,
		   0,	/* no border */
		   CopyFromParent, InputOnly, CopyFromParent,
		   valuemask, &attributes);
  attributes.cursor = Scr.FvwmCursors[CRS_RIGHT_EDGE];
  Scr.PanFrameRight.win =
    XCreateWindow (dpy, Scr.Root,
		   Scr.MyDisplayWidth - edge_thickness, 0,
		   edge_thickness, Scr.MyDisplayHeight,
		   0,	/* no border */
		   CopyFromParent, InputOnly, CopyFromParent,
		   valuemask, &attributes);
  attributes.cursor = Scr.FvwmCursors[CRS_BOTTOM_EDGE];
  Scr.PanFrameBottom.win =
    XCreateWindow (dpy, Scr.Root,
		   0, Scr.MyDisplayHeight - edge_thickness,
		   Scr.MyDisplayWidth, edge_thickness,
		   0,	/* no border */
		   CopyFromParent, InputOnly, CopyFromParent,
		   valuemask, &attributes);
  Scr.PanFrameTop.isMapped=Scr.PanFrameLeft.isMapped=
    Scr.PanFrameRight.isMapped= Scr.PanFrameBottom.isMapped=False;

  edge_thickness = saved_thickness;
}


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

  if (grab)
    MyXGrabServer(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 - 1;
  PageRight     =  Scr.MyDisplayWidth  - deltax - 1;
  PageTop       =  0 - deltay;
  PageLeft      =  0 - deltax;
  if (deltax || deltay)
  {
    prev_page_x = Scr.Vx;
    prev_page_y = Scr.Vy;
    prev_desk_and_page_page_x = Scr.Vx;
    prev_desk_and_page_page_y = Scr.Vy;
    prev_desk_and_page_desk = Scr.CurrentDesk;
  }
  Scr.Vx = newx;
  Scr.Vy = newy;

  if (deltax || deltay)
  {
     BroadcastPacket(
       M_NEW_PAGE, 5, Scr.Vx, Scr.Vy, Scr.CurrentDesk, Scr.VxMax, Scr.VyMax);

    /*
     * RBW - 11/13/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. Use the new	 stacking-order chain, rather than the
     * old last-focussed chain.
     *
     * domivogt (29-Nov-1999): It's faster to first map windows top to bottom
     * and then unmap windows bottom up.
     */
    t = get_next_window_in_stack_ring(&Scr.FvwmRoot);
    while (t != &Scr.FvwmRoot)
    {
      /*
       * If the window is moving into the viewport...
       */
      txl = t->frame_g.x;
      tyt = t->frame_g.y;
      txr = t->frame_g.x + t->frame_g.width - 1;
      tyb = t->frame_g.y + t->frame_g.height - 1;
      if ((IS_STICKY(t) || (IS_ICONIFIED(t) && IS_ICON_STICKY(t))) &&
          !IS_VIEWPORT_MOVED(t))
      {
	/* the absolute position has changed */
	t->normal_g.x -= deltax;
	t->normal_g.y -= deltay;
	t->max_g.x -= deltax;
	t->max_g.y -= deltay;
	SET_VIEWPORT_MOVED(t, 1); /*  Block double move.  */
      }
      if ((txr >= PageLeft && txl <= PageRight
	   && tyb >= PageTop && tyt <= PageBottom)
	  && !IS_VIEWPORT_MOVED(t)
	  && !IS_WINDOW_BEING_MOVED_OPAQUE(t))
      {
	SET_VIEWPORT_MOVED(t, 1); /*  Block double move.  */
	/* If the window is iconified, and sticky Icons is set,
	 * then the window should essentially be sticky */
	if(!(IS_ICONIFIED(t) && IS_ICON_STICKY(t)) && !IS_STICKY(t))
	{
	  if (IS_ICONIFIED(t))
	  {
	    t->icon_g.x += deltax;
	    t->icon_xl_loc += deltax;
	    t->icon_g.y += deltay;
	    if(t->icon_pixmap_w != None)
	      XMoveWindow(dpy,t->icon_pixmap_w,t->icon_g.x,
			  t->icon_g.y);
	    if(t->icon_w != None)
	      XMoveWindow(dpy,t->icon_w,t->icon_g.x,
			  t->icon_g.y+t->icon_p_height);
	    if(!(IS_ICON_UNMAPPED(t)))
	    {
	      BroadcastPacket(M_ICON_LOCATION, 7,
			      t->w, t->frame,
			      (unsigned long)t,
			      t->icon_g.x, t->icon_g.y,
			      t->icon_p_width,
			      t->icon_g.height+t->icon_p_height);
	    }
	  }
	  SetupFrame(t, t->frame_g.x+ deltax, t->frame_g.y + deltay,
		     t->frame_g.width, t->frame_g.height, False);
	}
      }
      /*  Bump to next win...	 */
      t = get_next_window_in_stack_ring(t);
    }
    t1 = get_prev_window_in_stack_ring(&Scr.FvwmRoot);
    while (t1 != &Scr.FvwmRoot)
    {
      /*
       *If the window is not moving into the viewport...
       */
      SET_VIEWPORT_MOVED(t, 1);
      txl = t1->frame_g.x;
      tyt = t1->frame_g.y;
      txr = t1->frame_g.x + t1->frame_g.width - 1;
      tyb = t1->frame_g.y + t1->frame_g.height - 1;
      if (! (txr >= PageLeft && txl <= PageRight
	     && tyb >= PageTop && tyt <= PageBottom)
	  && !IS_VIEWPORT_MOVED(t1)
	  && !IS_WINDOW_BEING_MOVED_OPAQUE(t1))
      {
	/* If the window is iconified, and sticky Icons is set,
	 * then the window should essentially be sticky */
	if (!(IS_ICONIFIED(t1) && IS_ICON_STICKY(t1)) && !IS_STICKY(t1))
	{
	  if (IS_ICONIFIED(t1))
	  {
	    t1->icon_g.x += deltax;
	    t1->icon_xl_loc += deltax;
	    t1->icon_g.y += deltay;
	    if(t1->icon_pixmap_w != None)
	      XMoveWindow(dpy,t1->icon_pixmap_w,
			  t1->icon_g.x,
			  t1->icon_g.y);
	    if(t1->icon_w != None)
	      XMoveWindow(dpy,t1->icon_w,t1->icon_g.x,
			  t1->icon_g.y+t1->icon_p_height);
	    if(!IS_ICON_UNMAPPED(t1))
	    {
	      BroadcastPacket(M_ICON_LOCATION, 7,
			      t1->w, t1->frame,
			      (unsigned long)t1,
			      t1->icon_g.x, t1->icon_g.y,
			      t1->icon_p_width,
			      t1->icon_g.height +
			      t1->icon_p_height);
	    }
	  }
	  SetupFrame(t1, t1->frame_g.x+ deltax,
		     t1->frame_g.y + deltay, t1->frame_g.width,
		     t1->frame_g.height, False);
	}
      }
      /*  Bump to next win...	 */
      t1 = get_prev_window_in_stack_ring(t1);
    }
    for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
    {
      if (IS_VIEWPORT_MOVED(t))
      {
	SET_VIEWPORT_MOVED(t, 0); /* Clear double move blocker. */
	GNOME_SetWinArea(t);
      }
      /* If its an icon, and its sticking, autoplace it so
       * that it doesn't wind up on top a a stationary
       * icon */
      if((IS_STICKY(t) || IS_ICON_STICKY(t)) &&
	 IS_ICONIFIED(t) && !IS_ICON_MOVED(t) &&
	 !IS_ICON_UNMAPPED(t))
	AutoPlaceIcon(t);
    }
  }
  checkPanFrames();
  if ((sf = get_focus_window()))
  {
    /* regrab buttons for focused window in case it is now obscured */
    focus_grab_buttons(sf, True);
  }

  /* do this with PanFrames too ??? HEDU */
  while(XCheckTypedEvent(dpy,MotionNotify,&Event))
    StashEventTime(&Event);
  if(grab)
    MyXUngrabServer(dpy);
  /* update GNOME pager */
  GNOME_SetCurrentArea();
}

/************************************************************************
 *
 * Unmap all windows on a desk -
 *   - Part 1 of a desktop switch
 *   - must eventually be followed by a call to MapDesk
 *   - unmaps from the bottom of the stack up
 *
 *************************************************************************/
static void UnmapDesk(int desk, Bool grab)
{
  FvwmWindow *t;
  FvwmWindow *sf = get_focus_window();

  if (grab)
  {
    MyXGrabServer(dpy);
  }
  for (t = get_prev_window_in_stack_ring(&Scr.FvwmRoot); t != &Scr.FvwmRoot;
       t = get_prev_window_in_stack_ring(t))
  {
    /* Only change mapping for non-sticky windows */
    if(!(IS_ICONIFIED(t) && IS_ICON_STICKY(t)) &&
       !(IS_STICKY(t)) && !IS_ICON_UNMAPPED(t))
    {
      if (t->Desk == desk)
      {
	if (sf == t)
	{
	  t->flags.is_focused_on_other_desk = 1;
	  t->FocusDesk = desk;
	  DeleteFocus(1);
	}
	else
	{
	  t->flags.is_focused_on_other_desk = 0;
	}
	unmap_window(t);
	SET_FULLY_VISIBLE(t, 0);
	SET_PARTIALLY_VISIBLE(t, 0);
      }
    }
    else
    {
      t->flags.is_focused_on_other_desk = 0;
    }
  }
  if (grab)
  {
    MyXUngrabServer(dpy);
  }

  return;
}


/************************************************************************
 *
 * Map all windows on a desk -
 *   - Part 2 of a desktop switch
 *   - only use if UnmapDesk has previously been called
 *   - maps from the top of the stack down
 *
 ************************************************************************/
static void MapDesk(int desk, Bool grab)
{
  FvwmWindow *t;
  FvwmWindow *FocusWin = NULL;
  FvwmWindow *StickyWin = NULL;
  FvwmWindow *sf = get_focus_window();

  Scr.flags.is_map_desk_in_progress = 1;
  if (grab)
  {
    MyXGrabServer(dpy);
  }
  for (t = get_next_window_in_stack_ring(&Scr.FvwmRoot); t != &Scr.FvwmRoot;
       t = get_next_window_in_stack_ring(t))
  {
    /* Only change mapping for non-sticky windows */
    if(!(IS_ICONIFIED(t) && IS_ICON_STICKY(t)) &&
       !(IS_STICKY(t)) && !IS_ICON_UNMAPPED(t))
    {
      if(t->Desk == desk)
      {
	map_window(t);
      }
    }
    else
    {
      /*  If window is sticky, just update its desk (it's still mapped).  */
      t->Desk = desk;
      if (sf == t)
      {
	StickyWin = t;
      }
    }
  }
  if (grab)
  {
    MyXUngrabServer(dpy);
  }

  for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
  {
    /*
      Autoplace any sticky icons, so that sticky icons from the old
      desk don't land on top of stationary ones on the new desk.
    */
    if((IS_STICKY(t) || IS_ICON_STICKY(t)) &&
       IS_ICONIFIED(t) && !IS_ICON_MOVED(t) &&
       !IS_ICON_UNMAPPED(t))
    {
      AutoPlaceIcon(t);
    }
    /*	Keep track of the last-focused window on the new desk.	*/
    if (t->flags.is_focused_on_other_desk && t->FocusDesk == desk)
    {
      t->flags.is_focused_on_other_desk = 0;
      FocusWin = t;
    }
  }

  /*  If a sticky window has focus, don't disturb it.  */
  if (!StickyWin)
  {
    /*  Otherwise, handle remembering the last-focused clicky window.  */
    if (FocusWin && (HAS_CLICK_FOCUS(FocusWin) || HAS_SLOPPY_FOCUS(FocusWin)))
    {
      ReturnFocusWindow(FocusWin, 1);
    }
    else if (FocusWin && !HAS_NEVER_FOCUS(FocusWin))
    {
      DeleteFocus(1);
    }
  }
  Scr.flags.is_map_desk_in_progress = 0;

  return;
}


/**************************************************************************
 *
 * Move to a new desktop
 *
 *************************************************************************/
void CMD_GotoDesk(F_CMD_ARGS)
{
  goto_desk(GetDeskNumber(action, Scr.CurrentDesk));
}

void CMD_Desk(F_CMD_ARGS)
{
  CMD_GotoDesk(F_PASS_ARGS);
}

void goto_desk(int desk)
{
  /*
    RBW - the unmapping operations are now removed to their own functions so
    they can also be used by the new GoToDeskAndPage command.
  */
  if (Scr.CurrentDesk != desk)
  {
    prev_desk = Scr.CurrentDesk;
    prev_desk_and_page_desk = Scr.CurrentDesk;
    prev_desk_and_page_page_x = Scr.Vx;
    prev_desk_and_page_page_y = Scr.Vy;
    UnmapDesk(Scr.CurrentDesk, True);
    Scr.CurrentDesk = desk;
    MapDesk(desk, True);
    BroadcastPacket(M_NEW_DESK, 1, Scr.CurrentDesk);
    /* FIXME: domivogt (22-Apr-2000): Fake a 'restack' for sticky window
     * upon desk change.  This is a workaround for a problem in FvwmPager:
     * The pager has a separate 'root' window for each desk.  If the active
     * desk changes, the pager destroys sticky mini windows and creates new
     * ones in the other desktop 'root'.  But the pager can't know where to
     * stack them.  So we have to tell it ecplicitly where they go :-(
     * This should be fixed in the pager, but right now the pager doesn't
     * maintain the stacking order. */
    BroadcastRestackAllWindows();
    GNOME_SetCurrentDesk();
    GNOME_SetDeskCount();
  }

  return;
}



/*************************************************************************
 *
 * Move to a new desktop and page at the same time.
 *   This function is designed for use by the Pager, and replaces the old
 *   GoToDesk 0 10000 hack.
 *   - unmap all windows on the current desk so they don't flash when the
 *     viewport is moved, then switch the viewport, then the desk.
 *
 *************************************************************************/
void CMD_GotoDeskAndPage(F_CMD_ARGS)
{
  int val[3];
  Bool is_new_desk;

  if (MatchToken(action, "prev"))
  {
    val[0] = prev_desk_and_page_desk;
    val[1] = prev_desk_and_page_page_x;
    val[2] = prev_desk_and_page_page_y;
  }
  else if (GetIntegerArguments(action, NULL, val, 3) == 3)
  {
    val[1] *= Scr.MyDisplayWidth;
    val[2] *= Scr.MyDisplayHeight;
  }
  else
  {
    return;
  }

  is_new_desk = (Scr.CurrentDesk != val[0]);
  if (is_new_desk)
  {
    UnmapDesk(Scr.CurrentDesk, True);
  }
  prev_desk_and_page_page_x = Scr.Vx;
  prev_desk_and_page_page_y = Scr.Vy;
  MoveViewport(val[1], val[2], True);
  if (is_new_desk)
  {
    prev_desk = Scr.CurrentDesk;
    prev_desk_and_page_desk = Scr.CurrentDesk;
    Scr.CurrentDesk = val[0];
    MapDesk(val[0], True);
    BroadcastPacket(M_NEW_DESK, 1, Scr.CurrentDesk);
    /* FIXME: domivogt (22-Apr-2000): Fake a 'restack' for sticky window
     * upon desk change.  This is a workaround for a problem in FvwmPager:
     * The pager has a separate 'root' window for each desk.  If the active
     * desk changes, the pager destroys sticky mini windows and creates new
     * ones in the other desktop 'root'.  But the pager can't know where to
     * stack them.  So we have to tell it ecplicitly where they go :-(
     * This should be fixed in the pager, but right now the pager doesn't
     * maintain the stacking order. */
    BroadcastRestackAllWindows();
  }
  else
  {
    BroadcastPacket(M_NEW_DESK, 1, Scr.CurrentDesk);
  }

  GNOME_SetCurrentDesk();
  GNOME_SetDeskCount();

  return;
}


void CMD_GotoPage(F_CMD_ARGS)
{
  int x;
  int y;

  if (!get_page_arguments(action, &x, &y))
  {
    fvwm_msg(ERR,"goto_page_func","GotoPage: invalid arguments: %s", action);
    return;
  }

  MoveViewport(x,y,True);
}


/**************************************************************************
 *
 * Move a window to a new desktop
 *
 *************************************************************************/
void do_move_window_to_desk(FvwmWindow *tmp_win, int desk)
{
  if (tmp_win == NULL)
    return;

  /*
   * Set the window's desktop, and map or unmap it as needed.
   */
  /* Only change mapping for non-sticky windows */
  if (!(IS_ICONIFIED(tmp_win) && IS_ICON_STICKY(tmp_win)) &&
      !IS_STICKY(tmp_win) /*&& !IS_ICON_UNMAPPED(tmp_win)*/)
  {
    if (tmp_win->Desk == Scr.CurrentDesk)
    {
      tmp_win->Desk = desk;
      if (tmp_win == get_focus_window())
      {
	DeleteFocus(0);
      }
      unmap_window(tmp_win);
      SET_FULLY_VISIBLE(tmp_win, 0);
      SET_PARTIALLY_VISIBLE(tmp_win, 0);
    }
    else if(desk == Scr.CurrentDesk)
    {
      tmp_win->Desk = desk;
      /* If its an icon, auto-place it */
      if (IS_ICONIFIED(tmp_win))
	AutoPlaceIcon(tmp_win);
      map_window(tmp_win);
    }
    else
    {
      tmp_win->Desk = desk;
    }
    BroadcastConfig(M_CONFIGURE_WINDOW,tmp_win);
  }
  GNOME_SetDeskCount();
  GNOME_SetDesk(tmp_win);
  GNOME_SetWinArea(tmp_win);
}

/* function with parsing of command line */
void CMD_MoveToDesk(F_CMD_ARGS)
{
  int desk;

  if (DeferExecution(eventp,&w,&tmp_win,&context,CRS_SELECT,ButtonRelease))
    return;
  desk = GetDeskNumber(action, tmp_win->Desk);
  if (desk == tmp_win->Desk)
    return;
  do_move_window_to_desk(tmp_win, desk);
}


void CMD_Scroll(F_CMD_ARGS)
{
  int x,y;
  int val1, val2, val1_unit, val2_unit;

  if (GetTwoArguments(action, &val1, &val2, &val1_unit, &val2_unit) != 2)
    /* to few parameters */
    return;

  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))
  {
    int xpixels = (Scr.VxMax / Scr.MyDisplayWidth + 1) * Scr.MyDisplayWidth;
    x %= xpixels;
    y += Scr.MyDisplayHeight * (1+((x-Scr.VxMax-1)/xpixels));
    if(y > Scr.VyMax)
      y %= (Scr.VyMax / Scr.MyDisplayHeight + 1) * Scr.MyDisplayHeight;
  }
  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))
  {
    int ypixels = (Scr.VyMax / Scr.MyDisplayHeight + 1) *
      Scr.MyDisplayHeight;
    y %= ypixels;
    x += Scr.MyDisplayWidth * (1+((y-Scr.VyMax-1)/ypixels));
    if(x > Scr.VxMax)
      x %= (Scr.VxMax / Scr.MyDisplayWidth + 1) * Scr.MyDisplayWidth;
  }
  if(((val2 <= -100000)||(val2>= 100000))&&(y<0))
  {
    y = Scr.VyMax;
    x -= Scr.MyDisplayWidth;
    if(x < 0)
      x=Scr.VxMax;
  }
  MoveViewport(x,y,True);
}

Bool get_page_arguments(char *action, int *page_x, int *page_y)
{
  int val[2];
  int suffix[2];
  char *token;
  char *taction;
  Bool xwrap = False;
  Bool ywrap = False;

  taction = GetNextToken(action, &token);
  if (token == NULL)
  {
    *page_x = Scr.Vx;
    *page_y = Scr.Vy;
    return True;
  }
  if (StrEquals(token, "prev"))
  {
    /* last page selected */
    *page_x = prev_page_x;
    *page_y = prev_page_y;
    free(token);
    return True;
  }
  if (StrEquals(token, "xwrap"))
  {
    xwrap = True;
  }
  else if (StrEquals(token, "ywrap"))
  {
    ywrap = True;
  }
  else if (StrEquals(token, "xywrap"))
  {
    xwrap = True;
    ywrap = True;
  }
  free(token);
  if (xwrap || ywrap)
  {
    GetNextToken(taction, &token);
    if (token == NULL)
    {
      *page_x = Scr.Vx;
      *page_y = Scr.Vy;
      return True;
    }
    action = taction;
    free(token);
  }

  if (GetSuffixedIntegerArguments(action, NULL, val, 2, "p", suffix) != 2)
    return False;

  if (val[0] >= 0 || suffix[0] == 1)
    *page_x = val[0] * Scr.MyDisplayWidth;
  else
    *page_x = (val[0]+1) * Scr.MyDisplayWidth + Scr.VxMax;
  if (val[1] >= 0 || suffix[1] == 1)
    *page_y = val[1] * Scr.MyDisplayHeight;
  else
    *page_y = (val[1]+1) * Scr.MyDisplayHeight + Scr.VyMax;
  /* handle 'p' suffix */
  if (suffix[0] == 1)
    *page_x += Scr.Vx;
  if (suffix[1] == 1)
    *page_y += Scr.Vy;

  /* limit to desktop size */
  if (*page_x < 0)
    *page_x = 0;
  if (*page_y < 0)
    *page_y = 0;
  if (*page_x > Scr.VxMax)
    *page_x = Scr.VxMax;
  if (*page_y > Scr.VyMax)
    *page_y = Scr.VyMax;

  return True;
}


syntax highlighted by Code2HTML, v. 0.9.1